1# 2# Copyright (C) 2024 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 17import os 18import unittest 19import subprocess 20import sys 21import io 22from unittest import mock 23from src.command import ProfilerCommand, ConfigCommand 24from src.device import AdbDevice 25from src.validation_error import ValidationError 26from src.torq import DEFAULT_DUR_MS, DEFAULT_OUT_DIR, PREDEFINED_PERFETTO_CONFIGS 27 28PROFILER_COMMAND_TYPE = "profiler" 29TEST_ERROR_MSG = "test-error" 30TEST_EXCEPTION = Exception(TEST_ERROR_MSG) 31TEST_VALIDATION_ERROR = ValidationError(TEST_ERROR_MSG, None) 32TEST_SERIAL = "test-serial" 33DEFAULT_PERFETTO_CONFIG = "default" 34TEST_USER_ID_1 = 0 35TEST_USER_ID_2 = 1 36TEST_USER_ID_3 = 2 37TEST_PACKAGE_1 = "test-package-1" 38TEST_PACKAGE_2 = "test-package-2" 39TEST_PACKAGE_3 = "test-package-3" 40TEST_DURATION = 0 41ANDROID_SDK_VERSION_S = 32 42ANDROID_SDK_VERSION_T = 33 43 44TEST_DEFAULT_CONFIG = f'''\ 45buffers: {{ 46 size_kb: 4096 47 fill_policy: RING_BUFFER 48}} 49buffers {{ 50 size_kb: 4096 51 fill_policy: RING_BUFFER 52}} 53buffers: {{ 54 size_kb: 260096 55 fill_policy: RING_BUFFER 56}} 57 58data_sources: {{ 59 config {{ 60 name: "linux.process_stats" 61 process_stats_config {{ 62 scan_all_processes_on_start: true 63 }} 64 }} 65}} 66 67data_sources: {{ 68 config {{ 69 name: "android.log" 70 android_log_config {{ 71 }} 72 }} 73}} 74 75data_sources {{ 76 config {{ 77 name: "android.packages_list" 78 }} 79}} 80 81data_sources: {{ 82 config {{ 83 name: "linux.sys_stats" 84 target_buffer: 1 85 sys_stats_config {{ 86 stat_period_ms: 500 87 stat_counters: STAT_CPU_TIMES 88 stat_counters: STAT_FORK_COUNT 89 meminfo_period_ms: 1000 90 meminfo_counters: MEMINFO_ACTIVE_ANON 91 meminfo_counters: MEMINFO_ACTIVE_FILE 92 meminfo_counters: MEMINFO_INACTIVE_ANON 93 meminfo_counters: MEMINFO_INACTIVE_FILE 94 meminfo_counters: MEMINFO_KERNEL_STACK 95 meminfo_counters: MEMINFO_MLOCKED 96 meminfo_counters: MEMINFO_SHMEM 97 meminfo_counters: MEMINFO_SLAB 98 meminfo_counters: MEMINFO_SLAB_UNRECLAIMABLE 99 meminfo_counters: MEMINFO_VMALLOC_USED 100 meminfo_counters: MEMINFO_MEM_FREE 101 meminfo_counters: MEMINFO_SWAP_FREE 102 vmstat_period_ms: 1000 103 vmstat_counters: VMSTAT_PGFAULT 104 vmstat_counters: VMSTAT_PGMAJFAULT 105 vmstat_counters: VMSTAT_PGFREE 106 vmstat_counters: VMSTAT_PGPGIN 107 vmstat_counters: VMSTAT_PGPGOUT 108 vmstat_counters: VMSTAT_PSWPIN 109 vmstat_counters: VMSTAT_PSWPOUT 110 vmstat_counters: VMSTAT_PGSCAN_DIRECT 111 vmstat_counters: VMSTAT_PGSTEAL_DIRECT 112 vmstat_counters: VMSTAT_PGSCAN_KSWAPD 113 vmstat_counters: VMSTAT_PGSTEAL_KSWAPD 114 vmstat_counters: VMSTAT_WORKINGSET_REFAULT 115 cpufreq_period_ms: 500 116 }} 117 }} 118}} 119 120data_sources: {{ 121 config {{ 122 name: "android.surfaceflinger.frametimeline" 123 target_buffer: 2 124 }} 125}} 126 127data_sources: {{ 128 config {{ 129 name: "linux.ftrace" 130 target_buffer: 2 131 ftrace_config {{ 132 ftrace_events: "dmabuf_heap/dma_heap_stat" 133 ftrace_events: "ftrace/print" 134 ftrace_events: "gpu_mem/gpu_mem_total" 135 ftrace_events: "ion/ion_stat" 136 ftrace_events: "kmem/ion_heap_grow" 137 ftrace_events: "kmem/ion_heap_shrink" 138 ftrace_events: "kmem/rss_stat" 139 ftrace_events: "lowmemorykiller/lowmemory_kill" 140 ftrace_events: "mm_event/mm_event_record" 141 ftrace_events: "oom/mark_victim" 142 ftrace_events: "oom/oom_score_adj_update" 143 ftrace_events: "power/cpu_frequency" 144 ftrace_events: "power/cpu_idle" 145 ftrace_events: "power/gpu_frequency" 146 ftrace_events: "power/suspend_resume" 147 ftrace_events: "power/wakeup_source_activate" 148 ftrace_events: "power/wakeup_source_deactivate" 149 ftrace_events: "sched/sched_blocked_reason" 150 ftrace_events: "sched/sched_process_exit" 151 ftrace_events: "sched/sched_process_free" 152 ftrace_events: "sched/sched_switch" 153 ftrace_events: "sched/sched_wakeup" 154 ftrace_events: "sched/sched_wakeup_new" 155 ftrace_events: "sched/sched_waking" 156 ftrace_events: "task/task_newtask" 157 ftrace_events: "task/task_rename" 158 ftrace_events: "vmscan/*" 159 ftrace_events: "workqueue/*" 160 atrace_categories: "aidl" 161 atrace_categories: "am" 162 atrace_categories: "dalvik" 163 atrace_categories: "binder_lock" 164 atrace_categories: "binder_driver" 165 atrace_categories: "bionic" 166 atrace_categories: "camera" 167 atrace_categories: "disk" 168 atrace_categories: "freq" 169 atrace_categories: "idle" 170 atrace_categories: "gfx" 171 atrace_categories: "hal" 172 atrace_categories: "input" 173 atrace_categories: "pm" 174 atrace_categories: "power" 175 atrace_categories: "res" 176 atrace_categories: "rro" 177 atrace_categories: "sched" 178 atrace_categories: "sm" 179 atrace_categories: "ss" 180 atrace_categories: "thermal" 181 atrace_categories: "video" 182 atrace_categories: "view" 183 atrace_categories: "wm" 184 atrace_apps: "lmkd" 185 atrace_apps: "system_server" 186 atrace_apps: "com.android.systemui" 187 atrace_apps: "com.google.android.gms" 188 atrace_apps: "com.google.android.gms.persistent" 189 atrace_apps: "android:ui" 190 atrace_apps: "com.google.android.apps.maps" 191 atrace_apps: "*" 192 buffer_size_kb: 16384 193 drain_period_ms: 150 194 symbolize_ksyms: true 195 }} 196 }} 197}} 198duration_ms: 10000 199write_into_file: true 200file_write_period_ms: 5000 201max_file_size_bytes: 100000000000 202flush_period_ms: 5000 203incremental_state_config {{ 204 clear_period_ms: 5000 205}} 206''' 207 208TEST_DEFAULT_CONFIG_OLD_ANDROID = f'''\ 209buffers: {{ 210 size_kb: 4096 211 fill_policy: RING_BUFFER 212}} 213buffers {{ 214 size_kb: 4096 215 fill_policy: RING_BUFFER 216}} 217buffers: {{ 218 size_kb: 260096 219 fill_policy: RING_BUFFER 220}} 221 222data_sources: {{ 223 config {{ 224 name: "linux.process_stats" 225 process_stats_config {{ 226 scan_all_processes_on_start: true 227 }} 228 }} 229}} 230 231data_sources: {{ 232 config {{ 233 name: "android.log" 234 android_log_config {{ 235 }} 236 }} 237}} 238 239data_sources {{ 240 config {{ 241 name: "android.packages_list" 242 }} 243}} 244 245data_sources: {{ 246 config {{ 247 name: "linux.sys_stats" 248 target_buffer: 1 249 sys_stats_config {{ 250 stat_period_ms: 500 251 stat_counters: STAT_CPU_TIMES 252 stat_counters: STAT_FORK_COUNT 253 meminfo_period_ms: 1000 254 meminfo_counters: MEMINFO_ACTIVE_ANON 255 meminfo_counters: MEMINFO_ACTIVE_FILE 256 meminfo_counters: MEMINFO_INACTIVE_ANON 257 meminfo_counters: MEMINFO_INACTIVE_FILE 258 meminfo_counters: MEMINFO_KERNEL_STACK 259 meminfo_counters: MEMINFO_MLOCKED 260 meminfo_counters: MEMINFO_SHMEM 261 meminfo_counters: MEMINFO_SLAB 262 meminfo_counters: MEMINFO_SLAB_UNRECLAIMABLE 263 meminfo_counters: MEMINFO_VMALLOC_USED 264 meminfo_counters: MEMINFO_MEM_FREE 265 meminfo_counters: MEMINFO_SWAP_FREE 266 vmstat_period_ms: 1000 267 vmstat_counters: VMSTAT_PGFAULT 268 vmstat_counters: VMSTAT_PGMAJFAULT 269 vmstat_counters: VMSTAT_PGFREE 270 vmstat_counters: VMSTAT_PGPGIN 271 vmstat_counters: VMSTAT_PGPGOUT 272 vmstat_counters: VMSTAT_PSWPIN 273 vmstat_counters: VMSTAT_PSWPOUT 274 vmstat_counters: VMSTAT_PGSCAN_DIRECT 275 vmstat_counters: VMSTAT_PGSTEAL_DIRECT 276 vmstat_counters: VMSTAT_PGSCAN_KSWAPD 277 vmstat_counters: VMSTAT_PGSTEAL_KSWAPD 278 vmstat_counters: VMSTAT_WORKINGSET_REFAULT 279 280 }} 281 }} 282}} 283 284data_sources: {{ 285 config {{ 286 name: "android.surfaceflinger.frametimeline" 287 target_buffer: 2 288 }} 289}} 290 291data_sources: {{ 292 config {{ 293 name: "linux.ftrace" 294 target_buffer: 2 295 ftrace_config {{ 296 ftrace_events: "dmabuf_heap/dma_heap_stat" 297 ftrace_events: "ftrace/print" 298 ftrace_events: "gpu_mem/gpu_mem_total" 299 ftrace_events: "ion/ion_stat" 300 ftrace_events: "kmem/ion_heap_grow" 301 ftrace_events: "kmem/ion_heap_shrink" 302 ftrace_events: "kmem/rss_stat" 303 ftrace_events: "lowmemorykiller/lowmemory_kill" 304 ftrace_events: "mm_event/mm_event_record" 305 ftrace_events: "oom/mark_victim" 306 ftrace_events: "oom/oom_score_adj_update" 307 ftrace_events: "power/cpu_frequency" 308 ftrace_events: "power/cpu_idle" 309 ftrace_events: "power/gpu_frequency" 310 ftrace_events: "power/suspend_resume" 311 ftrace_events: "power/wakeup_source_activate" 312 ftrace_events: "power/wakeup_source_deactivate" 313 ftrace_events: "sched/sched_blocked_reason" 314 ftrace_events: "sched/sched_process_exit" 315 ftrace_events: "sched/sched_process_free" 316 ftrace_events: "sched/sched_switch" 317 ftrace_events: "sched/sched_wakeup" 318 ftrace_events: "sched/sched_wakeup_new" 319 ftrace_events: "sched/sched_waking" 320 ftrace_events: "task/task_newtask" 321 ftrace_events: "task/task_rename" 322 ftrace_events: "vmscan/*" 323 ftrace_events: "workqueue/*" 324 atrace_categories: "aidl" 325 atrace_categories: "am" 326 atrace_categories: "dalvik" 327 atrace_categories: "binder_lock" 328 atrace_categories: "binder_driver" 329 atrace_categories: "bionic" 330 atrace_categories: "camera" 331 atrace_categories: "disk" 332 atrace_categories: "freq" 333 atrace_categories: "idle" 334 atrace_categories: "gfx" 335 atrace_categories: "hal" 336 atrace_categories: "input" 337 atrace_categories: "pm" 338 atrace_categories: "power" 339 atrace_categories: "res" 340 atrace_categories: "rro" 341 atrace_categories: "sched" 342 atrace_categories: "sm" 343 atrace_categories: "ss" 344 atrace_categories: "thermal" 345 atrace_categories: "video" 346 atrace_categories: "view" 347 atrace_categories: "wm" 348 atrace_apps: "lmkd" 349 atrace_apps: "system_server" 350 atrace_apps: "com.android.systemui" 351 atrace_apps: "com.google.android.gms" 352 atrace_apps: "com.google.android.gms.persistent" 353 atrace_apps: "android:ui" 354 atrace_apps: "com.google.android.apps.maps" 355 atrace_apps: "*" 356 buffer_size_kb: 16384 357 drain_period_ms: 150 358 symbolize_ksyms: true 359 }} 360 }} 361}} 362duration_ms: 10000 363write_into_file: true 364file_write_period_ms: 5000 365max_file_size_bytes: 100000000000 366flush_period_ms: 5000 367incremental_state_config {{ 368 clear_period_ms: 5000 369}} 370''' 371 372 373class ProfilerCommandExecutorUnitTest(unittest.TestCase): 374 375 def setUp(self): 376 self.command = ProfilerCommand( 377 PROFILER_COMMAND_TYPE, "custom", "perfetto", DEFAULT_OUT_DIR, DEFAULT_DUR_MS, 378 None, 1, None, DEFAULT_PERFETTO_CONFIG, None, False, None, None, None, 379 None, None, None) 380 self.mock_device = mock.create_autospec(AdbDevice, instance=True, 381 serial=TEST_SERIAL) 382 self.mock_device.check_device_connection.return_value = None 383 self.mock_device.get_android_sdk_version.return_value = ANDROID_SDK_VERSION_T 384 385 @mock.patch.object(subprocess, "Popen", autospec=True) 386 def test_execute_one_run_and_use_ui_success(self, mock_process): 387 with (mock.patch("src.command_executor.open_trace", autospec=True) 388 as mock_open_trace): 389 mock_open_trace.return_value = None 390 self.command.use_ui = True 391 self.mock_device.start_perfetto_trace.return_value = mock_process 392 393 error = self.command.execute(self.mock_device) 394 395 self.assertEqual(error, None) 396 self.assertEqual(self.mock_device.pull_file.call_count, 1) 397 398 @mock.patch.object(subprocess, "run", autospec=True) 399 @mock.patch.object(subprocess, "Popen", autospec=True) 400 @mock.patch.object(os.path, "exists", autospec=True) 401 def test_execute_one_simpleperf_run_success(self, 402 mock_exists, mock_process, mock_run): 403 with (mock.patch("src.command_executor.open_trace", autospec=True) 404 as mock_open_trace): 405 mock_open_trace.return_value = None 406 self.mock_device.start_simpleperf_trace.return_value = mock_process 407 mock_exists.return_value = True 408 mock_run.return_value = None 409 simpleperf_command = ProfilerCommand( 410 PROFILER_COMMAND_TYPE, "custom", "simpleperf", DEFAULT_OUT_DIR, 411 DEFAULT_DUR_MS, None, 1, None, DEFAULT_PERFETTO_CONFIG, None, False, 412 None, None, None, None, "/", "/") 413 simpleperf_command.use_ui = True 414 415 error = simpleperf_command.execute(self.mock_device) 416 417 self.assertEqual(error, None) 418 self.assertEqual(self.mock_device.pull_file.call_count, 1) 419 420 @mock.patch.object(subprocess, "run", autospec=True) 421 @mock.patch.object(subprocess, "Popen", autospec=True) 422 @mock.patch.object(os.path, "exists", autospec=True) 423 def test_execute_one_simpleperf_run_failure(self, 424 mock_exists, mock_process, mock_run): 425 with mock.patch("src.command_executor.open_trace", autospec=True): 426 self.mock_device.start_simpleperf_trace.return_value = mock_process 427 mock_exists.return_value = False 428 mock_run.return_value = None 429 simpleperf_command = ProfilerCommand( 430 PROFILER_COMMAND_TYPE, "custom", "simpleperf", DEFAULT_OUT_DIR, 431 DEFAULT_DUR_MS, None, 1, None, DEFAULT_PERFETTO_CONFIG, None, False, 432 None, None, None, None, "/", "/") 433 simpleperf_command.use_ui = True 434 435 with self.assertRaises(Exception) as e: 436 simpleperf_command.execute(self.mock_device) 437 438 self.assertEqual(str(e.exception), "Gecko file was not created.") 439 440 @mock.patch.object(subprocess, "Popen", autospec=True) 441 def test_execute_one_run_no_ui_success(self, mock_process): 442 self.mock_device.start_perfetto_trace.return_value = mock_process 443 444 error = self.command.execute(self.mock_device) 445 446 self.assertEqual(error, None) 447 self.assertEqual(self.mock_device.pull_file.call_count, 1) 448 449 def test_execute_check_device_connection_failure(self): 450 self.mock_device.check_device_connection.side_effect = TEST_EXCEPTION 451 452 with self.assertRaises(Exception) as e: 453 self.command.execute(self.mock_device) 454 455 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 456 self.assertEqual(self.mock_device.pull_file.call_count, 0) 457 458 def test_execute_root_device_failure(self): 459 self.mock_device.root_device.side_effect = TEST_EXCEPTION 460 461 with self.assertRaises(Exception) as e: 462 self.command.execute(self.mock_device) 463 464 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 465 self.assertEqual(self.mock_device.pull_file.call_count, 0) 466 467 def test_execute_create_default_config_no_dur_ms_error(self): 468 self.command.dur_ms = None 469 470 with self.assertRaises(ValueError) as e: 471 self.command.execute(self.mock_device) 472 473 self.assertEqual(str(e.exception), 474 "Cannot create config because a valid dur_ms was not set.") 475 self.assertEqual(self.mock_device.pull_file.call_count, 0) 476 477 def test_execute_create_default_config_bad_excluded_ftrace_event_error(self): 478 self.command.excluded_ftrace_events = ["mock-ftrace-event"] 479 480 error = self.command.execute(self.mock_device) 481 482 self.assertNotEqual(error, None) 483 self.assertEqual(error.message, 484 ("Cannot remove ftrace event %s from config because it is" 485 " not one of the config's ftrace events." 486 % self.command.excluded_ftrace_events[0])) 487 self.assertEqual(error.suggestion, ("Please specify one of the following" 488 " possible ftrace events:\n\t" 489 " dmabuf_heap/dma_heap_stat\n\t" 490 " ftrace/print\n\t" 491 " gpu_mem/gpu_mem_total\n\t" 492 " ion/ion_stat\n\t" 493 " kmem/ion_heap_grow\n\t" 494 " kmem/ion_heap_shrink\n\t" 495 " kmem/rss_stat\n\t" 496 " lowmemorykiller/lowmemory_kill\n\t" 497 " mm_event/mm_event_record\n\t" 498 " oom/mark_victim\n\t" 499 " oom/oom_score_adj_update\n\t" 500 " power/cpu_frequency\n\t" 501 " power/cpu_idle\n\t" 502 " power/gpu_frequency\n\t" 503 " power/suspend_resume\n\t" 504 " power/wakeup_source_activate\n\t" 505 " power/wakeup_source_deactivate\n\t" 506 " sched/sched_blocked_reason\n\t" 507 " sched/sched_process_exit\n\t" 508 " sched/sched_process_free\n\t" 509 " sched/sched_switch\n\t" 510 " sched/sched_wakeup\n\t" 511 " sched/sched_wakeup_new\n\t" 512 " sched/sched_waking\n\t" 513 " task/task_newtask\n\t" 514 " task/task_rename\n\t" 515 " vmscan/*\n\t" 516 " workqueue/*")) 517 self.assertEqual(self.mock_device.pull_file.call_count, 0) 518 519 def test_execute_create_default_config_bad_included_ftrace_event_error(self): 520 self.command.included_ftrace_events = ["power/cpu_idle"] 521 522 error = self.command.execute(self.mock_device) 523 524 self.assertNotEqual(error, None) 525 self.assertEqual(error.message, 526 ("Cannot add ftrace event %s to config because it is" 527 " already one of the config's ftrace events." 528 % self.command.included_ftrace_events[0])) 529 self.assertEqual(error.suggestion, ("Please do not specify any of the" 530 " following ftrace events that are" 531 " already included:\n\t" 532 " dmabuf_heap/dma_heap_stat\n\t" 533 " ftrace/print\n\t" 534 " gpu_mem/gpu_mem_total\n\t" 535 " ion/ion_stat\n\t" 536 " kmem/ion_heap_grow\n\t" 537 " kmem/ion_heap_shrink\n\t" 538 " kmem/rss_stat\n\t" 539 " lowmemorykiller/lowmemory_kill\n\t" 540 " mm_event/mm_event_record\n\t" 541 " oom/mark_victim\n\t" 542 " oom/oom_score_adj_update\n\t" 543 " power/cpu_frequency\n\t" 544 " power/cpu_idle\n\t" 545 " power/gpu_frequency\n\t" 546 " power/suspend_resume\n\t" 547 " power/wakeup_source_activate\n\t" 548 " power/wakeup_source_deactivate\n\t" 549 " sched/sched_blocked_reason\n\t" 550 " sched/sched_process_exit\n\t" 551 " sched/sched_process_free\n\t" 552 " sched/sched_switch\n\t" 553 " sched/sched_wakeup\n\t" 554 " sched/sched_wakeup_new\n\t" 555 " sched/sched_waking\n\t" 556 " task/task_newtask\n\t" 557 " task/task_rename\n\t" 558 " vmscan/*\n\t" 559 " workqueue/*")) 560 self.assertEqual(self.mock_device.pull_file.call_count, 0) 561 562 def test_execute_remove_file_failure(self): 563 self.mock_device.remove_file.side_effect = TEST_EXCEPTION 564 565 with self.assertRaises(Exception) as e: 566 self.command.execute(self.mock_device) 567 568 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 569 self.assertEqual(self.mock_device.pull_file.call_count, 0) 570 571 def test_execute_start_perfetto_trace_failure(self): 572 self.mock_device.start_perfetto_trace.side_effect = TEST_EXCEPTION 573 574 with self.assertRaises(Exception) as e: 575 self.command.execute(self.mock_device) 576 577 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 578 self.assertEqual(self.mock_device.pull_file.call_count, 0) 579 580 @mock.patch.object(subprocess, "Popen", autospec=True) 581 def test_execute_process_wait_failure(self, mock_process): 582 self.mock_device.start_perfetto_trace.return_value = mock_process 583 mock_process.wait.side_effect = TEST_EXCEPTION 584 585 with self.assertRaises(Exception) as e: 586 self.command.execute(self.mock_device) 587 588 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 589 self.assertEqual(self.mock_device.pull_file.call_count, 0) 590 591 @mock.patch.object(subprocess, "Popen", autospec=True) 592 def test_execute_pull_file_failure(self, mock_process): 593 self.mock_device.start_perfetto_trace.return_value = mock_process 594 self.mock_device.pull_file.side_effect = TEST_EXCEPTION 595 596 with self.assertRaises(Exception) as e: 597 self.command.execute(self.mock_device) 598 599 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 600 self.assertEqual(self.mock_device.pull_file.call_count, 1) 601 602 603class UserSwitchCommandExecutorUnitTest(unittest.TestCase): 604 605 def simulate_user_switch(self, user): 606 self.current_user = user 607 608 def setUp(self): 609 self.command = ProfilerCommand( 610 PROFILER_COMMAND_TYPE, "user-switch", "perfetto", DEFAULT_OUT_DIR, 611 DEFAULT_DUR_MS, None, 1, None, DEFAULT_PERFETTO_CONFIG, None, False, 612 None, None, None, None, None, None) 613 self.mock_device = mock.create_autospec(AdbDevice, instance=True, 614 serial=TEST_SERIAL) 615 self.mock_device.check_device_connection.return_value = None 616 self.mock_device.user_exists.return_value = None 617 self.current_user = TEST_USER_ID_3 618 self.mock_device.get_current_user.side_effect = lambda: self.current_user 619 self.mock_device.get_android_sdk_version.return_value = ANDROID_SDK_VERSION_T 620 621 @mock.patch.object(subprocess, "Popen", autospec=True) 622 def test_execute_all_users_different_success(self, mock_process): 623 self.command.from_user = TEST_USER_ID_1 624 self.command.to_user = TEST_USER_ID_2 625 self.mock_device.start_perfetto_trace.return_value = mock_process 626 self.mock_device.perform_user_switch.side_effect = ( 627 lambda user: self.simulate_user_switch(user)) 628 629 error = self.command.execute(self.mock_device) 630 631 self.assertEqual(error, None) 632 self.assertEqual(self.current_user, TEST_USER_ID_3) 633 self.assertEqual(self.mock_device.perform_user_switch.call_count, 3) 634 self.assertEqual(self.mock_device.pull_file.call_count, 1) 635 636 @mock.patch.object(subprocess, "Popen", autospec=True) 637 def test_execute_perform_user_switch_failure(self, mock_process): 638 self.command.from_user = TEST_USER_ID_2 639 self.command.to_user = TEST_USER_ID_1 640 self.mock_device.start_perfetto_trace.return_value = mock_process 641 self.mock_device.perform_user_switch.side_effect = TEST_EXCEPTION 642 643 with self.assertRaises(Exception) as e: 644 self.command.execute(self.mock_device) 645 646 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 647 self.assertEqual(self.mock_device.perform_user_switch.call_count, 1) 648 self.assertEqual(self.mock_device.pull_file.call_count, 0) 649 650 def test_execute_to_user_is_from_user_error(self): 651 self.command.from_user = TEST_USER_ID_1 652 self.command.to_user = TEST_USER_ID_1 653 654 error = self.command.execute(self.mock_device) 655 656 self.assertNotEqual(error, None) 657 self.assertEqual(error.message, ("Cannot perform user-switch to user %s" 658 " because the current user on device" 659 " %s is already %s." 660 % (TEST_USER_ID_1, TEST_SERIAL, 661 TEST_USER_ID_1))) 662 self.assertEqual(error.suggestion, ("Choose a --to-user ID that is" 663 " different than the --from-user ID.")) 664 self.assertEqual(self.mock_device.perform_user_switch.call_count, 0) 665 self.assertEqual(self.mock_device.pull_file.call_count, 0) 666 667 @mock.patch.object(subprocess, "Popen", autospec=True) 668 def test_execute_from_user_empty_success(self, mock_process): 669 self.command.from_user = None 670 self.command.to_user = TEST_USER_ID_2 671 self.mock_device.start_perfetto_trace.return_value = mock_process 672 self.mock_device.perform_user_switch.side_effect = ( 673 lambda user: self.simulate_user_switch(user)) 674 675 error = self.command.execute(self.mock_device) 676 677 self.assertEqual(error, None) 678 self.assertEqual(self.current_user, TEST_USER_ID_3) 679 self.assertEqual(self.mock_device.perform_user_switch.call_count, 2) 680 self.assertEqual(self.mock_device.pull_file.call_count, 1) 681 682 def test_execute_to_user_is_current_user_and_from_user_empty_error(self): 683 self.command.from_user = None 684 self.command.to_user = self.current_user 685 686 error = self.command.execute(self.mock_device) 687 688 self.assertNotEqual(error, None) 689 self.assertEqual(error.message, ("Cannot perform user-switch to user %s" 690 " because the current user on device" 691 " %s is already %s." 692 % (self.current_user, TEST_SERIAL, 693 self.current_user))) 694 self.assertEqual(error.suggestion, ("Choose a --to-user ID that is" 695 " different than the --from-user ID.")) 696 self.assertEqual(self.mock_device.perform_user_switch.call_count, 0) 697 self.assertEqual(self.mock_device.pull_file.call_count, 0) 698 699 @mock.patch.object(subprocess, "Popen", autospec=True) 700 def test_execute_from_user_is_current_user_success(self, mock_process): 701 self.command.from_user = self.current_user 702 self.command.to_user = TEST_USER_ID_2 703 self.mock_device.start_perfetto_trace.return_value = mock_process 704 self.mock_device.perform_user_switch.side_effect = ( 705 lambda user: self.simulate_user_switch(user)) 706 707 error = self.command.execute(self.mock_device) 708 709 self.assertEqual(error, None) 710 self.assertEqual(self.current_user, TEST_USER_ID_3) 711 self.assertEqual(self.mock_device.perform_user_switch.call_count, 2) 712 self.assertEqual(self.mock_device.pull_file.call_count, 1) 713 714 @mock.patch.object(subprocess, "Popen", autospec=True) 715 def test_execute_to_user_is_current_user_success(self, mock_process): 716 self.command.from_user = TEST_USER_ID_1 717 self.command.to_user = self.current_user 718 self.mock_device.start_perfetto_trace.return_value = mock_process 719 self.mock_device.perform_user_switch.side_effect = ( 720 lambda user: self.simulate_user_switch(user)) 721 722 error = self.command.execute(self.mock_device) 723 724 self.assertEqual(error, None) 725 self.assertEqual(self.current_user, TEST_USER_ID_3) 726 self.assertEqual(self.mock_device.perform_user_switch.call_count, 2) 727 self.assertEqual(self.mock_device.pull_file.call_count, 1) 728 729 730class BootCommandExecutorUnitTest(unittest.TestCase): 731 732 def setUp(self): 733 self.command = ProfilerCommand( 734 PROFILER_COMMAND_TYPE, "boot", "perfetto", DEFAULT_OUT_DIR, 735 TEST_DURATION, None, 1, None, DEFAULT_PERFETTO_CONFIG, TEST_DURATION, 736 False, None, None, None, None, None, None) 737 self.mock_device = mock.create_autospec(AdbDevice, instance=True, 738 serial=TEST_SERIAL) 739 self.mock_device.check_device_connection.return_value = None 740 self.mock_device.get_android_sdk_version.return_value = ANDROID_SDK_VERSION_T 741 742 def test_execute_reboot_success(self): 743 error = self.command.execute(self.mock_device) 744 745 self.assertEqual(error, None) 746 self.assertEqual(self.mock_device.reboot.call_count, 1) 747 self.assertEqual(self.mock_device.pull_file.call_count, 1) 748 749 def test_execute_reboot_multiple_runs_success(self): 750 self.command.runs = 5 751 752 error = self.command.execute(self.mock_device) 753 754 self.assertEqual(error, None) 755 self.assertEqual(self.mock_device.reboot.call_count, 5) 756 self.assertEqual(self.mock_device.pull_file.call_count, 5) 757 758 def test_execute_reboot_failure(self): 759 self.mock_device.reboot.side_effect = TEST_EXCEPTION 760 761 with self.assertRaises(Exception) as e: 762 self.command.execute(self.mock_device) 763 764 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 765 self.assertEqual(self.mock_device.reboot.call_count, 1) 766 self.assertEqual(self.mock_device.pull_file.call_count, 0) 767 768 def test_execute_get_prop_and_old_android_version_failure(self): 769 self.mock_device.get_android_sdk_version.return_value = ANDROID_SDK_VERSION_S 770 771 error = self.command.execute(self.mock_device) 772 773 self.assertNotEqual(error, None) 774 self.assertEqual(error.message, ( 775 "Cannot perform trace on boot because only devices with version Android 13 (T)" 776 " or newer can be configured to automatically start recording traces on boot.")) 777 self.assertEqual(error.suggestion, ( 778 "Update your device or use a different device with Android 13 (T) or" 779 " newer.")) 780 self.assertEqual(self.mock_device.reboot.call_count, 0) 781 self.assertEqual(self.mock_device.pull_file.call_count, 0) 782 783 def test_execute_write_to_file_failure(self): 784 self.mock_device.write_to_file.side_effect = TEST_EXCEPTION 785 786 with self.assertRaises(Exception) as e: 787 self.command.execute(self.mock_device) 788 789 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 790 self.assertEqual(self.mock_device.reboot.call_count, 0) 791 self.assertEqual(self.mock_device.pull_file.call_count, 0) 792 793 def test_execute_remove_file_failure(self): 794 self.mock_device.remove_file.side_effect = TEST_EXCEPTION 795 796 with self.assertRaises(Exception) as e: 797 self.command.execute(self.mock_device) 798 799 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 800 self.assertEqual(self.mock_device.reboot.call_count, 0) 801 self.assertEqual(self.mock_device.pull_file.call_count, 0) 802 803 def test_execute_set_prop_failure(self): 804 self.mock_device.set_prop.side_effect = TEST_EXCEPTION 805 806 with self.assertRaises(Exception) as e: 807 self.command.execute(self.mock_device) 808 809 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 810 self.assertEqual(self.mock_device.reboot.call_count, 0) 811 self.assertEqual(self.mock_device.pull_file.call_count, 0) 812 813 def test_execute_wait_for_device_failure(self): 814 self.mock_device.wait_for_device.side_effect = TEST_EXCEPTION 815 816 with self.assertRaises(Exception) as e: 817 self.command.execute(self.mock_device) 818 819 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 820 self.assertEqual(self.mock_device.reboot.call_count, 1) 821 self.assertEqual(self.mock_device.pull_file.call_count, 0) 822 823 def test_execute_second_root_device_failure(self): 824 self.mock_device.root_device.side_effect = [None, TEST_EXCEPTION] 825 826 with self.assertRaises(Exception) as e: 827 self.command.execute(self.mock_device) 828 829 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 830 self.assertEqual(self.mock_device.reboot.call_count, 1) 831 self.assertEqual(self.mock_device.pull_file.call_count, 0) 832 833 def test_execute_wait_for_boot_to_complete_failure(self): 834 self.mock_device.wait_for_boot_to_complete.side_effect = TEST_EXCEPTION 835 836 with self.assertRaises(Exception) as e: 837 self.command.execute(self.mock_device) 838 839 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 840 self.assertEqual(self.mock_device.reboot.call_count, 1) 841 self.assertEqual(self.mock_device.pull_file.call_count, 0) 842 843 844class AppStartupExecutorUnitTest(unittest.TestCase): 845 846 def setUp(self): 847 self.command = ProfilerCommand( 848 PROFILER_COMMAND_TYPE, "app-startup", "perfetto", DEFAULT_OUT_DIR, 849 DEFAULT_DUR_MS, TEST_PACKAGE_1, 1, None, DEFAULT_PERFETTO_CONFIG, None, 850 False, None, None, None, None, None, None) 851 self.mock_device = mock.create_autospec(AdbDevice, instance=True, 852 serial=TEST_SERIAL) 853 self.mock_device.check_device_connection.return_value = None 854 self.mock_device.get_packages.return_value = [TEST_PACKAGE_1, 855 TEST_PACKAGE_2] 856 self.mock_device.is_package_running.return_value = False 857 self.mock_device.get_android_sdk_version.return_value = ANDROID_SDK_VERSION_T 858 859 def test_app_startup_command_success(self): 860 self.mock_device.start_package.return_value = None 861 862 error = self.command.execute(self.mock_device) 863 864 self.assertEqual(error, None) 865 self.assertEqual(self.mock_device.start_package.call_count, 1) 866 self.assertEqual(self.mock_device.force_stop_package.call_count, 1) 867 self.assertEqual(self.mock_device.pull_file.call_count, 1) 868 869 def test_start_package_failure(self): 870 self.mock_device.start_package.side_effect = TEST_EXCEPTION 871 872 with self.assertRaises(Exception) as e: 873 self.command.execute(self.mock_device) 874 875 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 876 self.assertEqual(self.mock_device.start_package.call_count, 1) 877 self.assertEqual(self.mock_device.force_stop_package.call_count, 0) 878 self.assertEqual(self.mock_device.pull_file.call_count, 0) 879 880 def test_get_packages_failure(self): 881 self.mock_device.get_packages.side_effect = TEST_EXCEPTION 882 883 with self.assertRaises(Exception) as e: 884 self.command.execute(self.mock_device) 885 886 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 887 self.assertEqual(self.mock_device.start_package.call_count, 0) 888 self.assertEqual(self.mock_device.pull_file.call_count, 0) 889 890 def test_package_does_not_exist_failure(self): 891 self.mock_device.get_packages.return_value = [TEST_PACKAGE_2, 892 TEST_PACKAGE_3] 893 894 error = self.command.execute(self.mock_device) 895 896 self.assertNotEqual(error, None) 897 self.assertEqual(error.message, ( 898 "Package %s does not exist on device with serial %s." 899 % (TEST_PACKAGE_1, self.mock_device.serial))) 900 self.assertEqual(error.suggestion, ( 901 "Select from one of the following packages on device with serial %s:" 902 " \n\t %s,\n\t %s" % (self.mock_device.serial, TEST_PACKAGE_2, 903 TEST_PACKAGE_3))) 904 self.assertEqual(self.mock_device.start_package.call_count, 0) 905 self.assertEqual(self.mock_device.pull_file.call_count, 0) 906 907 def test_package_is_running_failure(self): 908 self.mock_device.is_package_running.return_value = True 909 910 error = self.command.execute(self.mock_device) 911 912 self.assertNotEqual(error, None) 913 self.assertEqual(error.message, ( 914 "Package %s is already running on device with serial %s." 915 % (TEST_PACKAGE_1, self.mock_device.serial))) 916 self.assertEqual(error.suggestion, ( 917 "Run 'adb -s %s shell am force-stop %s' to close the package %s before" 918 " trying to start it." 919 % (self.mock_device.serial, TEST_PACKAGE_1, TEST_PACKAGE_1))) 920 self.assertEqual(self.mock_device.start_package.call_count, 0) 921 self.assertEqual(self.mock_device.pull_file.call_count, 0) 922 923 def test_force_stop_package_failure(self): 924 self.mock_device.start_package.return_value = None 925 self.mock_device.force_stop_package.side_effect = TEST_EXCEPTION 926 927 with self.assertRaises(Exception) as e: 928 self.command.execute(self.mock_device) 929 930 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 931 self.assertEqual(self.mock_device.start_package.call_count, 1) 932 self.assertEqual(self.mock_device.pull_file.call_count, 0) 933 934 def test_kill_pid_success(self): 935 self.mock_device.start_package.return_value = TEST_VALIDATION_ERROR 936 937 error = self.command.execute(self.mock_device) 938 939 self.assertNotEqual(error, None) 940 self.assertEqual(error.message, TEST_ERROR_MSG) 941 self.assertEqual(error.suggestion, None) 942 self.assertEqual(self.mock_device.start_package.call_count, 1) 943 self.assertEqual(self.mock_device.kill_pid.call_count, 1) 944 self.assertEqual(self.mock_device.pull_file.call_count, 0) 945 946 def test_kill_pid_failure(self): 947 self.mock_device.start_package.return_value = TEST_VALIDATION_ERROR 948 self.mock_device.kill_pid.side_effect = TEST_EXCEPTION 949 950 with self.assertRaises(Exception) as e: 951 self.command.execute(self.mock_device) 952 953 self.assertEqual(str(e.exception), TEST_ERROR_MSG) 954 self.assertEqual(self.mock_device.start_package.call_count, 1) 955 self.assertEqual(self.mock_device.kill_pid.call_count, 1) 956 self.assertEqual(self.mock_device.pull_file.call_count, 0) 957 958 959class ConfigCommandExecutorUnitTest(unittest.TestCase): 960 961 def setUp(self): 962 self.mock_device = mock.create_autospec(AdbDevice, instance=True, 963 serial=TEST_SERIAL) 964 self.mock_device.check_device_connection.return_value = None 965 self.mock_device.get_android_sdk_version.return_value = ( 966 ANDROID_SDK_VERSION_T) 967 968 @staticmethod 969 def generate_mock_completed_process(stdout_string=b'\n', stderr_string=b'\n'): 970 return mock.create_autospec(subprocess.CompletedProcess, instance=True, 971 stdout=stdout_string, stderr=stderr_string) 972 973 def test_config_list(self): 974 terminal_output = io.StringIO() 975 sys.stdout = terminal_output 976 977 self.command = ConfigCommand("config list", None, None, None, None, None) 978 error = self.command.execute(self.mock_device) 979 980 self.assertEqual(error, None) 981 self.assertEqual(terminal_output.getvalue(), ( 982 "%s\n" % "\n".join(list(PREDEFINED_PERFETTO_CONFIGS.keys())))) 983 984 def test_config_show(self): 985 terminal_output = io.StringIO() 986 sys.stdout = terminal_output 987 988 self.command = ConfigCommand("config show", "default", None, DEFAULT_DUR_MS, 989 None, None) 990 error = self.command.execute(self.mock_device) 991 992 self.assertEqual(error, None) 993 self.assertEqual(terminal_output.getvalue(), TEST_DEFAULT_CONFIG) 994 995 def test_config_show_no_device_connection(self): 996 self.mock_device.check_device_connection.return_value = ( 997 TEST_VALIDATION_ERROR) 998 terminal_output = io.StringIO() 999 sys.stdout = terminal_output 1000 1001 self.command = ConfigCommand("config show", "default", None, DEFAULT_DUR_MS, 1002 None, None) 1003 error = self.command.execute(self.mock_device) 1004 1005 self.assertEqual(error, None) 1006 self.assertEqual(terminal_output.getvalue(), TEST_DEFAULT_CONFIG) 1007 1008 def test_config_show_old_android_version(self): 1009 self.mock_device.get_android_sdk_version.return_value = ( 1010 ANDROID_SDK_VERSION_S) 1011 terminal_output = io.StringIO() 1012 sys.stdout = terminal_output 1013 1014 self.command = ConfigCommand("config show", "default", None, DEFAULT_DUR_MS, 1015 None, None) 1016 error = self.command.execute(self.mock_device) 1017 1018 self.assertEqual(error, None) 1019 self.assertEqual(terminal_output.getvalue(), 1020 TEST_DEFAULT_CONFIG_OLD_ANDROID) 1021 1022 @mock.patch.object(subprocess, "run", autospec=True) 1023 def test_config_pull(self, mock_subprocess_run): 1024 mock_subprocess_run.return_value = self.generate_mock_completed_process() 1025 self.command = ConfigCommand("config pull", "default", None, DEFAULT_DUR_MS, 1026 None, None) 1027 1028 error = self.command.execute(self.mock_device) 1029 1030 self.assertEqual(error, None) 1031 1032 @mock.patch.object(subprocess, "run", autospec=True) 1033 def test_config_pull_no_device_connection(self, mock_subprocess_run): 1034 self.mock_device.check_device_connection.return_value = ( 1035 TEST_VALIDATION_ERROR) 1036 mock_subprocess_run.return_value = self.generate_mock_completed_process() 1037 self.command = ConfigCommand("config pull", "default", None, DEFAULT_DUR_MS, 1038 None, None) 1039 1040 error = self.command.execute(self.mock_device) 1041 1042 self.assertEqual(error, None) 1043 1044 @mock.patch.object(subprocess, "run", autospec=True) 1045 def test_config_pull_old_android_version(self, mock_subprocess_run): 1046 self.mock_device.get_android_sdk_version.return_value = ( 1047 ANDROID_SDK_VERSION_S) 1048 mock_subprocess_run.return_value = self.generate_mock_completed_process() 1049 self.command = ConfigCommand("config pull", "default", None, DEFAULT_DUR_MS, 1050 None, None) 1051 1052 error = self.command.execute(self.mock_device) 1053 1054 self.assertEqual(error, None) 1055 1056 1057if __name__ == '__main__': 1058 unittest.main() 1059