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