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