1#!/usr/bin/env python 2# 3# Copyright (C) 2017 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17"""test.py: Tests for simpleperf python scripts. 18 19These are smoke tests Using examples to run python scripts. 20For each example, we go through the steps of running each python script. 21Examples are collected from simpleperf/demo, which includes: 22 SimpleperfExamplePureJava 23 SimpleperfExampleWithNative 24 SimpleperfExampleOfKotlin 25 26Tested python scripts include: 27 app_profiler.py 28 report.py 29 annotate.py 30 report_sample.py 31 pprof_proto_generator.py 32 report_html.py 33 34Test using both `adb root` and `adb unroot`. 35 36""" 37from __future__ import print_function 38import argparse 39import collections 40import filecmp 41import fnmatch 42import inspect 43import json 44import logging 45import os 46import re 47import shutil 48import signal 49import subprocess 50import sys 51import time 52import types 53import unittest 54 55from app_profiler import NativeLibDownloader 56from binary_cache_builder import BinaryCacheBuilder 57from simpleperf_report_lib import ReportLib 58from utils import log_exit, log_info, log_fatal 59from utils import AdbHelper, Addr2Nearestline, bytes_to_str, find_tool_path, get_script_dir 60from utils import is_elf_file, is_python3, is_windows, Objdump, ReadElf, remove, SourceFileSearcher 61from utils import str_to_bytes 62 63try: 64 # pylint: disable=unused-import 65 import google.protobuf 66 # pylint: disable=ungrouped-imports 67 from pprof_proto_generator import load_pprof_profile 68 HAS_GOOGLE_PROTOBUF = True 69except ImportError: 70 HAS_GOOGLE_PROTOBUF = False 71 72INFERNO_SCRIPT = os.path.join(get_script_dir(), "inferno.bat" if is_windows() else "./inferno.sh") 73 74 75class TestLogger(object): 76 """ Write test progress in sys.stderr and keep verbose log in log file. """ 77 def __init__(self): 78 self.log_file = self.get_log_file(3 if is_python3() else 2) 79 if os.path.isfile(self.log_file): 80 remove(self.log_file) 81 # Logs can come from multiple processes. So use append mode to avoid overwrite. 82 self.log_fh = open(self.log_file, 'a') 83 logging.basicConfig(filename=self.log_file) 84 85 @staticmethod 86 def get_log_file(python_version): 87 return 'test_python_%d.log' % python_version 88 89 def writeln(self, s): 90 return self.write(s + '\n') 91 92 def write(self, s): 93 sys.stderr.write(s) 94 self.log_fh.write(s) 95 # Child processes can also write to log file, so flush it immediately to keep the order. 96 self.flush() 97 98 def flush(self): 99 self.log_fh.flush() 100 101 102TEST_LOGGER = TestLogger() 103 104 105def get_device_features(): 106 adb = AdbHelper() 107 adb.check_run_and_return_output(['push', 108 'bin/android/%s/simpleperf' % adb.get_device_arch(), 109 '/data/local/tmp']) 110 adb.check_run_and_return_output(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf']) 111 return adb.check_run_and_return_output(['shell', '/data/local/tmp/simpleperf', 'list', 112 '--show-features']) 113 114def is_trace_offcpu_supported(): 115 if not hasattr(is_trace_offcpu_supported, 'value'): 116 is_trace_offcpu_supported.value = 'trace-offcpu' in get_device_features() 117 return is_trace_offcpu_supported.value 118 119 120def android_version(): 121 """ Get Android version on device, like 7 is for Android N, 8 is for Android O.""" 122 if not hasattr(android_version, 'value'): 123 android_version.value = AdbHelper().get_android_version() 124 return android_version.value 125 126 127def build_testdata(): 128 """ Collect testdata from ../testdata and ../demo. """ 129 from_testdata_path = os.path.join('..', 'testdata') 130 from_demo_path = os.path.join('..', 'demo') 131 from_script_testdata_path = 'script_testdata' 132 if (not os.path.isdir(from_testdata_path) or not os.path.isdir(from_demo_path) or 133 not from_script_testdata_path): 134 return 135 copy_demo_list = ['SimpleperfExamplePureJava', 'SimpleperfExampleWithNative', 136 'SimpleperfExampleOfKotlin'] 137 138 testdata_path = "testdata" 139 remove(testdata_path) 140 shutil.copytree(from_testdata_path, testdata_path) 141 for demo in copy_demo_list: 142 shutil.copytree(os.path.join(from_demo_path, demo), os.path.join(testdata_path, demo)) 143 for f in os.listdir(from_script_testdata_path): 144 shutil.copy(os.path.join(from_script_testdata_path, f), testdata_path) 145 146class TestBase(unittest.TestCase): 147 def run_cmd(self, args, return_output=False): 148 if args[0].endswith('.py'): 149 args = [sys.executable] + args 150 use_shell = args[0].endswith('.bat') 151 try: 152 if not return_output: 153 returncode = subprocess.call(args, shell=use_shell, stderr=TEST_LOGGER.log_fh) 154 else: 155 subproc = subprocess.Popen(args, stdout=subprocess.PIPE, 156 stderr=TEST_LOGGER.log_fh, shell=use_shell) 157 (output_data, _) = subproc.communicate() 158 output_data = bytes_to_str(output_data) 159 returncode = subproc.returncode 160 except OSError: 161 returncode = None 162 self.assertEqual(returncode, 0, msg="failed to run cmd: %s" % args) 163 if return_output: 164 return output_data 165 return '' 166 167 def check_strings_in_file(self, filename, strings): 168 self.check_exist(filename=filename) 169 with open(filename, 'r') as fh: 170 self.check_strings_in_content(fh.read(), strings) 171 172 def check_exist(self, filename=None, dirname=None): 173 if filename: 174 self.assertTrue(os.path.isfile(filename), filename) 175 if dirname: 176 self.assertTrue(os.path.isdir(dirname), dirname) 177 178 def check_strings_in_content(self, content, strings): 179 for s in strings: 180 self.assertNotEqual(content.find(s), -1, "s: %s, content: %s" % (s, content)) 181 182 183class TestExampleBase(TestBase): 184 @classmethod 185 def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False): 186 cls.adb = AdbHelper(enable_switch_to_root=adb_root) 187 cls.example_path = os.path.join("testdata", example_name) 188 if not os.path.isdir(cls.example_path): 189 log_fatal("can't find " + cls.example_path) 190 for root, _, files in os.walk(cls.example_path): 191 if 'app-profiling.apk' in files: 192 cls.apk_path = os.path.join(root, 'app-profiling.apk') 193 break 194 if not hasattr(cls, 'apk_path'): 195 log_fatal("can't find app-profiling.apk under " + cls.example_path) 196 cls.package_name = package_name 197 cls.activity_name = activity_name 198 cls.abi = "arm64" 199 if abi and abi != "arm64" and abi.find("arm") != -1: 200 cls.abi = "arm" 201 args = ["install", "-r"] 202 if abi: 203 args += ["--abi", abi] 204 args.append(cls.apk_path) 205 cls.adb.check_run(args) 206 cls.adb_root = adb_root 207 cls.compiled = False 208 cls.has_perf_data_for_report = False 209 # On Android >= P (version 9), we can profile JITed and interpreted Java code. 210 # So only compile Java code on Android <= O (version 8). 211 cls.use_compiled_java_code = android_version() <= 8 212 213 def setUp(self): 214 if self.id().find('TraceOffCpu') != -1 and not is_trace_offcpu_supported(): 215 self.skipTest('trace-offcpu is not supported on device') 216 cls = self.__class__ 217 if not cls.has_perf_data_for_report: 218 cls.has_perf_data_for_report = True 219 self.run_app_profiler() 220 shutil.copy('perf.data', 'perf.data_for_report') 221 remove('binary_cache_for_report') 222 shutil.copytree('binary_cache', 'binary_cache_for_report') 223 else: 224 shutil.copy('perf.data_for_report', 'perf.data') 225 remove('binary_cache') 226 shutil.copytree('binary_cache_for_report', 'binary_cache') 227 228 @classmethod 229 def tearDownClass(cls): 230 if hasattr(cls, 'test_result') and cls.test_result and not cls.test_result.wasSuccessful(): 231 return 232 if hasattr(cls, 'package_name'): 233 cls.adb.check_run(["uninstall", cls.package_name]) 234 remove("binary_cache") 235 remove("annotated_files") 236 remove("perf.data") 237 remove("report.txt") 238 remove("pprof.profile") 239 if cls.has_perf_data_for_report: 240 cls.has_perf_data_for_report = False 241 remove('perf.data_for_report') 242 remove('binary_cache_for_report') 243 244 def run(self, result=None): 245 self.__class__.test_result = result 246 super(TestExampleBase, self).run(result) 247 248 def run_app_profiler(self, record_arg="-g --duration 10", build_binary_cache=True, 249 start_activity=True): 250 args = ['app_profiler.py', '--app', self.package_name, '-r', record_arg, '-o', 'perf.data'] 251 if not build_binary_cache: 252 args.append("-nb") 253 if self.use_compiled_java_code and not self.__class__.compiled: 254 args.append('--compile_java_code') 255 self.__class__.compiled = True 256 if start_activity: 257 args += ["-a", self.activity_name] 258 args += ["-lib", self.example_path] 259 if not self.adb_root: 260 args.append("--disable_adb_root") 261 self.run_cmd(args) 262 self.check_exist(filename="perf.data") 263 if build_binary_cache: 264 self.check_exist(dirname="binary_cache") 265 266 def check_file_under_dir(self, dirname, filename): 267 self.check_exist(dirname=dirname) 268 for _, _, files in os.walk(dirname): 269 for f in files: 270 if f == filename: 271 return 272 self.fail("Failed to call check_file_under_dir(dir=%s, file=%s)" % (dirname, filename)) 273 274 def check_annotation_summary(self, summary_file, check_entries): 275 """ check_entries is a list of (name, accumulated_period, period). 276 This function checks for each entry, if the line containing [name] 277 has at least required accumulated_period and period. 278 """ 279 self.check_exist(filename=summary_file) 280 with open(summary_file, 'r') as fh: 281 summary = fh.read() 282 fulfilled = [False for x in check_entries] 283 summary_check_re = re.compile(r'accumulated_period:\s*([\d.]+)%.*period:\s*([\d.]+)%') 284 for line in summary.split('\n'): 285 for i, (name, need_acc_period, need_period) in enumerate(check_entries): 286 if not fulfilled[i] and name in line: 287 m = summary_check_re.search(line) 288 if m: 289 acc_period = float(m.group(1)) 290 period = float(m.group(2)) 291 if acc_period >= need_acc_period and period >= need_period: 292 fulfilled[i] = True 293 self.assertEqual(len(fulfilled), sum([int(x) for x in fulfilled]), fulfilled) 294 295 def check_inferno_report_html(self, check_entries, filename="report.html"): 296 self.check_exist(filename=filename) 297 with open(filename, 'r') as fh: 298 data = fh.read() 299 fulfilled = [False for _ in check_entries] 300 for line in data.split('\n'): 301 # each entry is a (function_name, min_percentage) pair. 302 for i, entry in enumerate(check_entries): 303 if fulfilled[i] or line.find(entry[0]) == -1: 304 continue 305 m = re.search(r'(\d+\.\d+)%', line) 306 if m and float(m.group(1)) >= entry[1]: 307 fulfilled[i] = True 308 break 309 self.assertEqual(fulfilled, [True for _ in check_entries]) 310 311 def common_test_app_profiler(self): 312 self.run_cmd(["app_profiler.py", "-h"]) 313 remove("binary_cache") 314 self.run_app_profiler(build_binary_cache=False) 315 self.assertFalse(os.path.isdir("binary_cache")) 316 args = ["binary_cache_builder.py"] 317 if not self.adb_root: 318 args.append("--disable_adb_root") 319 self.run_cmd(args) 320 self.check_exist(dirname="binary_cache") 321 remove("binary_cache") 322 self.run_app_profiler(build_binary_cache=True) 323 self.run_app_profiler() 324 self.run_app_profiler(start_activity=False) 325 326 def common_test_report(self): 327 self.run_cmd(["report.py", "-h"]) 328 self.run_cmd(["report.py"]) 329 self.run_cmd(["report.py", "-i", "perf.data"]) 330 self.run_cmd(["report.py", "-g"]) 331 self.run_cmd(["report.py", "--self-kill-for-testing", "-g", "--gui"]) 332 333 def common_test_annotate(self): 334 self.run_cmd(["annotate.py", "-h"]) 335 remove("annotated_files") 336 self.run_cmd(["annotate.py", "-s", self.example_path]) 337 self.check_exist(dirname="annotated_files") 338 339 def common_test_report_sample(self, check_strings): 340 self.run_cmd(["report_sample.py", "-h"]) 341 self.run_cmd(["report_sample.py"]) 342 output = self.run_cmd(["report_sample.py", "perf.data"], return_output=True) 343 self.check_strings_in_content(output, check_strings) 344 345 def common_test_pprof_proto_generator(self, check_strings_with_lines, 346 check_strings_without_lines): 347 if not HAS_GOOGLE_PROTOBUF: 348 log_info('Skip test for pprof_proto_generator because google.protobuf is missing') 349 return 350 self.run_cmd(["pprof_proto_generator.py", "-h"]) 351 self.run_cmd(["pprof_proto_generator.py"]) 352 remove("pprof.profile") 353 self.run_cmd(["pprof_proto_generator.py", "-i", "perf.data", "-o", "pprof.profile"]) 354 self.check_exist(filename="pprof.profile") 355 self.run_cmd(["pprof_proto_generator.py", "--show"]) 356 output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"], 357 return_output=True) 358 self.check_strings_in_content(output, check_strings_with_lines + ["has_line_numbers: True"]) 359 remove("binary_cache") 360 self.run_cmd(["pprof_proto_generator.py"]) 361 output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"], 362 return_output=True) 363 self.check_strings_in_content(output, check_strings_without_lines + 364 ["has_line_numbers: False"]) 365 366 def common_test_inferno(self): 367 self.run_cmd([INFERNO_SCRIPT, "-h"]) 368 remove("perf.data") 369 append_args = [] if self.adb_root else ["--disable_adb_root"] 370 self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-t", "3"] + append_args) 371 self.check_exist(filename="perf.data") 372 self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-f", "1000", "-du", "-t", "1"] + 373 append_args) 374 self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-e", "100000 cpu-cycles", 375 "-t", "1"] + append_args) 376 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 377 378 def common_test_report_html(self): 379 self.run_cmd(['report_html.py', '-h']) 380 self.run_cmd(['report_html.py']) 381 self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata']) 382 self.run_cmd(['report_html.py', '--add_disassembly']) 383 # Test with multiple perf.data. 384 shutil.move('perf.data', 'perf2.data') 385 self.run_app_profiler(record_arg='-g -f 1000 --duration 3 -e task-clock:u') 386 self.run_cmd(['report_html.py', '-i', 'perf.data', 'perf2.data']) 387 remove('perf2.data') 388 389 390class TestExamplePureJava(TestExampleBase): 391 @classmethod 392 def setUpClass(cls): 393 cls.prepare("SimpleperfExamplePureJava", 394 "com.example.simpleperf.simpleperfexamplepurejava", 395 ".MainActivity") 396 397 def test_app_profiler(self): 398 self.common_test_app_profiler() 399 400 def test_app_profiler_profile_from_launch(self): 401 self.run_app_profiler(start_activity=True, build_binary_cache=False) 402 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 403 self.check_strings_in_file("report.txt", [ 404 "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run", 405 "__start_thread"]) 406 407 def test_app_profiler_multiprocesses(self): 408 self.adb.check_run(['shell', 'am', 'force-stop', self.package_name]) 409 self.adb.check_run(['shell', 'am', 'start', '-n', 410 self.package_name + '/.MultiProcessActivity']) 411 # Wait until both MultiProcessActivity and MultiProcessService set up. 412 time.sleep(3) 413 self.run_app_profiler(start_activity=False) 414 self.run_cmd(["report.py", "-o", "report.txt"]) 415 self.check_strings_in_file("report.txt", ["BusyService", "BusyThread"]) 416 417 def test_app_profiler_with_ctrl_c(self): 418 if is_windows(): 419 return 420 self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) 421 time.sleep(1) 422 args = [sys.executable, "app_profiler.py", "--app", self.package_name, 423 "-r", "--duration 10000", "--disable_adb_root"] 424 subproc = subprocess.Popen(args) 425 time.sleep(3) 426 427 subproc.send_signal(signal.SIGINT) 428 subproc.wait() 429 self.assertEqual(subproc.returncode, 0) 430 self.run_cmd(["report.py"]) 431 432 def test_app_profiler_stop_after_app_exit(self): 433 self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) 434 time.sleep(1) 435 subproc = subprocess.Popen([sys.executable, 'app_profiler.py', '--app', self.package_name, 436 '-r', '--duration 10000', '--disable_adb_root']) 437 time.sleep(3) 438 self.adb.check_run(['shell', 'am', 'force-stop', self.package_name]) 439 subproc.wait() 440 self.assertEqual(subproc.returncode, 0) 441 self.run_cmd(["report.py"]) 442 443 def test_app_profiler_with_ndk_path(self): 444 # Although we pass an invalid ndk path, it should be able to find tools in default ndk path. 445 self.run_cmd(['app_profiler.py', '--app', self.package_name, '-a', self.activity_name, 446 '--ndk_path', '.']) 447 448 def test_report(self): 449 self.common_test_report() 450 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 451 self.check_strings_in_file("report.txt", [ 452 "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run", 453 "__start_thread"]) 454 455 def test_profile_with_process_id(self): 456 self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) 457 time.sleep(1) 458 pid = self.adb.check_run_and_return_output([ 459 'shell', 'pidof', 'com.example.simpleperf.simpleperfexamplepurejava']).strip() 460 self.run_app_profiler(start_activity=False, record_arg='-g --duration 10 -p ' + pid) 461 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 462 self.check_strings_in_file("report.txt", [ 463 "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run", 464 "__start_thread"]) 465 466 def test_annotate(self): 467 self.common_test_annotate() 468 if not self.use_compiled_java_code: 469 # Currently annotating Java code is only supported when the Java code is compiled. 470 return 471 self.check_file_under_dir("annotated_files", "MainActivity.java") 472 summary_file = os.path.join("annotated_files", "summary") 473 self.check_annotation_summary(summary_file, [ 474 ("MainActivity.java", 80, 80), 475 ("run", 80, 0), 476 ("callFunction", 0, 0), 477 ("line 23", 80, 0)]) 478 479 def test_report_sample(self): 480 self.common_test_report_sample( 481 ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run", 482 "__start_thread"]) 483 484 def test_pprof_proto_generator(self): 485 check_strings_with_lines = [] 486 if self.use_compiled_java_code: 487 check_strings_with_lines = [ 488 "com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java", 489 "run"] 490 self.common_test_pprof_proto_generator( 491 check_strings_with_lines=check_strings_with_lines, 492 check_strings_without_lines=[ 493 "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run"]) 494 495 def test_inferno(self): 496 self.common_test_inferno() 497 self.run_app_profiler() 498 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 499 self.check_inferno_report_html( 500 [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run', 80)]) 501 self.run_cmd([INFERNO_SCRIPT, "-sc", "-o", "report2.html"]) 502 self.check_inferno_report_html( 503 [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run', 80)], 504 "report2.html") 505 remove("report2.html") 506 507 def test_inferno_in_another_dir(self): 508 test_dir = 'inferno_testdir' 509 saved_dir = os.getcwd() 510 remove(test_dir) 511 os.mkdir(test_dir) 512 os.chdir(test_dir) 513 self.run_cmd(['python', os.path.join(saved_dir, 'app_profiler.py'), 514 '--app', self.package_name, '-r', '-e task-clock:u -g --duration 3']) 515 self.check_exist(filename="perf.data") 516 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 517 os.chdir(saved_dir) 518 remove(test_dir) 519 520 def test_report_html(self): 521 self.common_test_report_html() 522 523 def test_run_simpleperf_without_usb_connection(self): 524 self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) 525 self.run_cmd(['run_simpleperf_without_usb_connection.py', 'start', '-p', 526 self.package_name, '--size_limit', '1M']) 527 self.adb.check_run(['kill-server']) 528 time.sleep(3) 529 self.run_cmd(['run_simpleperf_without_usb_connection.py', 'stop']) 530 self.check_exist(filename="perf.data") 531 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 532 533 534class TestExamplePureJavaRoot(TestExampleBase): 535 @classmethod 536 def setUpClass(cls): 537 cls.prepare("SimpleperfExamplePureJava", 538 "com.example.simpleperf.simpleperfexamplepurejava", 539 ".MainActivity", 540 adb_root=True) 541 542 def test_app_profiler(self): 543 self.common_test_app_profiler() 544 545 546class TestExamplePureJavaTraceOffCpu(TestExampleBase): 547 @classmethod 548 def setUpClass(cls): 549 cls.prepare("SimpleperfExamplePureJava", 550 "com.example.simpleperf.simpleperfexamplepurejava", 551 ".SleepActivity") 552 553 def test_smoke(self): 554 self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu") 555 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 556 self.check_strings_in_file("report.txt", [ 557 "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run", 558 "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction", 559 "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction" 560 ]) 561 remove("annotated_files") 562 self.run_cmd(["annotate.py", "-s", self.example_path]) 563 self.check_exist(dirname="annotated_files") 564 if self.use_compiled_java_code: 565 self.check_file_under_dir("annotated_files", "SleepActivity.java") 566 summary_file = os.path.join("annotated_files", "summary") 567 self.check_annotation_summary(summary_file, [ 568 ("SleepActivity.java", 80, 20), 569 ("run", 80, 0), 570 ("RunFunction", 20, 20), 571 ("SleepFunction", 20, 0), 572 ("line 24", 20, 0), 573 ("line 32", 20, 0)]) 574 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 575 self.check_inferno_report_html( 576 [('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run', 80), 577 ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction', 578 20), 579 ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction', 580 20)]) 581 582 583class TestExampleWithNative(TestExampleBase): 584 @classmethod 585 def setUpClass(cls): 586 cls.prepare("SimpleperfExampleWithNative", 587 "com.example.simpleperf.simpleperfexamplewithnative", 588 ".MainActivity") 589 590 def test_app_profiler(self): 591 self.common_test_app_profiler() 592 593 def test_app_profiler_profile_from_launch(self): 594 self.run_app_profiler(start_activity=True, build_binary_cache=False) 595 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 596 self.check_strings_in_file("report.txt", ["BusyLoopThread", "__start_thread"]) 597 598 def test_report(self): 599 self.common_test_report() 600 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 601 self.check_strings_in_file("report.txt", ["BusyLoopThread", "__start_thread"]) 602 603 def test_annotate(self): 604 self.common_test_annotate() 605 self.check_file_under_dir("annotated_files", "native-lib.cpp") 606 summary_file = os.path.join("annotated_files", "summary") 607 self.check_annotation_summary(summary_file, [ 608 ("native-lib.cpp", 20, 0), 609 ("BusyLoopThread", 20, 0), 610 ("line 46", 20, 0)]) 611 612 def test_report_sample(self): 613 self.common_test_report_sample( 614 ["BusyLoopThread", 615 "__start_thread"]) 616 617 def test_pprof_proto_generator(self): 618 check_strings_with_lines = [ 619 "native-lib.cpp", 620 "BusyLoopThread", 621 # Check if dso name in perf.data is replaced by binary path in binary_cache. 622 'filename: binary_cache/data/app/com.example.simpleperf.simpleperfexamplewithnative-'] 623 self.common_test_pprof_proto_generator( 624 check_strings_with_lines, 625 check_strings_without_lines=["BusyLoopThread"]) 626 627 def test_inferno(self): 628 self.common_test_inferno() 629 self.run_app_profiler() 630 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 631 self.check_inferno_report_html([('BusyLoopThread', 20)]) 632 633 def test_report_html(self): 634 self.common_test_report_html() 635 self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata', 636 '--add_disassembly', '--binary_filter', "libnative-lib.so"]) 637 638 639class TestExampleWithNativeRoot(TestExampleBase): 640 @classmethod 641 def setUpClass(cls): 642 cls.prepare("SimpleperfExampleWithNative", 643 "com.example.simpleperf.simpleperfexamplewithnative", 644 ".MainActivity", 645 adb_root=True) 646 647 def test_app_profiler(self): 648 self.common_test_app_profiler() 649 650 651class TestExampleWithNativeTraceOffCpu(TestExampleBase): 652 @classmethod 653 def setUpClass(cls): 654 cls.prepare("SimpleperfExampleWithNative", 655 "com.example.simpleperf.simpleperfexamplewithnative", 656 ".SleepActivity") 657 658 def test_smoke(self): 659 self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu") 660 self.run_cmd(["report.py", "-g", "--comms", "SleepThread", "-o", "report.txt"]) 661 self.check_strings_in_file("report.txt", [ 662 "SleepThread(void*)", 663 "RunFunction()", 664 "SleepFunction(unsigned long long)"]) 665 remove("annotated_files") 666 self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "SleepThread"]) 667 self.check_exist(dirname="annotated_files") 668 self.check_file_under_dir("annotated_files", "native-lib.cpp") 669 summary_file = os.path.join("annotated_files", "summary") 670 self.check_annotation_summary(summary_file, [ 671 ("native-lib.cpp", 80, 20), 672 ("SleepThread", 80, 0), 673 ("RunFunction", 20, 20), 674 ("SleepFunction", 20, 0), 675 ("line 73", 20, 0), 676 ("line 83", 20, 0)]) 677 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 678 self.check_inferno_report_html([('SleepThread', 80), 679 ('RunFunction', 20), 680 ('SleepFunction', 20)]) 681 682 683class TestExampleWithNativeJniCall(TestExampleBase): 684 @classmethod 685 def setUpClass(cls): 686 cls.prepare("SimpleperfExampleWithNative", 687 "com.example.simpleperf.simpleperfexamplewithnative", 688 ".MixActivity") 689 690 def test_smoke(self): 691 self.run_app_profiler() 692 self.run_cmd(["report.py", "-g", "--comms", "BusyThread", "-o", "report.txt"]) 693 self.check_strings_in_file("report.txt", [ 694 "com.example.simpleperf.simpleperfexamplewithnative.MixActivity$1.run", 695 "Java_com_example_simpleperf_simpleperfexamplewithnative_MixActivity_callFunction"]) 696 remove("annotated_files") 697 self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "BusyThread"]) 698 self.check_exist(dirname="annotated_files") 699 self.check_file_under_dir("annotated_files", "native-lib.cpp") 700 summary_file = os.path.join("annotated_files", "summary") 701 self.check_annotation_summary(summary_file, [("native-lib.cpp", 5, 0), ("line 40", 5, 0)]) 702 if self.use_compiled_java_code: 703 self.check_file_under_dir("annotated_files", "MixActivity.java") 704 self.check_annotation_summary(summary_file, [ 705 ("MixActivity.java", 80, 0), 706 ("run", 80, 0), 707 ("line 26", 20, 0), 708 ("native-lib.cpp", 5, 0), 709 ("line 40", 5, 0)]) 710 711 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 712 713 714class TestExampleWithNativeForceArm(TestExampleWithNative): 715 @classmethod 716 def setUpClass(cls): 717 cls.prepare("SimpleperfExampleWithNative", 718 "com.example.simpleperf.simpleperfexamplewithnative", 719 ".MainActivity", 720 abi="armeabi-v7a") 721 722 723class TestExampleWithNativeForceArmRoot(TestExampleWithNativeRoot): 724 @classmethod 725 def setUpClass(cls): 726 cls.prepare("SimpleperfExampleWithNative", 727 "com.example.simpleperf.simpleperfexamplewithnative", 728 ".MainActivity", 729 abi="armeabi-v7a", 730 adb_root=False) 731 732 733class TestExampleWithNativeTraceOffCpuForceArm(TestExampleWithNativeTraceOffCpu): 734 @classmethod 735 def setUpClass(cls): 736 cls.prepare("SimpleperfExampleWithNative", 737 "com.example.simpleperf.simpleperfexamplewithnative", 738 ".SleepActivity", 739 abi="armeabi-v7a") 740 741 742class TestExampleOfKotlin(TestExampleBase): 743 @classmethod 744 def setUpClass(cls): 745 cls.prepare("SimpleperfExampleOfKotlin", 746 "com.example.simpleperf.simpleperfexampleofkotlin", 747 ".MainActivity") 748 749 def test_app_profiler(self): 750 self.common_test_app_profiler() 751 752 def test_app_profiler_profile_from_launch(self): 753 self.run_app_profiler(start_activity=True, build_binary_cache=False) 754 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 755 self.check_strings_in_file("report.txt", [ 756 "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." + 757 "run", "__start_thread"]) 758 759 def test_report(self): 760 self.common_test_report() 761 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 762 self.check_strings_in_file("report.txt", [ 763 "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." + 764 "run", "__start_thread"]) 765 766 def test_annotate(self): 767 if not self.use_compiled_java_code: 768 return 769 self.common_test_annotate() 770 self.check_file_under_dir("annotated_files", "MainActivity.kt") 771 summary_file = os.path.join("annotated_files", "summary") 772 self.check_annotation_summary(summary_file, [ 773 ("MainActivity.kt", 80, 80), 774 ("run", 80, 0), 775 ("callFunction", 0, 0), 776 ("line 19", 80, 0), 777 ("line 25", 0, 0)]) 778 779 def test_report_sample(self): 780 self.common_test_report_sample([ 781 "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." + 782 "run", "__start_thread"]) 783 784 def test_pprof_proto_generator(self): 785 check_strings_with_lines = [] 786 if self.use_compiled_java_code: 787 check_strings_with_lines = [ 788 "com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt", 789 "run"] 790 self.common_test_pprof_proto_generator( 791 check_strings_with_lines=check_strings_with_lines, 792 check_strings_without_lines=["com.example.simpleperf.simpleperfexampleofkotlin." + 793 "MainActivity$createBusyThread$1.run"]) 794 795 def test_inferno(self): 796 self.common_test_inferno() 797 self.run_app_profiler() 798 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 799 self.check_inferno_report_html([('com.example.simpleperf.simpleperfexampleofkotlin.' + 800 'MainActivity$createBusyThread$1.run', 80)]) 801 802 def test_report_html(self): 803 self.common_test_report_html() 804 805 806class TestExampleOfKotlinRoot(TestExampleBase): 807 @classmethod 808 def setUpClass(cls): 809 cls.prepare("SimpleperfExampleOfKotlin", 810 "com.example.simpleperf.simpleperfexampleofkotlin", 811 ".MainActivity", 812 adb_root=True) 813 814 def test_app_profiler(self): 815 self.common_test_app_profiler() 816 817 818class TestExampleOfKotlinTraceOffCpu(TestExampleBase): 819 @classmethod 820 def setUpClass(cls): 821 cls.prepare("SimpleperfExampleOfKotlin", 822 "com.example.simpleperf.simpleperfexampleofkotlin", 823 ".SleepActivity") 824 825 def test_smoke(self): 826 self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu") 827 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 828 function_prefix = "com.example.simpleperf.simpleperfexampleofkotlin." + \ 829 "SleepActivity$createRunSleepThread$1." 830 self.check_strings_in_file("report.txt", [ 831 function_prefix + "run", 832 function_prefix + "RunFunction", 833 function_prefix + "SleepFunction" 834 ]) 835 if self.use_compiled_java_code: 836 remove("annotated_files") 837 self.run_cmd(["annotate.py", "-s", self.example_path]) 838 self.check_exist(dirname="annotated_files") 839 self.check_file_under_dir("annotated_files", "SleepActivity.kt") 840 summary_file = os.path.join("annotated_files", "summary") 841 self.check_annotation_summary(summary_file, [ 842 ("SleepActivity.kt", 80, 20), 843 ("run", 80, 0), 844 ("RunFunction", 20, 20), 845 ("SleepFunction", 20, 0), 846 ("line 24", 20, 0), 847 ("line 32", 20, 0)]) 848 849 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 850 self.check_inferno_report_html([ 851 (function_prefix + 'run', 80), 852 (function_prefix + 'RunFunction', 20), 853 (function_prefix + 'SleepFunction', 20)]) 854 855 856class TestNativeProfiling(TestBase): 857 def setUp(self): 858 self.adb = AdbHelper() 859 self.is_rooted_device = self.adb.switch_to_root() 860 861 def test_profile_cmd(self): 862 self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--disable_adb_root"]) 863 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 864 865 def test_profile_native_program(self): 866 if not self.is_rooted_device: 867 return 868 self.run_cmd(["app_profiler.py", "-np", "surfaceflinger"]) 869 self.run_cmd(["report.py", "-g", "-o", "report.txt"]) 870 self.run_cmd([INFERNO_SCRIPT, "-sc"]) 871 self.run_cmd([INFERNO_SCRIPT, "-np", "surfaceflinger"]) 872 873 def test_profile_pids(self): 874 if not self.is_rooted_device: 875 return 876 pid = int(self.adb.check_run_and_return_output(['shell', 'pidof', 'system_server'])) 877 self.run_cmd(['app_profiler.py', '--pid', str(pid), '-r', '--duration 1']) 878 self.run_cmd(['app_profiler.py', '--pid', str(pid), str(pid), '-r', '--duration 1']) 879 self.run_cmd(['app_profiler.py', '--tid', str(pid), '-r', '--duration 1']) 880 self.run_cmd(['app_profiler.py', '--tid', str(pid), str(pid), '-r', '--duration 1']) 881 self.run_cmd([INFERNO_SCRIPT, '--pid', str(pid), '-t', '1']) 882 883 def test_profile_system_wide(self): 884 if not self.is_rooted_device: 885 return 886 self.run_cmd(['app_profiler.py', '--system_wide', '-r', '--duration 1']) 887 888 889class TestReportLib(unittest.TestCase): 890 def setUp(self): 891 self.report_lib = ReportLib() 892 self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_symbols.data')) 893 894 def tearDown(self): 895 self.report_lib.Close() 896 897 def test_build_id(self): 898 build_id = self.report_lib.GetBuildIdForPath('/data/t2') 899 self.assertEqual(build_id, '0x70f1fe24500fc8b0d9eb477199ca1ca21acca4de') 900 901 def test_symbol(self): 902 found_func2 = False 903 while self.report_lib.GetNextSample(): 904 symbol = self.report_lib.GetSymbolOfCurrentSample() 905 if symbol.symbol_name == 'func2(int, int)': 906 found_func2 = True 907 self.assertEqual(symbol.symbol_addr, 0x4004ed) 908 self.assertEqual(symbol.symbol_len, 0x14) 909 self.assertTrue(found_func2) 910 911 def test_sample(self): 912 found_sample = False 913 while self.report_lib.GetNextSample(): 914 sample = self.report_lib.GetCurrentSample() 915 if sample.ip == 0x4004ff and sample.time == 7637889424953: 916 found_sample = True 917 self.assertEqual(sample.pid, 15926) 918 self.assertEqual(sample.tid, 15926) 919 self.assertEqual(sample.thread_comm, 't2') 920 self.assertEqual(sample.cpu, 5) 921 self.assertEqual(sample.period, 694614) 922 event = self.report_lib.GetEventOfCurrentSample() 923 self.assertEqual(event.name, 'cpu-cycles') 924 callchain = self.report_lib.GetCallChainOfCurrentSample() 925 self.assertEqual(callchain.nr, 0) 926 self.assertTrue(found_sample) 927 928 def test_meta_info(self): 929 self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) 930 meta_info = self.report_lib.MetaInfo() 931 self.assertTrue("simpleperf_version" in meta_info) 932 self.assertEqual(meta_info["system_wide_collection"], "false") 933 self.assertEqual(meta_info["trace_offcpu"], "true") 934 self.assertEqual(meta_info["event_type_info"], "cpu-cycles,0,0\nsched:sched_switch,2,47") 935 self.assertTrue("product_props" in meta_info) 936 937 def test_event_name_from_meta_info(self): 938 self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_tracepoint_event.data')) 939 event_names = set() 940 while self.report_lib.GetNextSample(): 941 event_names.add(self.report_lib.GetEventOfCurrentSample().name) 942 self.assertTrue('sched:sched_switch' in event_names) 943 self.assertTrue('cpu-cycles' in event_names) 944 945 def test_record_cmd(self): 946 self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) 947 self.assertEqual(self.report_lib.GetRecordCmd(), 948 "/data/local/tmp/simpleperf record --trace-offcpu --duration 2 -g " + 949 "./simpleperf_runtest_run_and_sleep64") 950 951 def test_offcpu(self): 952 self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) 953 total_period = 0 954 sleep_function_period = 0 955 sleep_function_name = "SleepFunction(unsigned long long)" 956 while self.report_lib.GetNextSample(): 957 sample = self.report_lib.GetCurrentSample() 958 total_period += sample.period 959 if self.report_lib.GetSymbolOfCurrentSample().symbol_name == sleep_function_name: 960 sleep_function_period += sample.period 961 continue 962 callchain = self.report_lib.GetCallChainOfCurrentSample() 963 for i in range(callchain.nr): 964 if callchain.entries[i].symbol.symbol_name == sleep_function_name: 965 sleep_function_period += sample.period 966 break 967 self.assertEqual(self.report_lib.GetEventOfCurrentSample().name, 'cpu-cycles') 968 sleep_percentage = float(sleep_function_period) / total_period 969 self.assertGreater(sleep_percentage, 0.30) 970 971 def test_show_art_frames(self): 972 def has_art_frame(report_lib): 973 report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_interpreter_frames.data')) 974 result = False 975 while report_lib.GetNextSample(): 976 callchain = report_lib.GetCallChainOfCurrentSample() 977 for i in range(callchain.nr): 978 if callchain.entries[i].symbol.symbol_name == 'artMterpAsmInstructionStart': 979 result = True 980 break 981 report_lib.Close() 982 return result 983 984 report_lib = ReportLib() 985 self.assertFalse(has_art_frame(report_lib)) 986 report_lib = ReportLib() 987 report_lib.ShowArtFrames(False) 988 self.assertFalse(has_art_frame(report_lib)) 989 report_lib = ReportLib() 990 report_lib.ShowArtFrames(True) 991 self.assertTrue(has_art_frame(report_lib)) 992 993 def test_merge_java_methods(self): 994 def parse_dso_names(report_lib): 995 dso_names = set() 996 report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_interpreter_frames.data')) 997 while report_lib.GetNextSample(): 998 dso_names.add(report_lib.GetSymbolOfCurrentSample().dso_name) 999 callchain = report_lib.GetCallChainOfCurrentSample() 1000 for i in range(callchain.nr): 1001 dso_names.add(callchain.entries[i].symbol.dso_name) 1002 report_lib.Close() 1003 has_jit_symfiles = any('TemporaryFile-' in name for name in dso_names) 1004 has_jit_cache = '[JIT cache]' in dso_names 1005 return has_jit_symfiles, has_jit_cache 1006 1007 report_lib = ReportLib() 1008 self.assertEqual(parse_dso_names(report_lib), (False, True)) 1009 1010 report_lib = ReportLib() 1011 report_lib.MergeJavaMethods(True) 1012 self.assertEqual(parse_dso_names(report_lib), (False, True)) 1013 1014 report_lib = ReportLib() 1015 report_lib.MergeJavaMethods(False) 1016 self.assertEqual(parse_dso_names(report_lib), (True, False)) 1017 1018 def test_tracing_data(self): 1019 self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_tracepoint_event.data')) 1020 has_tracing_data = False 1021 while self.report_lib.GetNextSample(): 1022 event = self.report_lib.GetEventOfCurrentSample() 1023 tracing_data = self.report_lib.GetTracingDataOfCurrentSample() 1024 if event.name == 'sched:sched_switch': 1025 self.assertIsNotNone(tracing_data) 1026 self.assertIn('prev_pid', tracing_data) 1027 self.assertIn('next_comm', tracing_data) 1028 if tracing_data['prev_pid'] == 9896 and tracing_data['next_comm'] == 'swapper/4': 1029 has_tracing_data = True 1030 else: 1031 self.assertIsNone(tracing_data) 1032 self.assertTrue(has_tracing_data) 1033 1034 1035class TestRunSimpleperfOnDevice(TestBase): 1036 def test_smoke(self): 1037 self.run_cmd(['run_simpleperf_on_device.py', 'list', '--show-features']) 1038 1039 1040class TestTools(unittest.TestCase): 1041 def test_addr2nearestline(self): 1042 self.run_addr2nearestline_test(True) 1043 self.run_addr2nearestline_test(False) 1044 1045 def run_addr2nearestline_test(self, with_function_name): 1046 binary_cache_path = 'testdata' 1047 test_map = { 1048 '/simpleperf_runtest_two_functions_arm64': [ 1049 { 1050 'func_addr': 0x668, 1051 'addr': 0x668, 1052 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:20', 1053 'function': 'main', 1054 }, 1055 { 1056 'func_addr': 0x668, 1057 'addr': 0x6a4, 1058 'source': """system/extras/simpleperf/runtest/two_functions.cpp:7 1059 system/extras/simpleperf/runtest/two_functions.cpp:22""", 1060 'function': """Function1() 1061 main""", 1062 }, 1063 ], 1064 '/simpleperf_runtest_two_functions_arm': [ 1065 { 1066 'func_addr': 0x784, 1067 'addr': 0x7b0, 1068 'source': """system/extras/simpleperf/runtest/two_functions.cpp:14 1069 system/extras/simpleperf/runtest/two_functions.cpp:23""", 1070 'function': """Function2() 1071 main""", 1072 }, 1073 { 1074 'func_addr': 0x784, 1075 'addr': 0x7d0, 1076 'source': """system/extras/simpleperf/runtest/two_functions.cpp:15 1077 system/extras/simpleperf/runtest/two_functions.cpp:23""", 1078 'function': """Function2() 1079 main""", 1080 } 1081 ], 1082 '/simpleperf_runtest_two_functions_x86_64': [ 1083 { 1084 'func_addr': 0x840, 1085 'addr': 0x840, 1086 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:6', 1087 'function': 'Function1()', 1088 }, 1089 { 1090 'func_addr': 0x920, 1091 'addr': 0x94a, 1092 'source': """system/extras/simpleperf/runtest/two_functions.cpp:7 1093 system/extras/simpleperf/runtest/two_functions.cpp:22""", 1094 'function': """Function1() 1095 main""", 1096 } 1097 ], 1098 '/simpleperf_runtest_two_functions_x86': [ 1099 { 1100 'func_addr': 0x6d0, 1101 'addr': 0x6da, 1102 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:14', 1103 'function': 'Function2()', 1104 }, 1105 { 1106 'func_addr': 0x710, 1107 'addr': 0x749, 1108 'source': """system/extras/simpleperf/runtest/two_functions.cpp:8 1109 system/extras/simpleperf/runtest/two_functions.cpp:22""", 1110 'function': """Function1() 1111 main""", 1112 } 1113 ], 1114 } 1115 addr2line = Addr2Nearestline(None, binary_cache_path, with_function_name) 1116 for dso_path in test_map: 1117 test_addrs = test_map[dso_path] 1118 for test_addr in test_addrs: 1119 addr2line.add_addr(dso_path, test_addr['func_addr'], test_addr['addr']) 1120 addr2line.convert_addrs_to_lines() 1121 for dso_path in test_map: 1122 dso = addr2line.get_dso(dso_path) 1123 self.assertTrue(dso is not None) 1124 test_addrs = test_map[dso_path] 1125 for test_addr in test_addrs: 1126 expected_files = [] 1127 expected_lines = [] 1128 expected_functions = [] 1129 for line in test_addr['source'].split('\n'): 1130 items = line.split(':') 1131 expected_files.append(items[0].strip()) 1132 expected_lines.append(int(items[1])) 1133 for line in test_addr['function'].split('\n'): 1134 expected_functions.append(line.strip()) 1135 self.assertEqual(len(expected_files), len(expected_functions)) 1136 1137 actual_source = addr2line.get_addr_source(dso, test_addr['addr']) 1138 self.assertTrue(actual_source is not None) 1139 self.assertEqual(len(actual_source), len(expected_files)) 1140 for i, source in enumerate(actual_source): 1141 self.assertEqual(len(source), 3 if with_function_name else 2) 1142 self.assertEqual(source[0], expected_files[i]) 1143 self.assertEqual(source[1], expected_lines[i]) 1144 if with_function_name: 1145 self.assertEqual(source[2], expected_functions[i]) 1146 1147 def test_objdump(self): 1148 binary_cache_path = 'testdata' 1149 test_map = { 1150 '/simpleperf_runtest_two_functions_arm64': { 1151 'start_addr': 0x668, 1152 'len': 116, 1153 'expected_items': [ 1154 ('main():', 0), 1155 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 1156 (' 694: add x20, x20, #0x6de', 0x694), 1157 ], 1158 }, 1159 '/simpleperf_runtest_two_functions_arm': { 1160 'start_addr': 0x784, 1161 'len': 80, 1162 'expected_items': [ 1163 ('main():', 0), 1164 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 1165 (' 7ae: bne.n 7a6 <main+0x22>', 0x7ae), 1166 ], 1167 }, 1168 '/simpleperf_runtest_two_functions_x86_64': { 1169 'start_addr': 0x920, 1170 'len': 201, 1171 'expected_items': [ 1172 ('main():', 0), 1173 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 1174 (' 96e: mov %edx,(%rbx,%rax,4)', 0x96e), 1175 ], 1176 }, 1177 '/simpleperf_runtest_two_functions_x86': { 1178 'start_addr': 0x710, 1179 'len': 98, 1180 'expected_items': [ 1181 ('main():', 0), 1182 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 1183 (' 748: cmp $0x5f5e100,%ebp', 0x748), 1184 ], 1185 }, 1186 } 1187 objdump = Objdump(None, binary_cache_path) 1188 for dso_path in test_map: 1189 dso = test_map[dso_path] 1190 dso_info = objdump.get_dso_info(dso_path) 1191 self.assertIsNotNone(dso_info) 1192 disassemble_code = objdump.disassemble_code(dso_info, dso['start_addr'], dso['len']) 1193 self.assertTrue(disassemble_code) 1194 for item in dso['expected_items']: 1195 self.assertTrue(item in disassemble_code) 1196 1197 def test_readelf(self): 1198 test_map = { 1199 '/simpleperf_runtest_two_functions_arm64': { 1200 'arch': 'arm64', 1201 'build_id': '0xe8ecb3916d989dbdc068345c30f0c24300000000', 1202 'sections': ['.interp', '.note.android.ident', '.note.gnu.build-id', '.dynsym', 1203 '.dynstr', '.gnu.hash', '.gnu.version', '.gnu.version_r', '.rela.dyn', 1204 '.rela.plt', '.plt', '.text', '.rodata', '.eh_frame', '.eh_frame_hdr', 1205 '.preinit_array', '.init_array', '.fini_array', '.dynamic', '.got', 1206 '.got.plt', '.data', '.bss', '.comment', '.debug_str', '.debug_loc', 1207 '.debug_abbrev', '.debug_info', '.debug_ranges', '.debug_macinfo', 1208 '.debug_pubnames', '.debug_pubtypes', '.debug_line', 1209 '.note.gnu.gold-version', '.symtab', '.strtab', '.shstrtab'], 1210 }, 1211 '/simpleperf_runtest_two_functions_arm': { 1212 'arch': 'arm', 1213 'build_id': '0x718f5b36c4148ee1bd3f51af89ed2be600000000', 1214 }, 1215 '/simpleperf_runtest_two_functions_x86_64': { 1216 'arch': 'x86_64', 1217 }, 1218 '/simpleperf_runtest_two_functions_x86': { 1219 'arch': 'x86', 1220 } 1221 } 1222 readelf = ReadElf(None) 1223 for dso_path in test_map: 1224 dso_info = test_map[dso_path] 1225 path = 'testdata' + dso_path 1226 self.assertEqual(dso_info['arch'], readelf.get_arch(path)) 1227 if 'build_id' in dso_info: 1228 self.assertEqual(dso_info['build_id'], readelf.get_build_id(path)) 1229 if 'sections' in dso_info: 1230 self.assertEqual(dso_info['sections'], readelf.get_sections(path)) 1231 self.assertEqual(readelf.get_arch('not_exist_file'), 'unknown') 1232 self.assertEqual(readelf.get_build_id('not_exist_file'), '') 1233 self.assertEqual(readelf.get_sections('not_exist_file'), []) 1234 1235 def test_source_file_searcher(self): 1236 searcher = SourceFileSearcher(['testdata']) 1237 def format_path(path): 1238 return path.replace('/', os.sep) 1239 # Find a C++ file with pure file name. 1240 self.assertEqual( 1241 format_path('testdata/SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'), 1242 searcher.get_real_path('native-lib.cpp')) 1243 # Find a C++ file with an absolute file path. 1244 self.assertEqual( 1245 format_path('testdata/SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'), 1246 searcher.get_real_path('/data/native-lib.cpp')) 1247 # Find a Java file. 1248 self.assertEqual( 1249 format_path('testdata/SimpleperfExampleWithNative/app/src/main/java/com/example/' + 1250 'simpleperf/simpleperfexamplewithnative/MainActivity.java'), 1251 searcher.get_real_path('simpleperfexamplewithnative/MainActivity.java')) 1252 # Find a Kotlin file. 1253 self.assertEqual( 1254 format_path('testdata/SimpleperfExampleOfKotlin/app/src/main/java/com/example/' + 1255 'simpleperf/simpleperfexampleofkotlin/MainActivity.kt'), 1256 searcher.get_real_path('MainActivity.kt')) 1257 1258 def test_is_elf_file(self): 1259 self.assertTrue(is_elf_file(os.path.join( 1260 'testdata', 'simpleperf_runtest_two_functions_arm'))) 1261 with open('not_elf', 'wb') as fh: 1262 fh.write(b'\x90123') 1263 try: 1264 self.assertFalse(is_elf_file('not_elf')) 1265 finally: 1266 remove('not_elf') 1267 1268 1269class TestNativeLibDownloader(unittest.TestCase): 1270 def setUp(self): 1271 self.adb = AdbHelper() 1272 self.adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs']) 1273 1274 def tearDown(self): 1275 self.adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs']) 1276 1277 def test_smoke(self): 1278 def is_lib_on_device(path): 1279 return self.adb.run(['shell', 'ls', path]) 1280 1281 # Sync all native libs on device. 1282 downloader = NativeLibDownloader(None, 'arm64', self.adb) 1283 downloader.collect_native_libs_on_host(os.path.join( 1284 'testdata', 'SimpleperfExampleWithNative', 'app', 'build', 'intermediates', 'cmake', 1285 'profiling')) 1286 self.assertEqual(len(downloader.host_build_id_map), 2) 1287 for entry in downloader.host_build_id_map.values(): 1288 self.assertEqual(entry.score, 3) 1289 downloader.collect_native_libs_on_device() 1290 self.assertEqual(len(downloader.device_build_id_map), 0) 1291 1292 lib_list = list(downloader.host_build_id_map.items()) 1293 for sync_count in [0, 1, 2]: 1294 build_id_map = {} 1295 for i in range(sync_count): 1296 build_id_map[lib_list[i][0]] = lib_list[i][1] 1297 downloader.host_build_id_map = build_id_map 1298 downloader.sync_natives_libs_on_device() 1299 downloader.collect_native_libs_on_device() 1300 self.assertEqual(len(downloader.device_build_id_map), sync_count) 1301 for i, item in enumerate(lib_list): 1302 build_id = item[0] 1303 name = item[1].name 1304 if i < sync_count: 1305 self.assertTrue(build_id in downloader.device_build_id_map) 1306 self.assertEqual(name, downloader.device_build_id_map[build_id]) 1307 self.assertTrue(is_lib_on_device(downloader.dir_on_device + name)) 1308 else: 1309 self.assertTrue(build_id not in downloader.device_build_id_map) 1310 self.assertFalse(is_lib_on_device(downloader.dir_on_device + name)) 1311 if sync_count == 1: 1312 self.adb.run(['pull', '/data/local/tmp/native_libs/build_id_list', 1313 'build_id_list']) 1314 with open('build_id_list', 'rb') as fh: 1315 self.assertEqual(bytes_to_str(fh.read()), 1316 '{}={}\n'.format(lib_list[0][0], lib_list[0][1].name)) 1317 remove('build_id_list') 1318 1319 def test_handle_wrong_build_id_list(self): 1320 with open('build_id_list', 'wb') as fh: 1321 fh.write(str_to_bytes('fake_build_id=binary_not_exist\n')) 1322 self.adb.check_run(['shell', 'mkdir', '-p', '/data/local/tmp/native_libs']) 1323 self.adb.check_run(['push', 'build_id_list', '/data/local/tmp/native_libs']) 1324 remove('build_id_list') 1325 downloader = NativeLibDownloader(None, 'arm64', self.adb) 1326 downloader.collect_native_libs_on_device() 1327 self.assertEqual(len(downloader.device_build_id_map), 0) 1328 1329 1330class TestReportHtml(TestBase): 1331 def test_long_callchain(self): 1332 self.run_cmd(['report_html.py', '-i', 1333 os.path.join('testdata', 'perf_with_long_callchain.data')]) 1334 1335 def test_aggregated_by_thread_name(self): 1336 # Calculate event_count for each thread name before aggregation. 1337 event_count_for_thread_name = collections.defaultdict(lambda: 0) 1338 # use "--min_func_percent 0" to avoid cutting any thread. 1339 self.run_cmd(['report_html.py', '--min_func_percent', '0', '-i', 1340 os.path.join('testdata', 'aggregatable_perf1.data'), 1341 os.path.join('testdata', 'aggregatable_perf2.data')]) 1342 record_data = self._load_record_data_in_html('report.html') 1343 event = record_data['sampleInfo'][0] 1344 for process in event['processes']: 1345 for thread in process['threads']: 1346 thread_name = record_data['threadNames'][str(thread['tid'])] 1347 event_count_for_thread_name[thread_name] += thread['eventCount'] 1348 1349 # Check event count for each thread after aggregation. 1350 self.run_cmd(['report_html.py', '--aggregate-by-thread-name', 1351 '--min_func_percent', '0', '-i', 1352 os.path.join('testdata', 'aggregatable_perf1.data'), 1353 os.path.join('testdata', 'aggregatable_perf2.data')]) 1354 record_data = self._load_record_data_in_html('report.html') 1355 event = record_data['sampleInfo'][0] 1356 hit_count = 0 1357 for process in event['processes']: 1358 for thread in process['threads']: 1359 thread_name = record_data['threadNames'][str(thread['tid'])] 1360 self.assertEqual(thread['eventCount'], 1361 event_count_for_thread_name[thread_name]) 1362 hit_count += 1 1363 self.assertEqual(hit_count, len(event_count_for_thread_name)) 1364 1365 def test_no_empty_process(self): 1366 """ Test not showing a process having no threads. """ 1367 perf_data = os.path.join('testdata', 'two_process_perf.data') 1368 self.run_cmd(['report_html.py', '-i', perf_data]) 1369 record_data = self._load_record_data_in_html('report.html') 1370 processes = record_data['sampleInfo'][0]['processes'] 1371 self.assertEqual(len(processes), 2) 1372 1373 # One process is removed because all its threads are removed for not 1374 # reaching the min_func_percent limit. 1375 self.run_cmd(['report_html.py', '-i', perf_data, '--min_func_percent', '20']) 1376 record_data = self._load_record_data_in_html('report.html') 1377 processes = record_data['sampleInfo'][0]['processes'] 1378 self.assertEqual(len(processes), 1) 1379 1380 def _load_record_data_in_html(self, html_file): 1381 with open(html_file, 'r') as fh: 1382 data = fh.read() 1383 start_str = 'type="application/json"' 1384 end_str = '</script>' 1385 start_pos = data.find(start_str) 1386 self.assertNotEqual(start_pos, -1) 1387 start_pos = data.find('>', start_pos) 1388 self.assertNotEqual(start_pos, -1) 1389 start_pos += 1 1390 end_pos = data.find(end_str, start_pos) 1391 self.assertNotEqual(end_pos, -1) 1392 json_data = data[start_pos:end_pos] 1393 return json.loads(json_data) 1394 1395 1396class TestBinaryCacheBuilder(TestBase): 1397 def test_copy_binaries_from_symfs_dirs(self): 1398 readelf = ReadElf(None) 1399 strip = find_tool_path('strip', arch='arm') 1400 self.assertIsNotNone(strip) 1401 symfs_dir = os.path.join('testdata', 'symfs_dir') 1402 remove(symfs_dir) 1403 os.mkdir(symfs_dir) 1404 filename = 'simpleperf_runtest_two_functions_arm' 1405 origin_file = os.path.join('testdata', filename) 1406 source_file = os.path.join(symfs_dir, filename) 1407 target_file = os.path.join('binary_cache', filename) 1408 expected_build_id = readelf.get_build_id(origin_file) 1409 binary_cache_builder = BinaryCacheBuilder(None, False) 1410 binary_cache_builder.binaries['simpleperf_runtest_two_functions_arm'] = expected_build_id 1411 1412 # Copy binary if target file doesn't exist. 1413 remove(target_file) 1414 self.run_cmd([strip, '--strip-all', '-o', source_file, origin_file]) 1415 binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir]) 1416 self.assertTrue(filecmp.cmp(target_file, source_file)) 1417 1418 # Copy binary if target file doesn't have .symtab and source file has .symtab. 1419 self.run_cmd([strip, '--strip-debug', '-o', source_file, origin_file]) 1420 binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir]) 1421 self.assertTrue(filecmp.cmp(target_file, source_file)) 1422 1423 # Copy binary if target file doesn't have .debug_line and source_files has .debug_line. 1424 shutil.copy(origin_file, source_file) 1425 binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir]) 1426 self.assertTrue(filecmp.cmp(target_file, source_file)) 1427 1428 1429class TestApiProfiler(TestBase): 1430 def run_api_test(self, package_name, apk_name, expected_reports, min_android_version): 1431 adb = AdbHelper() 1432 if android_version() < ord(min_android_version) - ord('L') + 5: 1433 log_info('skip this test on Android < %s.' % min_android_version) 1434 return 1435 # step 1: Prepare profiling. 1436 self.run_cmd(['api_profiler.py', 'prepare']) 1437 # step 2: Install and run the app. 1438 apk_path = os.path.join('testdata', apk_name) 1439 adb.run(['uninstall', package_name]) 1440 adb.check_run(['install', '-t', apk_path]) 1441 adb.check_run(['shell', 'am', 'start', '-n', package_name + '/.MainActivity']) 1442 # step 3: Wait until the app exits. 1443 time.sleep(4) 1444 while True: 1445 result = adb.run(['shell', 'pidof', package_name]) 1446 if not result: 1447 break 1448 time.sleep(1) 1449 # step 4: Collect recording data. 1450 remove('simpleperf_data') 1451 self.run_cmd(['api_profiler.py', 'collect', '-p', package_name, '-o', 'simpleperf_data']) 1452 # step 5: Check recording data. 1453 names = os.listdir('simpleperf_data') 1454 self.assertGreater(len(names), 0) 1455 for name in names: 1456 path = os.path.join('simpleperf_data', name) 1457 remove('report.txt') 1458 self.run_cmd(['report.py', '-g', '-o', 'report.txt', '-i', path]) 1459 self.check_strings_in_file('report.txt', expected_reports) 1460 # step 6: Clean up. 1461 remove('report.txt') 1462 remove('simpleperf_data') 1463 adb.check_run(['uninstall', package_name]) 1464 1465 def run_cpp_api_test(self, apk_name, min_android_version): 1466 self.run_api_test('simpleperf.demo.cpp_api', apk_name, ['BusyThreadFunc'], 1467 min_android_version) 1468 1469 def test_cpp_api_on_a_debuggable_app_targeting_prev_q(self): 1470 # The source code of the apk is in simpleperf/demo/CppApi (with a small change to exit 1471 # after recording). 1472 self.run_cpp_api_test('cpp_api-debug_prev_Q.apk', 'N') 1473 1474 def test_cpp_api_on_a_debuggable_app_targeting_q(self): 1475 self.run_cpp_api_test('cpp_api-debug_Q.apk', 'N') 1476 1477 def test_cpp_api_on_a_profileable_app_targeting_prev_q(self): 1478 # a release apk with <profileable android:shell="true" /> 1479 self.run_cpp_api_test('cpp_api-profile_prev_Q.apk', 'Q') 1480 1481 def test_cpp_api_on_a_profileable_app_targeting_q(self): 1482 self.run_cpp_api_test('cpp_api-profile_Q.apk', 'Q') 1483 1484 def run_java_api_test(self, apk_name, min_android_version): 1485 self.run_api_test('simpleperf.demo.java_api', apk_name, 1486 ['simpleperf.demo.java_api.MainActivity', 'java.lang.Thread.run'], 1487 min_android_version) 1488 1489 def test_java_api_on_a_debuggable_app_targeting_prev_q(self): 1490 # The source code of the apk is in simpleperf/demo/JavaApi (with a small change to exit 1491 # after recording). 1492 self.run_java_api_test('java_api-debug_prev_Q.apk', 'P') 1493 1494 def test_java_api_on_a_debuggable_app_targeting_q(self): 1495 self.run_java_api_test('java_api-debug_Q.apk', 'P') 1496 1497 def test_java_api_on_a_profileable_app_targeting_prev_q(self): 1498 # a release apk with <profileable android:shell="true" /> 1499 self.run_java_api_test('java_api-profile_prev_Q.apk', 'Q') 1500 1501 def test_java_api_on_a_profileable_app_targeting_q(self): 1502 self.run_java_api_test('java_api-profile_Q.apk', 'Q') 1503 1504 1505class TestPprofProtoGenerator(TestBase): 1506 def setUp(self): 1507 if not HAS_GOOGLE_PROTOBUF: 1508 raise unittest.SkipTest( 1509 'Skip test for pprof_proto_generator because google.protobuf is missing') 1510 1511 def run_generator(self, options=None, testdata_file='perf_with_interpreter_frames.data'): 1512 testdata_path = os.path.join('testdata', testdata_file) 1513 options = options or [] 1514 self.run_cmd(['pprof_proto_generator.py', '-i', testdata_path] + options) 1515 return self.run_cmd(['pprof_proto_generator.py', '--show'], return_output=True) 1516 1517 def test_show_art_frames(self): 1518 art_frame_str = 'art::interpreter::DoCall' 1519 # By default, don't show art frames. 1520 self.assertNotIn(art_frame_str, self.run_generator()) 1521 # Use --show_art_frames to show art frames. 1522 self.assertIn(art_frame_str, self.run_generator(['--show_art_frames'])) 1523 1524 def test_pid_filter(self): 1525 key = 'PlayScene::DoFrame()' # function in process 10419 1526 self.assertIn(key, self.run_generator()) 1527 self.assertIn(key, self.run_generator(['--pid', '10419'])) 1528 self.assertIn(key, self.run_generator(['--pid', '10419', '10416'])) 1529 self.assertNotIn(key, self.run_generator(['--pid', '10416'])) 1530 1531 def test_tid_filter(self): 1532 key1 = 'art::ProfileSaver::Run()' # function in thread 10459 1533 key2 = 'PlayScene::DoFrame()' # function in thread 10463 1534 for options in ([], ['--tid', '10459', '10463']): 1535 output = self.run_generator(options) 1536 self.assertIn(key1, output) 1537 self.assertIn(key2, output) 1538 output = self.run_generator(['--tid', '10459']) 1539 self.assertIn(key1, output) 1540 self.assertNotIn(key2, output) 1541 output = self.run_generator(['--tid', '10463']) 1542 self.assertNotIn(key1, output) 1543 self.assertIn(key2, output) 1544 1545 def test_comm_filter(self): 1546 key1 = 'art::ProfileSaver::Run()' # function in thread 'Profile Saver' 1547 key2 = 'PlayScene::DoFrame()' # function in thread 'e.sample.tunnel' 1548 for options in ([], ['--comm', 'Profile Saver', 'e.sample.tunnel']): 1549 output = self.run_generator(options) 1550 self.assertIn(key1, output) 1551 self.assertIn(key2, output) 1552 output = self.run_generator(['--comm', 'Profile Saver']) 1553 self.assertIn(key1, output) 1554 self.assertNotIn(key2, output) 1555 output = self.run_generator(['--comm', 'e.sample.tunnel']) 1556 self.assertNotIn(key1, output) 1557 self.assertIn(key2, output) 1558 1559 def test_build_id(self): 1560 """ Test the build ids generated are not padded with zeros. """ 1561 self.assertIn('build_id: e3e938cc9e40de2cfe1a5ac7595897de(', self.run_generator()) 1562 1563 def test_location_address(self): 1564 """ Test if the address of a location is within the memory range of the corresponding 1565 mapping. 1566 """ 1567 self.run_cmd(['pprof_proto_generator.py', '-i', 1568 os.path.join('testdata', 'perf_with_interpreter_frames.data')]) 1569 1570 profile = load_pprof_profile('pprof.profile') 1571 # pylint: disable=no-member 1572 for location in profile.location: 1573 mapping = profile.mapping[location.mapping_id - 1] 1574 self.assertLessEqual(mapping.memory_start, location.address) 1575 self.assertGreaterEqual(mapping.memory_limit, location.address) 1576 1577 1578class TestRecordingRealApps(TestBase): 1579 def setUp(self): 1580 self.adb = AdbHelper(False) 1581 self.installed_packages = [] 1582 1583 def tearDown(self): 1584 for package in self.installed_packages: 1585 self.adb.run(['shell', 'pm', 'uninstall', package]) 1586 1587 def install_apk(self, apk_path, package_name): 1588 self.adb.run(['install', '-t', apk_path]) 1589 self.installed_packages.append(package_name) 1590 1591 def start_app(self, start_cmd): 1592 subprocess.Popen(self.adb.adb_path + ' ' + start_cmd, shell=True, 1593 stdout=TEST_LOGGER.log_fh, stderr=TEST_LOGGER.log_fh) 1594 1595 def record_data(self, package_name, record_arg): 1596 self.run_cmd(['app_profiler.py', '--app', package_name, '-r', record_arg]) 1597 1598 def check_symbol_in_record_file(self, symbol_name): 1599 self.run_cmd(['report.py', '--children', '-o', 'report.txt']) 1600 self.check_strings_in_file('report.txt', [symbol_name]) 1601 1602 def test_recording_displaybitmaps(self): 1603 self.install_apk(os.path.join('testdata', 'DisplayBitmaps.apk'), 1604 'com.example.android.displayingbitmaps') 1605 self.install_apk(os.path.join('testdata', 'DisplayBitmapsTest.apk'), 1606 'com.example.android.displayingbitmaps.test') 1607 self.start_app('shell am instrument -w -r -e debug false -e class ' + 1608 'com.example.android.displayingbitmaps.tests.GridViewTest ' + 1609 'com.example.android.displayingbitmaps.test/' + 1610 'androidx.test.runner.AndroidJUnitRunner') 1611 self.record_data('com.example.android.displayingbitmaps', '-e cpu-clock -g --duration 10') 1612 if android_version() >= 9: 1613 self.check_symbol_in_record_file('androidx.test.espresso') 1614 1615 def test_recording_endless_tunnel(self): 1616 self.install_apk(os.path.join('testdata', 'EndlessTunnel.apk'), 'com.google.sample.tunnel') 1617 self.start_app('shell am start -n com.google.sample.tunnel/android.app.NativeActivity -a ' + 1618 'android.intent.action.MAIN -c android.intent.category.LAUNCHER') 1619 self.record_data('com.google.sample.tunnel', '-e cpu-clock -g --duration 10') 1620 self.check_symbol_in_record_file('PlayScene::DoFrame') 1621 1622 1623def get_all_tests(): 1624 tests = [] 1625 for name, value in globals().items(): 1626 if isinstance(value, type) and issubclass(value, unittest.TestCase): 1627 for member_name, member in inspect.getmembers(value): 1628 if isinstance(member, (types.MethodType, types.FunctionType)): 1629 if member_name.startswith('test'): 1630 tests.append(name + '.' + member_name) 1631 return sorted(tests) 1632 1633 1634def run_tests(tests, repeats, python_version): 1635 os.chdir(get_script_dir()) 1636 build_testdata() 1637 argv = [sys.argv[0]] + tests 1638 test_runner = unittest.TextTestRunner(stream=TEST_LOGGER, verbosity=2) 1639 for repeat in range(repeats): 1640 print('Run tests with python %d for %dth time\n%s' % ( 1641 python_version, repeat + 1, '\n'.join(tests)), file=TEST_LOGGER) 1642 test_program = unittest.main(argv=argv, testRunner=test_runner, exit=False) 1643 if not test_program.result.wasSuccessful(): 1644 return False 1645 return True 1646 1647 1648def main(): 1649 parser = argparse.ArgumentParser(description='Test simpleperf scripts') 1650 parser.add_argument('--list-tests', action='store_true', help='List all tests.') 1651 parser.add_argument('--test-from', nargs=1, help='Run left tests from the selected test.') 1652 parser.add_argument('--python-version', choices=['2', '3', 'both'], default='both', help=""" 1653 Run tests on which python versions.""") 1654 parser.add_argument('--repeat', type=int, nargs=1, default=[1], help='run test multiple times') 1655 parser.add_argument('--no-test-result', dest='report_test_result', 1656 action='store_false', help="Don't report test result.") 1657 parser.add_argument('pattern', nargs='*', help='Run tests matching the selected pattern.') 1658 args = parser.parse_args() 1659 tests = get_all_tests() 1660 if args.list_tests: 1661 print('\n'.join(tests)) 1662 return True 1663 if args.test_from: 1664 start_pos = 0 1665 while start_pos < len(tests) and tests[start_pos] != args.test_from[0]: 1666 start_pos += 1 1667 if start_pos == len(tests): 1668 log_exit("Can't find test %s" % args.test_from[0]) 1669 tests = tests[start_pos:] 1670 if args.pattern: 1671 pattern = re.compile(fnmatch.translate(args.pattern[0])) 1672 new_tests = [] 1673 for test in tests: 1674 if pattern.match(test): 1675 new_tests.append(test) 1676 tests = new_tests 1677 if not tests: 1678 log_exit('No tests are matched.') 1679 1680 if android_version() < 7: 1681 print("Skip tests on Android version < N.", file=TEST_LOGGER) 1682 return False 1683 1684 if args.python_version == 'both': 1685 python_versions = [2, 3] 1686 else: 1687 python_versions = [int(args.python_version)] 1688 test_results = [] 1689 current_version = 3 if is_python3() else 2 1690 for version in python_versions: 1691 if version == current_version: 1692 test_result = run_tests(tests, args.repeat[0], version) 1693 else: 1694 argv = ['python3' if version == 3 else 'python'] 1695 argv.append(os.path.join(get_script_dir(), 'test.py')) 1696 argv += sys.argv[1:] 1697 argv += ['--python-version', str(version), '--no-test-result'] 1698 test_result = subprocess.call(argv) == 0 1699 test_results.append(test_result) 1700 1701 if args.report_test_result: 1702 for version, test_result in zip(python_versions, test_results): 1703 if not test_result: 1704 print('Tests with python %d failed, see %s for details.' % 1705 (version, TEST_LOGGER.get_log_file(version)), file=TEST_LOGGER) 1706 1707 return test_results.count(False) == 0 1708 1709 1710if __name__ == '__main__': 1711 sys.exit(0 if main() else 1) 1712