• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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