• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 filecmp
40import fnmatch
41import inspect
42import os
43import re
44import shutil
45import signal
46import subprocess
47import sys
48import time
49import types
50import unittest
51
52from app_profiler import NativeLibDownloader
53from binary_cache_builder import BinaryCacheBuilder
54from simpleperf_report_lib import ReportLib
55from utils import log_exit, log_info, log_fatal
56from utils import AdbHelper, Addr2Nearestline, bytes_to_str, find_tool_path, get_script_dir
57from utils import is_python3, is_windows, Objdump, ReadElf, remove, SourceFileSearcher
58
59try:
60    # pylint: disable=unused-import
61    import google.protobuf
62    HAS_GOOGLE_PROTOBUF = True
63except ImportError:
64    HAS_GOOGLE_PROTOBUF = False
65
66INFERNO_SCRIPT = os.path.join(get_script_dir(), "inferno.bat" if is_windows() else "./inferno.sh")
67
68def get_device_features():
69    adb = AdbHelper()
70    adb.check_run_and_return_output(['push',
71                                     'bin/android/%s/simpleperf' % adb.get_device_arch(),
72                                     '/data/local/tmp'])
73    adb.check_run_and_return_output(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf'])
74    return adb.check_run_and_return_output(['shell', '/data/local/tmp/simpleperf', 'list',
75                                            '--show-features'])
76
77def is_trace_offcpu_supported():
78    if not hasattr(is_trace_offcpu_supported, 'value'):
79        is_trace_offcpu_supported.value = 'trace-offcpu' in get_device_features()
80    return is_trace_offcpu_supported.value
81
82
83def build_testdata():
84    """ Collect testdata from ../testdata and ../demo. """
85    from_testdata_path = os.path.join('..', 'testdata')
86    from_demo_path = os.path.join('..', 'demo')
87    from_script_testdata_path = 'script_testdata'
88    if (not os.path.isdir(from_testdata_path) or not os.path.isdir(from_demo_path) or
89            not from_script_testdata_path):
90        return
91    copy_testdata_list = ['perf_with_symbols.data', 'perf_with_trace_offcpu.data',
92                          'perf_with_tracepoint_event.data', 'perf_with_interpreter_frames.data']
93    copy_demo_list = ['SimpleperfExamplePureJava', 'SimpleperfExampleWithNative',
94                      'SimpleperfExampleOfKotlin']
95
96    testdata_path = "testdata"
97    remove(testdata_path)
98    os.mkdir(testdata_path)
99    for testdata in copy_testdata_list:
100        shutil.copy(os.path.join(from_testdata_path, testdata), testdata_path)
101    for demo in copy_demo_list:
102        shutil.copytree(os.path.join(from_demo_path, demo), os.path.join(testdata_path, demo))
103    for f in os.listdir(from_script_testdata_path):
104        shutil.copy(os.path.join(from_script_testdata_path, f), testdata_path)
105
106class TestBase(unittest.TestCase):
107    def run_cmd(self, args, return_output=False):
108        if args[0].endswith('.py'):
109            args = [sys.executable] + args
110        use_shell = args[0].endswith('.bat')
111        try:
112            if not return_output:
113                returncode = subprocess.call(args, shell=use_shell)
114            else:
115                subproc = subprocess.Popen(args, stdout=subprocess.PIPE, shell=use_shell)
116                (output_data, _) = subproc.communicate()
117                output_data = bytes_to_str(output_data)
118                returncode = subproc.returncode
119        except OSError:
120            returncode = None
121        self.assertEqual(returncode, 0, msg="failed to run cmd: %s" % args)
122        if return_output:
123            return output_data
124        return ''
125
126
127class TestExampleBase(TestBase):
128    @classmethod
129    def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False):
130        cls.adb = AdbHelper(enable_switch_to_root=adb_root)
131        cls.example_path = os.path.join("testdata", example_name)
132        if not os.path.isdir(cls.example_path):
133            log_fatal("can't find " + cls.example_path)
134        for root, _, files in os.walk(cls.example_path):
135            if 'app-profiling.apk' in files:
136                cls.apk_path = os.path.join(root, 'app-profiling.apk')
137                break
138        if not hasattr(cls, 'apk_path'):
139            log_fatal("can't find app-profiling.apk under " + cls.example_path)
140        cls.package_name = package_name
141        cls.activity_name = activity_name
142        cls.abi = "arm64"
143        if abi and abi != "arm64" and abi.find("arm") != -1:
144            cls.abi = "arm"
145        args = ["install", "-r"]
146        if abi:
147            args += ["--abi", abi]
148        args.append(cls.apk_path)
149        cls.adb.check_run(args)
150        cls.adb_root = adb_root
151        cls.compiled = False
152        cls.has_perf_data_for_report = False
153        android_version = cls.adb.get_android_version()
154        # On Android >= P (version 9), we can profile JITed and interpreted Java code.
155        # So only compile Java code on Android <= O (version 8).
156        cls.use_compiled_java_code = android_version <= 8
157
158    def setUp(self):
159        if self.id().find('TraceOffCpu') != -1 and not is_trace_offcpu_supported():
160            self.skipTest('trace-offcpu is not supported on device')
161        cls = self.__class__
162        if not cls.has_perf_data_for_report:
163            cls.has_perf_data_for_report = True
164            self.run_app_profiler()
165            shutil.copy('perf.data', 'perf.data_for_report')
166            remove('binary_cache_for_report')
167            shutil.copytree('binary_cache', 'binary_cache_for_report')
168        else:
169            shutil.copy('perf.data_for_report', 'perf.data')
170            remove('binary_cache')
171            shutil.copytree('binary_cache_for_report', 'binary_cache')
172
173    @classmethod
174    def tearDownClass(cls):
175        if hasattr(cls, 'test_result') and cls.test_result and not cls.test_result.wasSuccessful():
176            return
177        if hasattr(cls, 'package_name'):
178            cls.adb.check_run(["uninstall", cls.package_name])
179        remove("binary_cache")
180        remove("annotated_files")
181        remove("perf.data")
182        remove("report.txt")
183        remove("pprof.profile")
184        if cls.has_perf_data_for_report:
185            cls.has_perf_data_for_report = False
186            remove('perf.data_for_report')
187            remove('binary_cache_for_report')
188
189    def run(self, result=None):
190        self.__class__.test_result = result
191        super(TestExampleBase, self).run(result)
192
193    def run_app_profiler(self, record_arg="-g --duration 10", build_binary_cache=True,
194                         start_activity=True):
195        args = ['app_profiler.py', '--app', self.package_name, '-r', record_arg, '-o', 'perf.data']
196        if not build_binary_cache:
197            args.append("-nb")
198        if self.use_compiled_java_code and not self.__class__.compiled:
199            args.append('--compile_java_code')
200            self.__class__.compiled = True
201        if start_activity:
202            args += ["-a", self.activity_name]
203        args += ["-lib", self.example_path]
204        if not self.adb_root:
205            args.append("--disable_adb_root")
206        self.run_cmd(args)
207        self.check_exist(filename="perf.data")
208        if build_binary_cache:
209            self.check_exist(dirname="binary_cache")
210
211    def check_exist(self, filename=None, dirname=None):
212        if filename:
213            self.assertTrue(os.path.isfile(filename), filename)
214        if dirname:
215            self.assertTrue(os.path.isdir(dirname), dirname)
216
217    def check_file_under_dir(self, dirname, filename):
218        self.check_exist(dirname=dirname)
219        for _, _, files in os.walk(dirname):
220            for f in files:
221                if f == filename:
222                    return
223        self.fail("Failed to call check_file_under_dir(dir=%s, file=%s)" % (dirname, filename))
224
225
226    def check_strings_in_file(self, filename, strings):
227        self.check_exist(filename=filename)
228        with open(filename, 'r') as fh:
229            self.check_strings_in_content(fh.read(), strings)
230
231    def check_strings_in_content(self, content, strings):
232        for s in strings:
233            self.assertNotEqual(content.find(s), -1, "s: %s, content: %s" % (s, content))
234
235    def check_annotation_summary(self, summary_file, check_entries):
236        """ check_entries is a list of (name, accumulated_period, period).
237            This function checks for each entry, if the line containing [name]
238            has at least required accumulated_period and period.
239        """
240        self.check_exist(filename=summary_file)
241        with open(summary_file, 'r') as fh:
242            summary = fh.read()
243        fulfilled = [False for x in check_entries]
244        summary_check_re = re.compile(r'accumulated_period:\s*([\d.]+)%.*period:\s*([\d.]+)%')
245        for line in summary.split('\n'):
246            for i, (name, need_acc_period, need_period) in enumerate(check_entries):
247                if not fulfilled[i] and name in line:
248                    m = summary_check_re.search(line)
249                    if m:
250                        acc_period = float(m.group(1))
251                        period = float(m.group(2))
252                        if acc_period >= need_acc_period and period >= need_period:
253                            fulfilled[i] = True
254        self.assertEqual(len(fulfilled), sum([int(x) for x in fulfilled]), fulfilled)
255
256    def check_inferno_report_html(self, check_entries, filename="report.html"):
257        self.check_exist(filename=filename)
258        with open(filename, 'r') as fh:
259            data = fh.read()
260        fulfilled = [False for _ in check_entries]
261        for line in data.split('\n'):
262            # each entry is a (function_name, min_percentage) pair.
263            for i, entry in enumerate(check_entries):
264                if fulfilled[i] or line.find(entry[0]) == -1:
265                    continue
266                m = re.search(r'(\d+\.\d+)%', line)
267                if m and float(m.group(1)) >= entry[1]:
268                    fulfilled[i] = True
269                    break
270        self.assertEqual(fulfilled, [True for _ in check_entries])
271
272    def common_test_app_profiler(self):
273        self.run_cmd(["app_profiler.py", "-h"])
274        remove("binary_cache")
275        self.run_app_profiler(build_binary_cache=False)
276        self.assertFalse(os.path.isdir("binary_cache"))
277        args = ["binary_cache_builder.py"]
278        if not self.adb_root:
279            args.append("--disable_adb_root")
280        self.run_cmd(args)
281        self.check_exist(dirname="binary_cache")
282        remove("binary_cache")
283        self.run_app_profiler(build_binary_cache=True)
284        self.run_app_profiler()
285        self.run_app_profiler(start_activity=False)
286
287    def common_test_report(self):
288        self.run_cmd(["report.py", "-h"])
289        self.run_cmd(["report.py"])
290        self.run_cmd(["report.py", "-i", "perf.data"])
291        self.run_cmd(["report.py", "-g"])
292        self.run_cmd(["report.py", "--self-kill-for-testing", "-g", "--gui"])
293
294    def common_test_annotate(self):
295        self.run_cmd(["annotate.py", "-h"])
296        remove("annotated_files")
297        self.run_cmd(["annotate.py", "-s", self.example_path])
298        self.check_exist(dirname="annotated_files")
299
300    def common_test_report_sample(self, check_strings):
301        self.run_cmd(["report_sample.py", "-h"])
302        self.run_cmd(["report_sample.py"])
303        output = self.run_cmd(["report_sample.py", "perf.data"], return_output=True)
304        self.check_strings_in_content(output, check_strings)
305
306    def common_test_pprof_proto_generator(self, check_strings_with_lines,
307                                          check_strings_without_lines):
308        if not HAS_GOOGLE_PROTOBUF:
309            log_info('Skip test for pprof_proto_generator because google.protobuf is missing')
310            return
311        self.run_cmd(["pprof_proto_generator.py", "-h"])
312        self.run_cmd(["pprof_proto_generator.py"])
313        remove("pprof.profile")
314        self.run_cmd(["pprof_proto_generator.py", "-i", "perf.data", "-o", "pprof.profile"])
315        self.check_exist(filename="pprof.profile")
316        self.run_cmd(["pprof_proto_generator.py", "--show"])
317        output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"],
318                              return_output=True)
319        self.check_strings_in_content(output, check_strings_with_lines + ["has_line_numbers: True"])
320        remove("binary_cache")
321        self.run_cmd(["pprof_proto_generator.py"])
322        output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"],
323                              return_output=True)
324        self.check_strings_in_content(output, check_strings_without_lines +
325                                      ["has_line_numbers: False"])
326
327    def common_test_inferno(self):
328        self.run_cmd([INFERNO_SCRIPT, "-h"])
329        remove("perf.data")
330        append_args = [] if self.adb_root else ["--disable_adb_root"]
331        self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-t", "3"] + append_args)
332        self.check_exist(filename="perf.data")
333        self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-f", "1000", "-du", "-t", "1"] +
334                     append_args)
335        self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-e", "100000 cpu-cycles",
336                      "-t", "1"] + append_args)
337        self.run_cmd([INFERNO_SCRIPT, "-sc"])
338
339    def common_test_report_html(self):
340        self.run_cmd(['report_html.py', '-h'])
341        self.run_cmd(['report_html.py'])
342        self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata'])
343        self.run_cmd(['report_html.py', '--add_disassembly'])
344        # Test with multiple perf.data.
345        shutil.move('perf.data', 'perf2.data')
346        self.run_app_profiler(record_arg='-g -f 1000 --duration 3 -e task-clock:u')
347        self.run_cmd(['report_html.py', '-i', 'perf.data', 'perf2.data'])
348        remove('perf2.data')
349
350
351class TestExamplePureJava(TestExampleBase):
352    @classmethod
353    def setUpClass(cls):
354        cls.prepare("SimpleperfExamplePureJava",
355                    "com.example.simpleperf.simpleperfexamplepurejava",
356                    ".MainActivity")
357
358    def test_app_profiler(self):
359        self.common_test_app_profiler()
360
361    def test_app_profiler_profile_from_launch(self):
362        self.run_app_profiler(start_activity=True, build_binary_cache=False)
363        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
364        self.check_strings_in_file("report.txt", [
365            "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
366            "__start_thread"])
367
368    def test_app_profiler_multiprocesses(self):
369        self.adb.check_run(['shell', 'am', 'force-stop', self.package_name])
370        self.adb.check_run(['shell', 'am', 'start', '-n',
371                            self.package_name + '/.MultiProcessActivity'])
372        # Wait until both MultiProcessActivity and MultiProcessService set up.
373        time.sleep(3)
374        self.run_app_profiler(start_activity=False)
375        self.run_cmd(["report.py", "-o", "report.txt"])
376        self.check_strings_in_file("report.txt", ["BusyService", "BusyThread"])
377
378    def test_app_profiler_with_ctrl_c(self):
379        if is_windows():
380            return
381        self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity'])
382        time.sleep(1)
383        args = [sys.executable, "app_profiler.py", "--app", self.package_name,
384                "-r", "--duration 10000", "--disable_adb_root"]
385        subproc = subprocess.Popen(args)
386        time.sleep(3)
387
388        subproc.send_signal(signal.SIGINT)
389        subproc.wait()
390        self.assertEqual(subproc.returncode, 0)
391        self.run_cmd(["report.py"])
392
393    def test_app_profiler_stop_after_app_exit(self):
394        self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity'])
395        time.sleep(1)
396        subproc = subprocess.Popen([sys.executable, 'app_profiler.py', '--app', self.package_name,
397                                    '-r', '--duration 10000', '--disable_adb_root'])
398        time.sleep(3)
399        self.adb.check_run(['shell', 'am', 'force-stop', self.package_name])
400        subproc.wait()
401        self.assertEqual(subproc.returncode, 0)
402        self.run_cmd(["report.py"])
403
404    def test_app_profiler_with_ndk_path(self):
405        # Although we pass an invalid ndk path, it should be able to find tools in default ndk path.
406        self.run_cmd(['app_profiler.py', '--app', self.package_name, '-a', self.activity_name,
407                      '--ndk_path', '.'])
408
409    def test_report(self):
410        self.common_test_report()
411        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
412        self.check_strings_in_file("report.txt", [
413            "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
414            "__start_thread"])
415
416    def test_profile_with_process_id(self):
417        self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity'])
418        time.sleep(1)
419        pid = self.adb.check_run_and_return_output([
420            'shell', 'pidof', 'com.example.simpleperf.simpleperfexamplepurejava']).strip()
421        self.run_app_profiler(start_activity=False, record_arg='-g --duration 10 -p ' + pid)
422        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
423        self.check_strings_in_file("report.txt", [
424            "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
425            "__start_thread"])
426
427    def test_annotate(self):
428        self.common_test_annotate()
429        if not self.use_compiled_java_code:
430            # Currently annotating Java code is only supported when the Java code is compiled.
431            return
432        self.check_file_under_dir("annotated_files", "MainActivity.java")
433        summary_file = os.path.join("annotated_files", "summary")
434        self.check_annotation_summary(summary_file, [
435            ("MainActivity.java", 80, 80),
436            ("run", 80, 0),
437            ("callFunction", 0, 0),
438            ("line 23", 80, 0)])
439
440    def test_report_sample(self):
441        self.common_test_report_sample(
442            ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
443             "__start_thread"])
444
445    def test_pprof_proto_generator(self):
446        check_strings_with_lines = []
447        if self.use_compiled_java_code:
448            check_strings_with_lines = [
449                "com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java",
450                "run"]
451        self.common_test_pprof_proto_generator(
452            check_strings_with_lines=check_strings_with_lines,
453            check_strings_without_lines=[
454                "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run"])
455
456    def test_inferno(self):
457        self.common_test_inferno()
458        self.run_app_profiler()
459        self.run_cmd([INFERNO_SCRIPT, "-sc"])
460        self.check_inferno_report_html(
461            [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run', 80)])
462        self.run_cmd([INFERNO_SCRIPT, "-sc", "-o", "report2.html"])
463        self.check_inferno_report_html(
464            [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run', 80)],
465            "report2.html")
466        remove("report2.html")
467
468    def test_inferno_in_another_dir(self):
469        test_dir = 'inferno_testdir'
470        saved_dir = os.getcwd()
471        remove(test_dir)
472        os.mkdir(test_dir)
473        os.chdir(test_dir)
474        self.run_cmd(['python', os.path.join(saved_dir, 'app_profiler.py'),
475                      '--app', self.package_name, '-r', '-e task-clock:u -g --duration 3'])
476        self.check_exist(filename="perf.data")
477        self.run_cmd([INFERNO_SCRIPT, "-sc"])
478        os.chdir(saved_dir)
479        remove(test_dir)
480
481    def test_report_html(self):
482        self.common_test_report_html()
483
484    def test_run_simpleperf_without_usb_connection(self):
485        self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity'])
486        self.run_cmd(['run_simpleperf_without_usb_connection.py', 'start', '-p',
487                      self.package_name, '--size_limit', '1M'])
488        self.adb.check_run(['kill-server'])
489        time.sleep(3)
490        self.run_cmd(['run_simpleperf_without_usb_connection.py', 'stop'])
491        self.check_exist(filename="perf.data")
492        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
493
494
495class TestExamplePureJavaRoot(TestExampleBase):
496    @classmethod
497    def setUpClass(cls):
498        cls.prepare("SimpleperfExamplePureJava",
499                    "com.example.simpleperf.simpleperfexamplepurejava",
500                    ".MainActivity",
501                    adb_root=True)
502
503    def test_app_profiler(self):
504        self.common_test_app_profiler()
505
506
507class TestExamplePureJavaTraceOffCpu(TestExampleBase):
508    @classmethod
509    def setUpClass(cls):
510        cls.prepare("SimpleperfExamplePureJava",
511                    "com.example.simpleperf.simpleperfexamplepurejava",
512                    ".SleepActivity")
513
514    def test_smoke(self):
515        self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu")
516        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
517        self.check_strings_in_file("report.txt", [
518            "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run",
519            "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction",
520            "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction"
521            ])
522        remove("annotated_files")
523        self.run_cmd(["annotate.py", "-s", self.example_path])
524        self.check_exist(dirname="annotated_files")
525        if self.use_compiled_java_code:
526            self.check_file_under_dir("annotated_files", "SleepActivity.java")
527            summary_file = os.path.join("annotated_files", "summary")
528            self.check_annotation_summary(summary_file, [
529                ("SleepActivity.java", 80, 20),
530                ("run", 80, 0),
531                ("RunFunction", 20, 20),
532                ("SleepFunction", 20, 0),
533                ("line 24", 20, 0),
534                ("line 32", 20, 0)])
535        self.run_cmd([INFERNO_SCRIPT, "-sc"])
536        self.check_inferno_report_html(
537            [('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run', 80),
538             ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction',
539              20),
540             ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction',
541              20)])
542
543
544class TestExampleWithNative(TestExampleBase):
545    @classmethod
546    def setUpClass(cls):
547        cls.prepare("SimpleperfExampleWithNative",
548                    "com.example.simpleperf.simpleperfexamplewithnative",
549                    ".MainActivity")
550
551    def test_app_profiler(self):
552        self.common_test_app_profiler()
553
554    def test_app_profiler_profile_from_launch(self):
555        self.run_app_profiler(start_activity=True, build_binary_cache=False)
556        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
557        self.check_strings_in_file("report.txt", ["BusyLoopThread", "__start_thread"])
558
559    def test_report(self):
560        self.common_test_report()
561        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
562        self.check_strings_in_file("report.txt", ["BusyLoopThread", "__start_thread"])
563
564    def test_annotate(self):
565        self.common_test_annotate()
566        self.check_file_under_dir("annotated_files", "native-lib.cpp")
567        summary_file = os.path.join("annotated_files", "summary")
568        self.check_annotation_summary(summary_file, [
569            ("native-lib.cpp", 20, 0),
570            ("BusyLoopThread", 20, 0),
571            ("line 46", 20, 0)])
572
573    def test_report_sample(self):
574        self.common_test_report_sample(
575            ["BusyLoopThread",
576             "__start_thread"])
577
578    def test_pprof_proto_generator(self):
579        self.common_test_pprof_proto_generator(
580            check_strings_with_lines=["native-lib.cpp", "BusyLoopThread"],
581            check_strings_without_lines=["BusyLoopThread"])
582
583    def test_inferno(self):
584        self.common_test_inferno()
585        self.run_app_profiler()
586        self.run_cmd([INFERNO_SCRIPT, "-sc"])
587        self.check_inferno_report_html([('BusyLoopThread', 20)])
588
589    def test_report_html(self):
590        self.common_test_report_html()
591        self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata',
592                      '--add_disassembly', '--binary_filter', "libnative-lib.so"])
593
594
595class TestExampleWithNativeRoot(TestExampleBase):
596    @classmethod
597    def setUpClass(cls):
598        cls.prepare("SimpleperfExampleWithNative",
599                    "com.example.simpleperf.simpleperfexamplewithnative",
600                    ".MainActivity",
601                    adb_root=True)
602
603    def test_app_profiler(self):
604        self.common_test_app_profiler()
605
606
607class TestExampleWithNativeTraceOffCpu(TestExampleBase):
608    @classmethod
609    def setUpClass(cls):
610        cls.prepare("SimpleperfExampleWithNative",
611                    "com.example.simpleperf.simpleperfexamplewithnative",
612                    ".SleepActivity")
613
614    def test_smoke(self):
615        self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu")
616        self.run_cmd(["report.py", "-g", "--comms", "SleepThread", "-o", "report.txt"])
617        self.check_strings_in_file("report.txt", [
618            "SleepThread(void*)",
619            "RunFunction()",
620            "SleepFunction(unsigned long long)"])
621        remove("annotated_files")
622        self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "SleepThread"])
623        self.check_exist(dirname="annotated_files")
624        self.check_file_under_dir("annotated_files", "native-lib.cpp")
625        summary_file = os.path.join("annotated_files", "summary")
626        self.check_annotation_summary(summary_file, [
627            ("native-lib.cpp", 80, 20),
628            ("SleepThread", 80, 0),
629            ("RunFunction", 20, 20),
630            ("SleepFunction", 20, 0),
631            ("line 73", 20, 0),
632            ("line 83", 20, 0)])
633        self.run_cmd([INFERNO_SCRIPT, "-sc"])
634        self.check_inferno_report_html([('SleepThread', 80),
635                                        ('RunFunction', 20),
636                                        ('SleepFunction', 20)])
637
638
639class TestExampleWithNativeJniCall(TestExampleBase):
640    @classmethod
641    def setUpClass(cls):
642        cls.prepare("SimpleperfExampleWithNative",
643                    "com.example.simpleperf.simpleperfexamplewithnative",
644                    ".MixActivity")
645
646    def test_smoke(self):
647        self.run_app_profiler()
648        self.run_cmd(["report.py", "-g", "--comms", "BusyThread", "-o", "report.txt"])
649        self.check_strings_in_file("report.txt", [
650            "com.example.simpleperf.simpleperfexamplewithnative.MixActivity$1.run",
651            "com.example.simpleperf.simpleperfexamplewithnative.MixActivity.callFunction",
652            "Java_com_example_simpleperf_simpleperfexamplewithnative_MixActivity_callFunction"])
653        remove("annotated_files")
654        self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "BusyThread"])
655        self.check_exist(dirname="annotated_files")
656        self.check_file_under_dir("annotated_files", "native-lib.cpp")
657        summary_file = os.path.join("annotated_files", "summary")
658        self.check_annotation_summary(summary_file, [("native-lib.cpp", 5, 0), ("line 40", 5, 0)])
659        if self.use_compiled_java_code:
660            self.check_file_under_dir("annotated_files", "MixActivity.java")
661            self.check_annotation_summary(summary_file, [
662                ("MixActivity.java", 80, 0),
663                ("run", 80, 0),
664                ("line 26", 20, 0),
665                ("native-lib.cpp", 5, 0),
666                ("line 40", 5, 0)])
667
668        self.run_cmd([INFERNO_SCRIPT, "-sc"])
669
670
671class TestExampleWithNativeForceArm(TestExampleWithNative):
672    @classmethod
673    def setUpClass(cls):
674        cls.prepare("SimpleperfExampleWithNative",
675                    "com.example.simpleperf.simpleperfexamplewithnative",
676                    ".MainActivity",
677                    abi="armeabi-v7a")
678
679
680class TestExampleWithNativeForceArmRoot(TestExampleWithNativeRoot):
681    @classmethod
682    def setUpClass(cls):
683        cls.prepare("SimpleperfExampleWithNative",
684                    "com.example.simpleperf.simpleperfexamplewithnative",
685                    ".MainActivity",
686                    abi="armeabi-v7a",
687                    adb_root=False)
688
689
690class TestExampleWithNativeTraceOffCpuForceArm(TestExampleWithNativeTraceOffCpu):
691    @classmethod
692    def setUpClass(cls):
693        cls.prepare("SimpleperfExampleWithNative",
694                    "com.example.simpleperf.simpleperfexamplewithnative",
695                    ".SleepActivity",
696                    abi="armeabi-v7a")
697
698
699class TestExampleOfKotlin(TestExampleBase):
700    @classmethod
701    def setUpClass(cls):
702        cls.prepare("SimpleperfExampleOfKotlin",
703                    "com.example.simpleperf.simpleperfexampleofkotlin",
704                    ".MainActivity")
705
706    def test_app_profiler(self):
707        self.common_test_app_profiler()
708
709    def test_app_profiler_profile_from_launch(self):
710        self.run_app_profiler(start_activity=True, build_binary_cache=False)
711        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
712        self.check_strings_in_file("report.txt", [
713            "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." +
714            "run", "__start_thread"])
715
716    def test_report(self):
717        self.common_test_report()
718        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
719        self.check_strings_in_file("report.txt", [
720            "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." +
721            "run", "__start_thread"])
722
723    def test_annotate(self):
724        if not self.use_compiled_java_code:
725            return
726        self.common_test_annotate()
727        self.check_file_under_dir("annotated_files", "MainActivity.kt")
728        summary_file = os.path.join("annotated_files", "summary")
729        self.check_annotation_summary(summary_file, [
730            ("MainActivity.kt", 80, 80),
731            ("run", 80, 0),
732            ("callFunction", 0, 0),
733            ("line 19", 80, 0),
734            ("line 25", 0, 0)])
735
736    def test_report_sample(self):
737        self.common_test_report_sample([
738            "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." +
739            "run", "__start_thread"])
740
741    def test_pprof_proto_generator(self):
742        check_strings_with_lines = []
743        if self.use_compiled_java_code:
744            check_strings_with_lines = [
745                "com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt",
746                "run"]
747        self.common_test_pprof_proto_generator(
748            check_strings_with_lines=check_strings_with_lines,
749            check_strings_without_lines=["com.example.simpleperf.simpleperfexampleofkotlin." +
750                                         "MainActivity$createBusyThread$1.run"])
751
752    def test_inferno(self):
753        self.common_test_inferno()
754        self.run_app_profiler()
755        self.run_cmd([INFERNO_SCRIPT, "-sc"])
756        self.check_inferno_report_html([('com.example.simpleperf.simpleperfexampleofkotlin.' +
757                                         'MainActivity$createBusyThread$1.run', 80)])
758
759    def test_report_html(self):
760        self.common_test_report_html()
761
762
763class TestExampleOfKotlinRoot(TestExampleBase):
764    @classmethod
765    def setUpClass(cls):
766        cls.prepare("SimpleperfExampleOfKotlin",
767                    "com.example.simpleperf.simpleperfexampleofkotlin",
768                    ".MainActivity",
769                    adb_root=True)
770
771    def test_app_profiler(self):
772        self.common_test_app_profiler()
773
774
775class TestExampleOfKotlinTraceOffCpu(TestExampleBase):
776    @classmethod
777    def setUpClass(cls):
778        cls.prepare("SimpleperfExampleOfKotlin",
779                    "com.example.simpleperf.simpleperfexampleofkotlin",
780                    ".SleepActivity")
781
782    def test_smoke(self):
783        self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu")
784        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
785        function_prefix = "com.example.simpleperf.simpleperfexampleofkotlin." + \
786                          "SleepActivity$createRunSleepThread$1."
787        self.check_strings_in_file("report.txt", [
788            function_prefix + "run",
789            function_prefix + "RunFunction",
790            function_prefix + "SleepFunction"
791            ])
792        if self.use_compiled_java_code:
793            remove("annotated_files")
794            self.run_cmd(["annotate.py", "-s", self.example_path])
795            self.check_exist(dirname="annotated_files")
796            self.check_file_under_dir("annotated_files", "SleepActivity.kt")
797            summary_file = os.path.join("annotated_files", "summary")
798            self.check_annotation_summary(summary_file, [
799                ("SleepActivity.kt", 80, 20),
800                ("run", 80, 0),
801                ("RunFunction", 20, 20),
802                ("SleepFunction", 20, 0),
803                ("line 24", 20, 0),
804                ("line 32", 20, 0)])
805
806        self.run_cmd([INFERNO_SCRIPT, "-sc"])
807        self.check_inferno_report_html([
808            (function_prefix + 'run', 80),
809            (function_prefix + 'RunFunction', 20),
810            (function_prefix + 'SleepFunction', 20)])
811
812
813class TestNativeProfiling(TestBase):
814    def setUp(self):
815        self.adb = AdbHelper()
816        self.is_rooted_device = self.adb.switch_to_root()
817
818    def test_profile_cmd(self):
819        self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--disable_adb_root"])
820        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
821
822    def test_profile_native_program(self):
823        if not self.is_rooted_device:
824            return
825        self.run_cmd(["app_profiler.py", "-np", "surfaceflinger"])
826        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
827        self.run_cmd([INFERNO_SCRIPT, "-sc"])
828        self.run_cmd([INFERNO_SCRIPT, "-np", "surfaceflinger"])
829
830    def test_profile_pids(self):
831        if not self.is_rooted_device:
832            return
833        pid = int(self.adb.check_run_and_return_output(['shell', 'pidof', 'system_server']))
834        self.run_cmd(['app_profiler.py', '--pid', str(pid), '-r', '--duration 1'])
835        self.run_cmd(['app_profiler.py', '--pid', str(pid), str(pid), '-r', '--duration 1'])
836        self.run_cmd(['app_profiler.py', '--tid', str(pid), '-r', '--duration 1'])
837        self.run_cmd(['app_profiler.py', '--tid', str(pid), str(pid), '-r', '--duration 1'])
838        self.run_cmd([INFERNO_SCRIPT, '--pid', str(pid), '-t', '1'])
839
840    def test_profile_system_wide(self):
841        if not self.is_rooted_device:
842            return
843        self.run_cmd(['app_profiler.py', '--system_wide', '-r', '--duration 1'])
844
845
846class TestReportLib(unittest.TestCase):
847    def setUp(self):
848        self.report_lib = ReportLib()
849        self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_symbols.data'))
850
851    def tearDown(self):
852        self.report_lib.Close()
853
854    def test_build_id(self):
855        build_id = self.report_lib.GetBuildIdForPath('/data/t2')
856        self.assertEqual(build_id, '0x70f1fe24500fc8b0d9eb477199ca1ca21acca4de')
857
858    def test_symbol(self):
859        found_func2 = False
860        while self.report_lib.GetNextSample():
861            symbol = self.report_lib.GetSymbolOfCurrentSample()
862            if symbol.symbol_name == 'func2(int, int)':
863                found_func2 = True
864                self.assertEqual(symbol.symbol_addr, 0x4004ed)
865                self.assertEqual(symbol.symbol_len, 0x14)
866        self.assertTrue(found_func2)
867
868    def test_sample(self):
869        found_sample = False
870        while self.report_lib.GetNextSample():
871            sample = self.report_lib.GetCurrentSample()
872            if sample.ip == 0x4004ff and sample.time == 7637889424953:
873                found_sample = True
874                self.assertEqual(sample.pid, 15926)
875                self.assertEqual(sample.tid, 15926)
876                self.assertEqual(sample.thread_comm, 't2')
877                self.assertEqual(sample.cpu, 5)
878                self.assertEqual(sample.period, 694614)
879                event = self.report_lib.GetEventOfCurrentSample()
880                self.assertEqual(event.name, 'cpu-cycles')
881                callchain = self.report_lib.GetCallChainOfCurrentSample()
882                self.assertEqual(callchain.nr, 0)
883        self.assertTrue(found_sample)
884
885    def test_meta_info(self):
886        self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data'))
887        meta_info = self.report_lib.MetaInfo()
888        self.assertTrue("simpleperf_version" in meta_info)
889        self.assertEqual(meta_info["system_wide_collection"], "false")
890        self.assertEqual(meta_info["trace_offcpu"], "true")
891        self.assertEqual(meta_info["event_type_info"], "cpu-cycles,0,0\nsched:sched_switch,2,47")
892        self.assertTrue("product_props" in meta_info)
893
894    def test_event_name_from_meta_info(self):
895        self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_tracepoint_event.data'))
896        event_names = set()
897        while self.report_lib.GetNextSample():
898            event_names.add(self.report_lib.GetEventOfCurrentSample().name)
899        self.assertTrue('sched:sched_switch' in event_names)
900        self.assertTrue('cpu-cycles' in event_names)
901
902    def test_record_cmd(self):
903        self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data'))
904        self.assertEqual(self.report_lib.GetRecordCmd(),
905                         "/data/local/tmp/simpleperf record --trace-offcpu --duration 2 -g " +
906                         "./simpleperf_runtest_run_and_sleep64")
907
908    def test_offcpu(self):
909        self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data'))
910        total_period = 0
911        sleep_function_period = 0
912        sleep_function_name = "SleepFunction(unsigned long long)"
913        while self.report_lib.GetNextSample():
914            sample = self.report_lib.GetCurrentSample()
915            total_period += sample.period
916            if self.report_lib.GetSymbolOfCurrentSample().symbol_name == sleep_function_name:
917                sleep_function_period += sample.period
918                continue
919            callchain = self.report_lib.GetCallChainOfCurrentSample()
920            for i in range(callchain.nr):
921                if callchain.entries[i].symbol.symbol_name == sleep_function_name:
922                    sleep_function_period += sample.period
923                    break
924            self.assertEqual(self.report_lib.GetEventOfCurrentSample().name, 'cpu-cycles')
925        sleep_percentage = float(sleep_function_period) / total_period
926        self.assertGreater(sleep_percentage, 0.30)
927
928    def test_show_art_frames(self):
929        def has_art_frame(report_lib):
930            report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_interpreter_frames.data'))
931            result = False
932            while report_lib.GetNextSample():
933                callchain = report_lib.GetCallChainOfCurrentSample()
934                for i in range(callchain.nr):
935                    if callchain.entries[i].symbol.symbol_name == 'artMterpAsmInstructionStart':
936                        result = True
937                        break
938            report_lib.Close()
939            return result
940
941        report_lib = ReportLib()
942        self.assertFalse(has_art_frame(report_lib))
943        report_lib = ReportLib()
944        report_lib.ShowArtFrames(False)
945        self.assertFalse(has_art_frame(report_lib))
946        report_lib = ReportLib()
947        report_lib.ShowArtFrames(True)
948        self.assertTrue(has_art_frame(report_lib))
949
950    def test_tracing_data(self):
951        self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_tracepoint_event.data'))
952        has_tracing_data = False
953        while self.report_lib.GetNextSample():
954            event = self.report_lib.GetEventOfCurrentSample()
955            tracing_data = self.report_lib.GetTracingDataOfCurrentSample()
956            if event.name == 'sched:sched_switch':
957                self.assertIsNotNone(tracing_data)
958                self.assertIn('prev_pid', tracing_data)
959                self.assertIn('next_comm', tracing_data)
960                if tracing_data['prev_pid'] == 9896 and tracing_data['next_comm'] == 'swapper/4':
961                    has_tracing_data = True
962            else:
963                self.assertIsNone(tracing_data)
964        self.assertTrue(has_tracing_data)
965
966
967class TestRunSimpleperfOnDevice(TestBase):
968    def test_smoke(self):
969        self.run_cmd(['run_simpleperf_on_device.py', 'list', '--show-features'])
970
971
972class TestTools(unittest.TestCase):
973    def test_addr2nearestline(self):
974        self.run_addr2nearestline_test(True)
975        self.run_addr2nearestline_test(False)
976
977    def run_addr2nearestline_test(self, with_function_name):
978        binary_cache_path = 'testdata'
979        test_map = {
980            '/simpleperf_runtest_two_functions_arm64': [
981                {
982                    'func_addr': 0x668,
983                    'addr': 0x668,
984                    'source': 'system/extras/simpleperf/runtest/two_functions.cpp:20',
985                    'function': 'main',
986                },
987                {
988                    'func_addr': 0x668,
989                    'addr': 0x6a4,
990                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:7
991                                 system/extras/simpleperf/runtest/two_functions.cpp:22""",
992                    'function': """Function1()
993                                   main""",
994                },
995            ],
996            '/simpleperf_runtest_two_functions_arm': [
997                {
998                    'func_addr': 0x784,
999                    'addr': 0x7b0,
1000                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:14
1001                                 system/extras/simpleperf/runtest/two_functions.cpp:23""",
1002                    'function': """Function2()
1003                                   main""",
1004                },
1005                {
1006                    'func_addr': 0x784,
1007                    'addr': 0x7d0,
1008                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:15
1009                                 system/extras/simpleperf/runtest/two_functions.cpp:23""",
1010                    'function': """Function2()
1011                                   main""",
1012                }
1013            ],
1014            '/simpleperf_runtest_two_functions_x86_64': [
1015                {
1016                    'func_addr': 0x840,
1017                    'addr': 0x840,
1018                    'source': 'system/extras/simpleperf/runtest/two_functions.cpp:7',
1019                    'function': 'Function1()',
1020                },
1021                {
1022                    'func_addr': 0x920,
1023                    'addr': 0x94a,
1024                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:7
1025                                 system/extras/simpleperf/runtest/two_functions.cpp:22""",
1026                    'function': """Function1()
1027                                   main""",
1028                }
1029            ],
1030            '/simpleperf_runtest_two_functions_x86': [
1031                {
1032                    'func_addr': 0x6d0,
1033                    'addr': 0x6da,
1034                    'source': 'system/extras/simpleperf/runtest/two_functions.cpp:14',
1035                    'function': 'Function2()',
1036                },
1037                {
1038                    'func_addr': 0x710,
1039                    'addr': 0x749,
1040                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:8
1041                                 system/extras/simpleperf/runtest/two_functions.cpp:22""",
1042                    'function': """Function1()
1043                                   main""",
1044                }
1045            ],
1046        }
1047        addr2line = Addr2Nearestline(None, binary_cache_path, with_function_name)
1048        for dso_path in test_map:
1049            test_addrs = test_map[dso_path]
1050            for test_addr in test_addrs:
1051                addr2line.add_addr(dso_path, test_addr['func_addr'], test_addr['addr'])
1052        addr2line.convert_addrs_to_lines()
1053        for dso_path in test_map:
1054            dso = addr2line.get_dso(dso_path)
1055            self.assertTrue(dso is not None)
1056            test_addrs = test_map[dso_path]
1057            for test_addr in test_addrs:
1058                expected_files = []
1059                expected_lines = []
1060                expected_functions = []
1061                for line in test_addr['source'].split('\n'):
1062                    items = line.split(':')
1063                    expected_files.append(items[0].strip())
1064                    expected_lines.append(int(items[1]))
1065                for line in test_addr['function'].split('\n'):
1066                    expected_functions.append(line.strip())
1067                self.assertEquals(len(expected_files), len(expected_functions))
1068
1069                actual_source = addr2line.get_addr_source(dso, test_addr['addr'])
1070                self.assertTrue(actual_source is not None)
1071                self.assertEqual(len(actual_source), len(expected_files))
1072                for i, source in enumerate(actual_source):
1073                    self.assertEqual(len(source), 3 if with_function_name else 2)
1074                    self.assertEqual(source[0], expected_files[i])
1075                    self.assertEqual(source[1], expected_lines[i])
1076                    if with_function_name:
1077                        self.assertEqual(source[2], expected_functions[i])
1078
1079    def test_objdump(self):
1080        binary_cache_path = 'testdata'
1081        test_map = {
1082            '/simpleperf_runtest_two_functions_arm64': {
1083                'start_addr': 0x668,
1084                'len': 116,
1085                'expected_items': [
1086                    ('main():', 0),
1087                    ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0),
1088                    (' 694:	add	x20, x20, #0x6de', 0x694),
1089                ],
1090            },
1091            '/simpleperf_runtest_two_functions_arm': {
1092                'start_addr': 0x784,
1093                'len': 80,
1094                'expected_items': [
1095                    ('main():', 0),
1096                    ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0),
1097                    ('     7ae:	bne.n	7a6 <main+0x22>', 0x7ae),
1098                ],
1099            },
1100            '/simpleperf_runtest_two_functions_x86_64': {
1101                'start_addr': 0x920,
1102                'len': 201,
1103                'expected_items': [
1104                    ('main():', 0),
1105                    ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0),
1106                    (' 96e:	mov    %edx,(%rbx,%rax,4)', 0x96e),
1107                ],
1108            },
1109            '/simpleperf_runtest_two_functions_x86': {
1110                'start_addr': 0x710,
1111                'len': 98,
1112                'expected_items': [
1113                    ('main():', 0),
1114                    ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0),
1115                    (' 748:	cmp    $0x5f5e100,%ebp', 0x748),
1116                ],
1117            },
1118        }
1119        objdump = Objdump(None, binary_cache_path)
1120        for dso_path in test_map:
1121            dso = test_map[dso_path]
1122            dso_info = objdump.get_dso_info(dso_path)
1123            self.assertIsNotNone(dso_info)
1124            disassemble_code = objdump.disassemble_code(dso_info, dso['start_addr'], dso['len'])
1125            self.assertTrue(disassemble_code)
1126            for item in dso['expected_items']:
1127                self.assertTrue(item in disassemble_code)
1128
1129    def test_readelf(self):
1130        test_map = {
1131            '/simpleperf_runtest_two_functions_arm64': {
1132                'arch': 'arm64',
1133                'build_id': '0xe8ecb3916d989dbdc068345c30f0c24300000000',
1134                'sections': ['.interp', '.note.android.ident', '.note.gnu.build-id', '.dynsym',
1135                             '.dynstr', '.gnu.hash', '.gnu.version', '.gnu.version_r', '.rela.dyn',
1136                             '.rela.plt', '.plt', '.text', '.rodata', '.eh_frame', '.eh_frame_hdr',
1137                             '.preinit_array', '.init_array', '.fini_array', '.dynamic', '.got',
1138                             '.got.plt', '.data', '.bss', '.comment', '.debug_str', '.debug_loc',
1139                             '.debug_abbrev', '.debug_info', '.debug_ranges', '.debug_macinfo',
1140                             '.debug_pubnames', '.debug_pubtypes', '.debug_line',
1141                             '.note.gnu.gold-version', '.symtab', '.strtab', '.shstrtab'],
1142            },
1143            '/simpleperf_runtest_two_functions_arm': {
1144                'arch': 'arm',
1145                'build_id': '0x718f5b36c4148ee1bd3f51af89ed2be600000000',
1146            },
1147            '/simpleperf_runtest_two_functions_x86_64': {
1148                'arch': 'x86_64',
1149            },
1150            '/simpleperf_runtest_two_functions_x86': {
1151                'arch': 'x86',
1152            }
1153        }
1154        readelf = ReadElf(None)
1155        for dso_path in test_map:
1156            dso_info = test_map[dso_path]
1157            path = 'testdata' + dso_path
1158            self.assertEqual(dso_info['arch'], readelf.get_arch(path))
1159            if 'build_id' in dso_info:
1160                self.assertEqual(dso_info['build_id'], readelf.get_build_id(path))
1161            if 'sections' in dso_info:
1162                self.assertEqual(dso_info['sections'], readelf.get_sections(path))
1163        self.assertEqual(readelf.get_arch('not_exist_file'), 'unknown')
1164        self.assertEqual(readelf.get_build_id('not_exist_file'), '')
1165        self.assertEqual(readelf.get_sections('not_exist_file'), [])
1166
1167    def test_source_file_searcher(self):
1168        searcher = SourceFileSearcher(['testdata'])
1169        def format_path(path):
1170            return path.replace('/', os.sep)
1171        # Find a C++ file with pure file name.
1172        self.assertEquals(
1173            format_path('testdata/SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'),
1174            searcher.get_real_path('native-lib.cpp'))
1175        # Find a C++ file with an absolute file path.
1176        self.assertEquals(
1177            format_path('testdata/SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'),
1178            searcher.get_real_path('/data/native-lib.cpp'))
1179        # Find a Java file.
1180        self.assertEquals(
1181            format_path('testdata/SimpleperfExampleWithNative/app/src/main/java/com/example/' +
1182                        'simpleperf/simpleperfexamplewithnative/MainActivity.java'),
1183            searcher.get_real_path('simpleperfexamplewithnative/MainActivity.java'))
1184        # Find a Kotlin file.
1185        self.assertEquals(
1186            format_path('testdata/SimpleperfExampleOfKotlin/app/src/main/java/com/example/' +
1187                        'simpleperf/simpleperfexampleofkotlin/MainActivity.kt'),
1188            searcher.get_real_path('MainActivity.kt'))
1189
1190
1191class TestNativeLibDownloader(unittest.TestCase):
1192    def test_smoke(self):
1193        adb = AdbHelper()
1194
1195        def is_lib_on_device(path):
1196            return adb.run(['shell', 'ls', path])
1197
1198        # Sync all native libs on device.
1199        adb.run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs'])
1200        downloader = NativeLibDownloader(None, 'arm64', adb)
1201        downloader.collect_native_libs_on_host(os.path.join(
1202            'testdata', 'SimpleperfExampleWithNative', 'app', 'build', 'intermediates', 'cmake',
1203            'profiling'))
1204        self.assertEqual(len(downloader.host_build_id_map), 2)
1205        for entry in downloader.host_build_id_map.values():
1206            self.assertEqual(entry.score, 3)
1207        downloader.collect_native_libs_on_device()
1208        self.assertEqual(len(downloader.device_build_id_map), 0)
1209
1210        lib_list = list(downloader.host_build_id_map.items())
1211        for sync_count in [0, 1, 2]:
1212            build_id_map = {}
1213            for i in range(sync_count):
1214                build_id_map[lib_list[i][0]] = lib_list[i][1]
1215            downloader.host_build_id_map = build_id_map
1216            downloader.sync_natives_libs_on_device()
1217            downloader.collect_native_libs_on_device()
1218            self.assertEqual(len(downloader.device_build_id_map), sync_count)
1219            for i, item in enumerate(lib_list):
1220                build_id = item[0]
1221                name = item[1].name
1222                if i < sync_count:
1223                    self.assertTrue(build_id in downloader.device_build_id_map)
1224                    self.assertEqual(name, downloader.device_build_id_map[build_id])
1225                    self.assertTrue(is_lib_on_device(downloader.dir_on_device + name))
1226                else:
1227                    self.assertTrue(build_id not in downloader.device_build_id_map)
1228                    self.assertFalse(is_lib_on_device(downloader.dir_on_device + name))
1229            if sync_count == 1:
1230                adb.run(['pull', '/data/local/tmp/native_libs/build_id_list', 'build_id_list'])
1231                with open('build_id_list', 'rb') as fh:
1232                    self.assertEqual(bytes_to_str(fh.read()),
1233                                     '{}={}\n'.format(lib_list[0][0], lib_list[0][1].name))
1234                remove('build_id_list')
1235        adb.run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs'])
1236
1237
1238class TestReportHtml(TestBase):
1239    def test_long_callchain(self):
1240        self.run_cmd(['report_html.py', '-i', 'testdata/perf_with_long_callchain.data'])
1241
1242
1243class TestBinaryCacheBuilder(TestBase):
1244    def test_copy_binaries_from_symfs_dirs(self):
1245        readelf = ReadElf(None)
1246        strip = find_tool_path('strip', arch='arm')
1247        self.assertIsNotNone(strip)
1248        symfs_dir = os.path.join('testdata', 'symfs_dir')
1249        remove(symfs_dir)
1250        os.mkdir(symfs_dir)
1251        filename = 'simpleperf_runtest_two_functions_arm'
1252        origin_file = os.path.join('testdata', filename)
1253        source_file = os.path.join(symfs_dir, filename)
1254        target_file = os.path.join('binary_cache', filename)
1255        expected_build_id = readelf.get_build_id(origin_file)
1256        binary_cache_builder = BinaryCacheBuilder(None, False)
1257        binary_cache_builder.binaries['simpleperf_runtest_two_functions_arm'] = expected_build_id
1258
1259        # Copy binary if target file doesn't exist.
1260        remove(target_file)
1261        self.run_cmd([strip, '--strip-all', '-o', source_file, origin_file])
1262        binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
1263        self.assertTrue(filecmp.cmp(target_file, source_file))
1264
1265        # Copy binary if target file doesn't have .symtab and source file has .symtab.
1266        self.run_cmd([strip, '--strip-debug', '-o', source_file, origin_file])
1267        binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
1268        self.assertTrue(filecmp.cmp(target_file, source_file))
1269
1270        # Copy binary if target file doesn't have .debug_line and source_files has .debug_line.
1271        shutil.copy(origin_file, source_file)
1272        binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
1273        self.assertTrue(filecmp.cmp(target_file, source_file))
1274
1275
1276def get_all_tests():
1277    tests = []
1278    for name, value in globals().items():
1279        if isinstance(value, type) and issubclass(value, unittest.TestCase):
1280            for member_name, member in inspect.getmembers(value):
1281                if isinstance(member, (types.MethodType, types.FunctionType)):
1282                    if member_name.startswith('test'):
1283                        tests.append(name + '.' + member_name)
1284    return sorted(tests)
1285
1286
1287def run_tests(tests):
1288    os.chdir(get_script_dir())
1289    build_testdata()
1290    log_info('Run tests with python%d\n%s' % (3 if is_python3() else 2, '\n'.join(tests)))
1291    argv = [sys.argv[0]] + tests
1292    unittest.main(argv=argv, failfast=True, verbosity=2, exit=False)
1293
1294
1295def main():
1296    parser = argparse.ArgumentParser(description='Test simpleperf scripts')
1297    parser.add_argument('--list-tests', action='store_true', help='List all tests.')
1298    parser.add_argument('--test-from', nargs=1, help='Run left tests from the selected test.')
1299    parser.add_argument('--python-version', choices=['2', '3', 'both'], default='both', help="""
1300                        Run tests on which python versions.""")
1301    parser.add_argument('pattern', nargs='*', help='Run tests matching the selected pattern.')
1302    args = parser.parse_args()
1303    tests = get_all_tests()
1304    if args.list_tests:
1305        print('\n'.join(tests))
1306        return
1307    if args.test_from:
1308        start_pos = 0
1309        while start_pos < len(tests) and tests[start_pos] != args.test_from[0]:
1310            start_pos += 1
1311        if start_pos == len(tests):
1312            log_exit("Can't find test %s" % args.test_from[0])
1313        tests = tests[start_pos:]
1314    if args.pattern:
1315        pattern = re.compile(fnmatch.translate(args.pattern[0]))
1316        new_tests = []
1317        for test in tests:
1318            if pattern.match(test):
1319                new_tests.append(test)
1320        tests = new_tests
1321        if not tests:
1322            log_exit('No tests are matched.')
1323
1324    if AdbHelper().get_android_version() < 7:
1325        log_info("Skip tests on Android version < N.")
1326        sys.exit(0)
1327
1328    if args.python_version == 'both':
1329        python_versions = [2, 3]
1330    else:
1331        python_versions = [int(args.python_version)]
1332    current_version = 3 if is_python3() else 2
1333    for version in python_versions:
1334        if version != current_version:
1335            argv = ['python3' if version == 3 else 'python']
1336            argv.append(os.path.join(get_script_dir(), 'test.py'))
1337            argv += sys.argv[1:]
1338            argv += ['--python-version', str(version)]
1339            subprocess.check_call(argv)
1340        else:
1341            run_tests(tests)
1342
1343
1344if __name__ == '__main__':
1345    main()
1346