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