diff options
author | Yabin Cui <yabinc@google.com> | 2021-04-05 11:44:21 -0700 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2021-04-12 12:07:03 -0700 |
commit | 9662a4c921e6bb2ad3be38a9d89cb88be86d1719 (patch) | |
tree | 39b575d772a53d4b7135413b95cac1b334a8dddb /simpleperf | |
parent | 6c56e2afb31a371ab8a54051a0949e197d997f33 (diff) | |
download | extras-9662a4c921e6bb2ad3be38a9d89cb88be86d1719.tar.gz |
simpleperf: prepare for merging test_monitor.py.
1. Refactor TestHelper class to remove global variable and add explicit
init class method.
2. Merge TestLogger into TestHelper.
3. Move build_testdata() into do_test.py.
4. Move test filter into an explicit function.
Bug: 182507493
Test: run test.py.
Change-Id: Ia76baa17197f6134ec4d7868e764edb66d29b6bd
Diffstat (limited to 'simpleperf')
-rw-r--r-- | simpleperf/scripts/test/api_profiler_test.py | 9 | ||||
-rw-r--r-- | simpleperf/scripts/test/app_profiler_test.py | 15 | ||||
-rw-r--r-- | simpleperf/scripts/test/app_test.py | 24 | ||||
-rw-r--r-- | simpleperf/scripts/test/binary_cache_builder_test.py | 13 | ||||
-rw-r--r-- | simpleperf/scripts/test/cpp_app_test.py | 9 | ||||
-rw-r--r-- | simpleperf/scripts/test/debug_unwind_reporter_test.py | 5 | ||||
-rwxr-xr-x | simpleperf/scripts/test/do_test.py | 155 | ||||
-rw-r--r-- | simpleperf/scripts/test/java_app_test.py | 9 | ||||
-rw-r--r-- | simpleperf/scripts/test/kotlin_app_test.py | 1 | ||||
-rw-r--r-- | simpleperf/scripts/test/pprof_proto_generator_test.py | 7 | ||||
-rw-r--r-- | simpleperf/scripts/test/report_html_test.py | 15 | ||||
-rw-r--r-- | simpleperf/scripts/test/report_lib_test.py | 22 | ||||
-rwxr-xr-x | simpleperf/scripts/test/test.py | 3 | ||||
-rw-r--r-- | simpleperf/scripts/test/test_utils.py | 201 | ||||
-rw-r--r-- | simpleperf/scripts/test/tools_test.py | 23 |
15 files changed, 257 insertions, 254 deletions
diff --git a/simpleperf/scripts/test/api_profiler_test.py b/simpleperf/scripts/test/api_profiler_test.py index 68fb3848..037b8fca 100644 --- a/simpleperf/scripts/test/api_profiler_test.py +++ b/simpleperf/scripts/test/api_profiler_test.py @@ -16,20 +16,21 @@ import os import time + from simpleperf_utils import log_info, remove -from . test_utils import TestBase, TEST_HELPER +from . test_utils import TestBase, TestHelper class TestApiProfiler(TestBase): def run_api_test(self, package_name, apk_name, expected_reports, min_android_version): - adb = TEST_HELPER.adb - if TEST_HELPER.android_version < ord(min_android_version) - ord('L') + 5: + adb = TestHelper.adb + if TestHelper.android_version < ord(min_android_version) - ord('L') + 5: log_info('skip this test on Android < %s.' % min_android_version) return # step 1: Prepare profiling. self.run_cmd(['api_profiler.py', 'prepare']) # step 2: Install and run the app. - apk_path = TEST_HELPER.testdata_path(apk_name) + apk_path = TestHelper.testdata_path(apk_name) adb.run(['uninstall', package_name]) adb.check_run(['install', '-t', apk_path]) # Without sleep, the activity may be killed by post install intent ACTION_PACKAGE_CHANGED. diff --git a/simpleperf/scripts/test/app_profiler_test.py b/simpleperf/scripts/test/app_profiler_test.py index 7b897ff4..ec3644b8 100644 --- a/simpleperf/scripts/test/app_profiler_test.py +++ b/simpleperf/scripts/test/app_profiler_test.py @@ -16,14 +16,15 @@ from app_profiler import NativeLibDownloader import shutil + from simpleperf_utils import str_to_bytes, bytes_to_str, remove -from . test_utils import TestBase, TEST_HELPER, INFERNO_SCRIPT +from . test_utils import TestBase, TestHelper, INFERNO_SCRIPT class TestNativeProfiling(TestBase): def setUp(self): super(TestNativeProfiling, self).setUp() - self.is_rooted_device = TEST_HELPER.adb.switch_to_root() + self.is_rooted_device = TestHelper.adb.switch_to_root() def test_profile_cmd(self): self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--disable_adb_root"]) @@ -40,7 +41,7 @@ class TestNativeProfiling(TestBase): def test_profile_pids(self): if not self.is_rooted_device: return - pid = int(TEST_HELPER.adb.check_run_and_return_output(['shell', 'pidof', 'system_server'])) + pid = int(TestHelper.adb.check_run_and_return_output(['shell', 'pidof', 'system_server'])) self.run_cmd(['app_profiler.py', '--pid', str(pid), '-r', '--duration 1']) self.run_cmd(['app_profiler.py', '--pid', str(pid), str(pid), '-r', '--duration 1']) self.run_cmd(['app_profiler.py', '--tid', str(pid), '-r', '--duration 1']) @@ -56,9 +57,9 @@ class TestNativeProfiling(TestBase): class TestNativeLibDownloader(TestBase): def setUp(self): super(TestNativeLibDownloader, self).setUp() - self.adb = TEST_HELPER.adb + self.adb = TestHelper.adb self.adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs']) - self.ndk_path = TEST_HELPER.ndk_path + self.ndk_path = TestHelper.ndk_path def tearDown(self): self.adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs']) @@ -72,7 +73,7 @@ class TestNativeLibDownloader(TestBase): def test_smoke(self): # Sync all native libs on device. downloader = NativeLibDownloader(self.ndk_path, 'arm64', self.adb) - downloader.collect_native_libs_on_host(TEST_HELPER.testdata_path( + downloader.collect_native_libs_on_host(TestHelper.testdata_path( 'SimpleperfExampleWithNative/app/build/intermediates/cmake/profiling')) self.assertEqual(len(downloader.host_build_id_map), 2) for entry in downloader.host_build_id_map.values(): @@ -120,7 +121,7 @@ class TestNativeLibDownloader(TestBase): def test_download_file_without_build_id(self): downloader = NativeLibDownloader(self.ndk_path, 'x86_64', self.adb) name = 'elf.so' - shutil.copyfile(TEST_HELPER.testdata_path('data/symfs_without_build_id/elf'), name) + shutil.copyfile(TestHelper.testdata_path('data/symfs_without_build_id/elf'), name) downloader.collect_native_libs_on_host('.') downloader.collect_native_libs_on_device() self.assertIn(name, downloader.no_build_id_file_map) diff --git a/simpleperf/scripts/test/app_test.py b/simpleperf/scripts/test/app_test.py index 5a9e5d16..c3a152bf 100644 --- a/simpleperf/scripts/test/app_test.py +++ b/simpleperf/scripts/test/app_test.py @@ -18,15 +18,16 @@ import os import re import shutil import subprocess + from simpleperf_utils import remove -from . test_utils import TestBase, TEST_HELPER, AdbHelper, TEST_LOGGER, INFERNO_SCRIPT +from . test_utils import TestBase, TestHelper, AdbHelper, INFERNO_SCRIPT class TestExampleBase(TestBase): @classmethod def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False): cls.adb = AdbHelper(enable_switch_to_root=adb_root) - cls.example_path = TEST_HELPER.testdata_path(example_name) + cls.example_path = TestHelper.testdata_path(example_name) if not os.path.isdir(cls.example_path): log_fatal("can't find " + cls.example_path) for root, _, files in os.walk(cls.example_path): @@ -46,8 +47,8 @@ class TestExampleBase(TestBase): cls.has_perf_data_for_report = False # On Android >= P (version 9), we can profile JITed and interpreted Java code. # So only compile Java code on Android <= O (version 8). - cls.use_compiled_java_code = TEST_HELPER.android_version <= 8 - cls.testcase_dir = TEST_HELPER.test_dir(cls.__name__) + cls.use_compiled_java_code = TestHelper.android_version <= 8 + cls.testcase_dir = TestHelper.get_test_dir(cls.__name__) @classmethod def tearDownClass(cls): @@ -57,7 +58,7 @@ class TestExampleBase(TestBase): def setUp(self): super(TestExampleBase, self).setUp() - if 'TraceOffCpu' in self.id() and not TEST_HELPER.is_trace_offcpu_supported(): + if 'TraceOffCpu' in self.id() and not TestHelper.is_trace_offcpu_supported(): self.skipTest('trace-offcpu is not supported on device') # Use testcase_dir to share a common perf.data for reporting. So we don't need to # generate it for each test. @@ -219,7 +220,7 @@ class TestExampleBase(TestBase): class TestRecordingRealApps(TestBase): def setUp(self): super(TestRecordingRealApps, self).setUp() - self.adb = TEST_HELPER.adb + self.adb = TestHelper.adb self.installed_packages = [] def tearDown(self): @@ -233,9 +234,8 @@ class TestRecordingRealApps(TestBase): self.installed_packages.append(package_name) def start_app(self, start_cmd): - print('start app %s' % start_cmd) subprocess.Popen(self.adb.adb_path + ' ' + start_cmd, shell=True, - stdout=TEST_LOGGER.log_fh, stderr=TEST_LOGGER.log_fh) + stdout=TestHelper.log_fh, stderr=TestHelper.log_fh) def record_data(self, package_name, record_arg): self.run_cmd(['app_profiler.py', '--app', package_name, '-r', record_arg]) @@ -245,20 +245,20 @@ class TestRecordingRealApps(TestBase): self.check_strings_in_file('report.txt', [symbol_name]) def test_recording_displaybitmaps(self): - self.install_apk(TEST_HELPER.testdata_path('DisplayBitmaps.apk'), + self.install_apk(TestHelper.testdata_path('DisplayBitmaps.apk'), 'com.example.android.displayingbitmaps') - self.install_apk(TEST_HELPER.testdata_path('DisplayBitmapsTest.apk'), + self.install_apk(TestHelper.testdata_path('DisplayBitmapsTest.apk'), 'com.example.android.displayingbitmaps.test') self.start_app('shell am instrument -w -r -e debug false -e class ' + 'com.example.android.displayingbitmaps.tests.GridViewTest ' + 'com.example.android.displayingbitmaps.test/' + 'androidx.test.runner.AndroidJUnitRunner') self.record_data('com.example.android.displayingbitmaps', '-e cpu-clock -g --duration 10') - if TEST_HELPER.android_version >= 9: + if TestHelper.android_version >= 9: self.check_symbol_in_record_file('androidx.test.espresso') def test_recording_endless_tunnel(self): - self.install_apk(TEST_HELPER.testdata_path( + self.install_apk(TestHelper.testdata_path( 'EndlessTunnel.apk'), 'com.google.sample.tunnel') self.start_app('shell am start -n com.google.sample.tunnel/android.app.NativeActivity -a ' + 'android.intent.action.MAIN -c android.intent.category.LAUNCHER') diff --git a/simpleperf/scripts/test/binary_cache_builder_test.py b/simpleperf/scripts/test/binary_cache_builder_test.py index d79a36a7..c17fbe49 100644 --- a/simpleperf/scripts/test/binary_cache_builder_test.py +++ b/simpleperf/scripts/test/binary_cache_builder_test.py @@ -17,25 +17,26 @@ import filecmp import os import shutil + from binary_cache_builder import BinaryCacheBuilder from simpleperf_utils import ReadElf, remove, find_tool_path -from . test_utils import TestBase, TEST_HELPER +from . test_utils import TestBase, TestHelper class TestBinaryCacheBuilder(TestBase): def test_copy_binaries_from_symfs_dirs(self): - readelf = ReadElf(TEST_HELPER.ndk_path) + readelf = ReadElf(TestHelper.ndk_path) strip = find_tool_path('strip', arch='arm') self.assertIsNotNone(strip) symfs_dir = os.path.join(self.test_dir, 'symfs_dir') remove(symfs_dir) os.mkdir(symfs_dir) filename = 'simpleperf_runtest_two_functions_arm' - origin_file = TEST_HELPER.testdata_path(filename) + origin_file = TestHelper.testdata_path(filename) source_file = os.path.join(symfs_dir, filename) target_file = os.path.join('binary_cache', filename) expected_build_id = readelf.get_build_id(origin_file) - binary_cache_builder = BinaryCacheBuilder(TEST_HELPER.ndk_path, False) + binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False) binary_cache_builder.binaries['simpleperf_runtest_two_functions_arm'] = expected_build_id # Copy binary if target file doesn't exist. @@ -55,9 +56,9 @@ class TestBinaryCacheBuilder(TestBase): self.assertTrue(filecmp.cmp(target_file, source_file)) def test_copy_elf_without_build_id_from_symfs_dir(self): - binary_cache_builder = BinaryCacheBuilder(TEST_HELPER.ndk_path, False) + binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False) binary_cache_builder.binaries['elf'] = '' - symfs_dir = TEST_HELPER.testdata_path('data/symfs_without_build_id') + symfs_dir = TestHelper.testdata_path('data/symfs_without_build_id') source_file = os.path.join(symfs_dir, 'elf') target_file = os.path.join('binary_cache', 'elf') binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir]) diff --git a/simpleperf/scripts/test/cpp_app_test.py b/simpleperf/scripts/test/cpp_app_test.py index 42cb9366..8565cb3a 100644 --- a/simpleperf/scripts/test/cpp_app_test.py +++ b/simpleperf/scripts/test/cpp_app_test.py @@ -15,9 +15,10 @@ # limitations under the License. import os + from simpleperf_utils import remove from . app_test import TestExampleBase -from . test_utils import INFERNO_SCRIPT, TEST_HELPER +from . test_utils import INFERNO_SCRIPT, TestHelper class TestExampleWithNative(TestExampleBase): @@ -157,7 +158,7 @@ class TestExampleWithNativeForce32Bit(TestExampleWithNative): cls.prepare("SimpleperfExampleWithNative", "com.example.simpleperf.simpleperfexamplewithnative", ".MainActivity", - abi=TEST_HELPER.get_32bit_abi()) + abi=TestHelper.get_32bit_abi()) class TestExampleWithNativeRootForce32Bit(TestExampleWithNativeRoot): @@ -166,7 +167,7 @@ class TestExampleWithNativeRootForce32Bit(TestExampleWithNativeRoot): cls.prepare("SimpleperfExampleWithNative", "com.example.simpleperf.simpleperfexamplewithnative", ".MainActivity", - abi=TEST_HELPER.get_32bit_abi(), + abi=TestHelper.get_32bit_abi(), adb_root=False) @@ -176,4 +177,4 @@ class TestExampleWithNativeTraceOffCpuForce32Bit(TestExampleWithNativeTraceOffCp cls.prepare("SimpleperfExampleWithNative", "com.example.simpleperf.simpleperfexamplewithnative", ".SleepActivity", - abi=TEST_HELPER.get_32bit_abi()) + abi=TestHelper.get_32bit_abi()) diff --git a/simpleperf/scripts/test/debug_unwind_reporter_test.py b/simpleperf/scripts/test/debug_unwind_reporter_test.py index 2a7c3ac6..0e381ac5 100644 --- a/simpleperf/scripts/test/debug_unwind_reporter_test.py +++ b/simpleperf/scripts/test/debug_unwind_reporter_test.py @@ -15,12 +15,13 @@ # limitations under the License. from typing import List -from . test_utils import TestBase, TEST_HELPER + +from . test_utils import TestBase, TestHelper class TestDebugUnwindReporter(TestBase): def run_reporter(self, options: List[str]) -> str: - report_file = TEST_HELPER.testdata_dir_p / 'debug_unwind_report.txt' + report_file = TestHelper.testdata_dir / 'debug_unwind_report.txt' return self.run_cmd(['debug_unwind_reporter.py', '-i', str(report_file)] + options, return_output=True) diff --git a/simpleperf/scripts/test/do_test.py b/simpleperf/scripts/test/do_test.py index 8cde3485..bbd978a1 100755 --- a/simpleperf/scripts/test/do_test.py +++ b/simpleperf/scripts/test/do_test.py @@ -14,35 +14,30 @@ # See the License for the specific language governing permissions and # limitations under the License. # -"""test.py: Tests for simpleperf python scripts. +"""Release test for simpleperf prebuilts. -These are smoke tests Using examples to run python scripts. -For each example, we go through the steps of running each python script. -Examples are collected from simpleperf/demo, which includes: - SimpleperfExamplePureJava - SimpleperfExampleWithNative - SimpleperfExampleOfKotlin - -Tested python scripts include: - app_profiler.py - report.py - annotate.py - report_sample.py - pprof_proto_generator.py - report_html.py - -Test using both `adb root` and `adb unroot`. +It includes below tests: +1. Test profiling Android apps on different Android versions (starting from Android N). +2. Test simpleperf python scripts on different Hosts (linux, darwin and windows) on x86_64. +3. Test using both devices and emulators. +4. Test using both `adb root` and `adb unroot`. """ + import argparse import fnmatch import inspect +import os +from pathlib import Path import re -from simpleperf_utils import extant_dir, log_exit, remove import sys +import time import types +from typing import List, Optional import unittest +from simpleperf_utils import extant_dir, log_exit, remove, ArgParseFormatter + from . api_profiler_test import * from . app_profiler_test import * from . app_test import * @@ -56,10 +51,22 @@ from . report_html_test import * from . report_lib_test import * from . run_simpleperf_on_device_test import * from . tools_test import * -from . test_utils import TEST_LOGGER, TEST_HELPER +from . test_utils import TestHelper -def get_all_tests(): +def get_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__, formatter_class=ArgParseFormatter) + parser.add_argument('--browser', action='store_true', help='open report html file in browser.') + parser.add_argument('--list-tests', action='store_true', help='List all tests.') + parser.add_argument('--ndk-path', type=extant_dir, help='Set the path of a ndk release') + parser.add_argument('-p', '--pattern', nargs='+', + help='Run tests matching the selected pattern.') + parser.add_argument('--test-from', help='Run tests following the selected test.') + parser.add_argument('--test-dir', default='test_dir', help='Directory to store test results') + return parser.parse_args() + + +def get_all_tests() -> List[str]: tests = [] for name, value in globals().items(): if isinstance(value, type) and issubclass(value, unittest.TestCase): @@ -70,59 +77,75 @@ def get_all_tests(): return sorted(tests) -def run_tests(tests): - TEST_HELPER.build_testdata() +def get_filtered_tests(test_from: Optional[str], test_pattern: Optional[List[str]]) -> List[str]: + tests = get_all_tests() + if test_from: + try: + tests = tests[tests.index(test_from):] + except ValueError: + log_exit("Can't find test %s" % test_from) + if test_pattern: + patterns = [re.compile(fnmatch.translate(x)) for x in test_pattern] + tests = [t for t in tests if any(pattern.match(t) for pattern in patterns)] + if not tests: + log_exit('No tests are matched.') + return tests + + +def build_testdata(testdata_dir: Path): + """ Collect testdata in testdata_dir. + In system/extras/simpleperf/scripts, testdata comes from: + <script_dir>/../testdata, <script_dir>/test/script_testdata, <script_dir>/../demo + In prebuilts/simpleperf, testdata comes from: + <script_dir>/test/testdata + """ + testdata_dir.mkdir() + + script_test_dir = Path(__file__).resolve().parent + script_dir = script_test_dir.parent + + source_dirs = [ + script_test_dir / 'script_testdata', + script_test_dir / 'testdata', + script_dir.parent / 'testdata', + script_dir.parent / 'demo', + ] + + for source_dir in source_dirs: + if not source_dir.is_dir(): + continue + for src_path in source_dir.iterdir(): + dest_path = testdata_dir / src_path.name + if dest_path.exists(): + continue + if src_path.is_file(): + shutil.copyfile(src_path, dest_path) + elif src_path.is_dir(): + shutil.copytree(src_path, dest_path) + + +def run_tests(tests: List[str]) -> bool: argv = [sys.argv[0]] + tests - test_runner = unittest.TextTestRunner(stream=TEST_LOGGER, verbosity=0) + test_runner = unittest.TextTestRunner(stream=TestHelper.log_fh, verbosity=0) test_program = unittest.main(argv=argv, testRunner=test_runner, exit=False, verbosity=0, module='test.do_test') - result = test_program.result.wasSuccessful() - remove(TEST_HELPER.testdata_dir) - return result + return test_program.result.wasSuccessful() -def main(): - parser = argparse.ArgumentParser(description='Test simpleperf scripts') - parser.add_argument('--list-tests', action='store_true', help='List all tests.') - parser.add_argument('--test-from', nargs=1, help='Run left tests from the selected test.') - parser.add_argument('--browser', action='store_true', help='pop report html file in browser.') - parser.add_argument('--progress-file', help='write test progress file') - parser.add_argument('--ndk-path', type=extant_dir, help='Set the path of a ndk release') - parser.add_argument('pattern', nargs='*', help='Run tests matching the selected pattern.') - args = parser.parse_args() - tests = get_all_tests() +def main() -> bool: + args = get_args() if args.list_tests: - print('\n'.join(tests)) + print('\n'.join(get_all_tests())) return True - if args.test_from: - start_pos = 0 - while start_pos < len(tests) and tests[start_pos] != args.test_from[0]: - start_pos += 1 - if start_pos == len(tests): - log_exit("Can't find test %s" % args.test_from[0]) - tests = tests[start_pos:] - if args.pattern: - patterns = [re.compile(fnmatch.translate(x)) for x in args.pattern] - tests = [t for t in tests if any(pattern.match(t) for pattern in patterns)] - if not tests: - log_exit('No tests are matched.') - - if TEST_HELPER.android_version < 7: - print("Skip tests on Android version < N.", file=TEST_LOGGER) - return False - - TEST_HELPER.ndk_path = args.ndk_path - - remove(TEST_HELPER.test_base_dir) - if not args.browser: - TEST_HELPER.browser_option = ['--no_browser'] + tests = get_filtered_tests(args.test_from, args.pattern) - if args.progress_file: - TEST_HELPER.progress_fh = open(args.progress_file, 'w') + test_dir = Path(args.test_dir).resolve() + remove(test_dir) + test_dir.mkdir(parents=True) + # Switch to the test dir. + os.chdir(test_dir) + build_testdata(Path('testdata')) - result = run_tests(tests) - if not result: - print('Tests failed, see %s for details.' % TEST_LOGGER.log_file, file=TEST_LOGGER) - TEST_HELPER.write_progress('Test end') - return result + TestHelper.init('.', 'testdata', args.browser, args.ndk_path, None, None) + return run_tests(tests) diff --git a/simpleperf/scripts/test/java_app_test.py b/simpleperf/scripts/test/java_app_test.py index 306e4aa2..f3c89136 100644 --- a/simpleperf/scripts/test/java_app_test.py +++ b/simpleperf/scripts/test/java_app_test.py @@ -19,9 +19,10 @@ import signal import subprocess import sys import time + from simpleperf_utils import is_windows, remove from . app_test import TestExampleBase -from . test_utils import TEST_HELPER, INFERNO_SCRIPT +from . test_utils import TestHelper, INFERNO_SCRIPT class TestExamplePureJava(TestExampleBase): @@ -56,7 +57,7 @@ class TestExamplePureJava(TestExampleBase): return self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) time.sleep(1) - args = [sys.executable, TEST_HELPER.script_path("app_profiler.py"), + args = [sys.executable, TestHelper.script_path("app_profiler.py"), "--app", self.package_name, "-r", "--duration 10000", "--disable_adb_root"] subproc = subprocess.Popen(args) time.sleep(3) @@ -70,7 +71,7 @@ class TestExamplePureJava(TestExampleBase): self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) time.sleep(1) subproc = subprocess.Popen( - [sys.executable, TEST_HELPER.script_path('app_profiler.py'), + [sys.executable, TestHelper.script_path('app_profiler.py'), '--app', self.package_name, '-r', '--duration 10000', '--disable_adb_root']) time.sleep(3) self.adb.check_run(['shell', 'am', 'force-stop', self.package_name]) @@ -160,7 +161,7 @@ class TestExamplePureJava(TestExampleBase): self.adb.check_run(['kill-server']) time.sleep(3) # Start adb process outside self.test_dir. Because it will be removed after testing. - os.chdir(self.saved_cwd) + os.chdir(self.test_dir.parent) self.adb.check_run(['devices']) os.chdir(self.test_dir) self.run_cmd(['run_simpleperf_without_usb_connection.py', 'stop']) diff --git a/simpleperf/scripts/test/kotlin_app_test.py b/simpleperf/scripts/test/kotlin_app_test.py index f3176567..9055453e 100644 --- a/simpleperf/scripts/test/kotlin_app_test.py +++ b/simpleperf/scripts/test/kotlin_app_test.py @@ -15,6 +15,7 @@ # limitations under the License. import os + from simpleperf_utils import remove from . app_test import TestExampleBase from . test_utils import INFERNO_SCRIPT diff --git a/simpleperf/scripts/test/pprof_proto_generator_test.py b/simpleperf/scripts/test/pprof_proto_generator_test.py index 1ea963c5..2b194325 100644 --- a/simpleperf/scripts/test/pprof_proto_generator_test.py +++ b/simpleperf/scripts/test/pprof_proto_generator_test.py @@ -15,19 +15,20 @@ # limitations under the License. import google.protobuf + from pprof_proto_generator import load_pprof_profile -from . test_utils import TestBase, TEST_HELPER +from . test_utils import TestBase, TestHelper class TestPprofProtoGenerator(TestBase): def run_generator(self, options=None, testdata_file='perf_with_interpreter_frames.data'): - testdata_path = TEST_HELPER.testdata_path(testdata_file) + testdata_path = TestHelper.testdata_path(testdata_file) options = options or [] self.run_cmd(['pprof_proto_generator.py', '-i', testdata_path] + options) return self.run_cmd(['pprof_proto_generator.py', '--show'], return_output=True) def generate_profile(self, options, testdata_files): - testdata_paths = [TEST_HELPER.testdata_path(f) for f in testdata_files] + testdata_paths = [TestHelper.testdata_path(f) for f in testdata_files] options = options or [] self.run_cmd(['pprof_proto_generator.py', '-i'] + testdata_paths + options) return load_pprof_profile('pprof.profile') diff --git a/simpleperf/scripts/test/report_html_test.py b/simpleperf/scripts/test/report_html_test.py index 41b2ac72..824d4ecf 100644 --- a/simpleperf/scripts/test/report_html_test.py +++ b/simpleperf/scripts/test/report_html_test.py @@ -16,21 +16,22 @@ import collections import json -from . test_utils import TestBase, TEST_HELPER + +from . test_utils import TestBase, TestHelper class TestReportHtml(TestBase): def test_long_callchain(self): self.run_cmd(['report_html.py', '-i', - TEST_HELPER.testdata_path('perf_with_long_callchain.data')]) + TestHelper.testdata_path('perf_with_long_callchain.data')]) def test_aggregated_by_thread_name(self): # Calculate event_count for each thread name before aggregation. event_count_for_thread_name = collections.defaultdict(lambda: 0) # use "--min_func_percent 0" to avoid cutting any thread. self.run_cmd(['report_html.py', '--min_func_percent', '0', '-i', - TEST_HELPER.testdata_path('aggregatable_perf1.data'), - TEST_HELPER.testdata_path('aggregatable_perf2.data')]) + TestHelper.testdata_path('aggregatable_perf1.data'), + TestHelper.testdata_path('aggregatable_perf2.data')]) record_data = self._load_record_data_in_html('report.html') event = record_data['sampleInfo'][0] for process in event['processes']: @@ -41,8 +42,8 @@ class TestReportHtml(TestBase): # Check event count for each thread after aggregation. self.run_cmd(['report_html.py', '--aggregate-by-thread-name', '--min_func_percent', '0', '-i', - TEST_HELPER.testdata_path('aggregatable_perf1.data'), - TEST_HELPER.testdata_path('aggregatable_perf2.data')]) + TestHelper.testdata_path('aggregatable_perf1.data'), + TestHelper.testdata_path('aggregatable_perf2.data')]) record_data = self._load_record_data_in_html('report.html') event = record_data['sampleInfo'][0] hit_count = 0 @@ -56,7 +57,7 @@ class TestReportHtml(TestBase): def test_no_empty_process(self): """ Test not showing a process having no threads. """ - perf_data = TEST_HELPER.testdata_path('two_process_perf.data') + perf_data = TestHelper.testdata_path('two_process_perf.data') self.run_cmd(['report_html.py', '-i', perf_data]) record_data = self._load_record_data_in_html('report.html') processes = record_data['sampleInfo'][0]['processes'] diff --git a/simpleperf/scripts/test/report_lib_test.py b/simpleperf/scripts/test/report_lib_test.py index 36732203..809fc75e 100644 --- a/simpleperf/scripts/test/report_lib_test.py +++ b/simpleperf/scripts/test/report_lib_test.py @@ -15,14 +15,14 @@ # limitations under the License. from simpleperf_report_lib import ReportLib -from . test_utils import TestBase, TEST_HELPER +from . test_utils import TestBase, TestHelper class TestReportLib(TestBase): def setUp(self): super(TestReportLib, self).setUp() self.report_lib = ReportLib() - self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_symbols.data')) + self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_symbols.data')) def tearDown(self): self.report_lib.Close() @@ -60,7 +60,7 @@ class TestReportLib(TestBase): self.assertTrue(found_sample) def test_meta_info(self): - self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_trace_offcpu.data')) + self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu.data')) meta_info = self.report_lib.MetaInfo() self.assertTrue("simpleperf_version" in meta_info) self.assertEqual(meta_info["system_wide_collection"], "false") @@ -69,7 +69,7 @@ class TestReportLib(TestBase): self.assertTrue("product_props" in meta_info) def test_event_name_from_meta_info(self): - self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_tracepoint_event.data')) + self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_tracepoint_event.data')) event_names = set() while self.report_lib.GetNextSample(): event_names.add(self.report_lib.GetEventOfCurrentSample().name) @@ -77,13 +77,13 @@ class TestReportLib(TestBase): self.assertTrue('cpu-cycles' in event_names) def test_record_cmd(self): - self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_trace_offcpu.data')) + self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu.data')) self.assertEqual(self.report_lib.GetRecordCmd(), "/data/local/tmp/simpleperf record --trace-offcpu --duration 2 -g " + "./simpleperf_runtest_run_and_sleep64") def test_offcpu(self): - self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_trace_offcpu.data')) + self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu.data')) total_period = 0 sleep_function_period = 0 sleep_function_name = "SleepFunction(unsigned long long)" @@ -104,7 +104,7 @@ class TestReportLib(TestBase): def test_show_art_frames(self): def has_art_frame(report_lib): - report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_interpreter_frames.data')) + report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_interpreter_frames.data')) result = False while report_lib.GetNextSample(): callchain = report_lib.GetCallChainOfCurrentSample() @@ -127,7 +127,7 @@ class TestReportLib(TestBase): def test_merge_java_methods(self): def parse_dso_names(report_lib): dso_names = set() - report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_interpreter_frames.data')) + report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_interpreter_frames.data')) while report_lib.GetNextSample(): dso_names.add(report_lib.GetSymbolOfCurrentSample().dso_name) callchain = report_lib.GetCallChainOfCurrentSample() @@ -151,7 +151,7 @@ class TestReportLib(TestBase): def test_jited_java_methods(self): report_lib = ReportLib() - report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_jit_symbol.data')) + report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_jit_symbol.data')) has_jit_cache = False while report_lib.GetNextSample(): if report_lib.GetSymbolOfCurrentSample().dso_name == '[JIT app cache]': @@ -164,7 +164,7 @@ class TestReportLib(TestBase): self.assertTrue(has_jit_cache) def test_tracing_data(self): - self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_tracepoint_event.data')) + self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_tracepoint_event.data')) has_tracing_data = False while self.report_lib.GetNextSample(): event = self.report_lib.GetEventOfCurrentSample() @@ -180,7 +180,7 @@ class TestReportLib(TestBase): self.assertTrue(has_tracing_data) def test_dynamic_field_in_tracing_data(self): - self.report_lib.SetRecordFile(TEST_HELPER.testdata_path( + self.report_lib.SetRecordFile(TestHelper.testdata_path( 'perf_with_tracepoint_event_dynamic_field.data')) has_dynamic_field = False while self.report_lib.GetNextSample(): diff --git a/simpleperf/scripts/test/test.py b/simpleperf/scripts/test/test.py index 6c5a8129..eac3d29d 100755 --- a/simpleperf/scripts/test/test.py +++ b/simpleperf/scripts/test/test.py @@ -22,4 +22,5 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1])) import test # fmt: on -sys.exit(0 if test.main() else 1) +if __name__ == '__main__': + sys.exit(0 if test.main() else 1) diff --git a/simpleperf/scripts/test/test_utils.py b/simpleperf/scripts/test/test_utils.py index e7865ec0..7041a61c 100644 --- a/simpleperf/scripts/test/test_utils.py +++ b/simpleperf/scripts/test/test_utils.py @@ -18,142 +18,112 @@ """ import logging +from multiprocessing.connection import Connection import os from pathlib import Path import shutil import sys -from simpleperf_utils import remove, get_script_dir, AdbHelper, is_windows, bytes_to_str import subprocess import time -from typing import List +from typing import List, Optional import unittest -INFERNO_SCRIPT = str(Path(__file__).parents[1] / ('inferno.bat' if is_windows() else 'inferno.sh')) +from simpleperf_utils import remove, get_script_dir, AdbHelper, is_windows, bytes_to_str +INFERNO_SCRIPT = str(Path(__file__).parents[1] / ('inferno.bat' if is_windows() else 'inferno.sh')) -class TestLogger: - """ Write test progress in sys.stderr and keep verbose log in log file. """ - def __init__(self): - self.log_file = 'test.log' - remove(self.log_file) - # Logs can come from multiple processes. So use append mode to avoid overwrite. - self.log_fh = open(self.log_file, 'a') - logging.basicConfig(filename=self.log_file) +class TestHelper: + """ Keep global test options. """ - def writeln(self, s): - return self.write(s + '\n') + @classmethod + def init( + cls, test_dir: str, testdata_dir: str, use_browser: bool, ndk_path: Optional[str], + device_serial_number: Optional[str], + progress_conn: Optional[Connection]): + """ + When device_serial_number is None, no Android device is used. + When device_serial_number is '', use the default Android device. + When device_serial_number is not empty, select Android device by serial number. + """ + cls.script_dir = Path(__file__).resolve().parents[1] + cls.test_base_dir = Path(test_dir).resolve() + cls.test_base_dir.mkdir(parents=True, exist_ok=True) + cls.testdata_dir = Path(testdata_dir).resolve() + cls.browser_option = [] if use_browser else ['--no_browser'] + cls.ndk_path = ndk_path + cls.progress_conn = progress_conn - def write(self, s): - sys.stderr.write(s) - self.log_fh.write(s) + # Logs can come from multiple processes. So use append mode to avoid overwrite. + cls.log_fh = open(cls.test_base_dir / 'test.log', 'a') + logging.getLogger().handlers.clear() + logging.getLogger().addHandler(logging.StreamHandler(cls.log_fh)) + os.close(sys.stderr.fileno()) + os.dup2(cls.log_fh.fileno(), sys.stderr.fileno()) + + if device_serial_number is not None: + if device_serial_number: + os.environ['ANDROID_SERIAL'] = device_serial_number + cls.adb = AdbHelper(enable_switch_to_root=True) + cls.android_version = cls.adb.get_android_version() + cls.device_features = None + + @classmethod + def log(cls, s: str): + cls.log_fh.write(s + '\n') # Child processes can also write to log file, so flush it immediately to keep the order. - self.flush() + cls.log_fh.flush() - def flush(self): - self.log_fh.flush() - - -TEST_LOGGER = TestLogger() - - -class TestHelper: - """ Keep global test info. """ - - def __init__(self): - #self.script_dir = os.path.abspath(get_script_dir()) - self.script_test_dir = Path(__file__).resolve().parent - self.script_dir = self.script_test_dir.parent - self.cur_dir = os.getcwd() - self.testdata_dir = os.path.join(self.cur_dir, 'testdata') - self.testdata_dir_p = Path(self.testdata_dir) - self.test_base_dir = os.path.join(self.cur_dir, 'test_results') - self.adb = AdbHelper(enable_switch_to_root=True) - self.android_version = self.adb.get_android_version() - self.device_features = None - self.browser_option = [] - self.progress_fh = None - self.ndk_path = None - - def testdata_path(self, testdata_name): + @classmethod + def testdata_path(cls, testdata_name: str) -> str: """ Return the path of a test data. """ - return os.path.join(self.testdata_dir, testdata_name.replace('/', os.sep)) + return str(cls.testdata_dir / testdata_name) - def test_dir(self, test_name): + @classmethod + def get_test_dir(cls, test_name: str) -> Path: """ Return the dir to run a test. """ - return os.path.join(self.test_base_dir, test_name) + return cls.test_base_dir / test_name - def script_path(self, script_name): + @classmethod + def script_path(cls, script_name: str) -> str: """ Return the dir of python scripts. """ - return os.path.join(self.script_dir, script_name) + return str(cls.script_dir / script_name) - def get_device_features(self): - if self.device_features is None: - args = [sys.executable, self.script_path( + @classmethod + def get_device_features(cls): + if cls.device_features is None: + args = [sys.executable, cls.script_path( 'run_simpleperf_on_device.py'), 'list', '--show-features'] - output = subprocess.check_output(args, stderr=TEST_LOGGER.log_fh) + output = subprocess.check_output(args, stderr=TestHelper.log_fh) output = bytes_to_str(output) - self.device_features = output.split() - return self.device_features - - def is_trace_offcpu_supported(self): - return 'trace-offcpu' in self.get_device_features() - - def build_testdata(self): - """ Collect testdata in self.testdata_dir. - In system/extras/simpleperf/scripts, testdata comes from: - <script_dir>/../testdata, <script_dir>/test/script_testdata, <script_dir>/../demo - In prebuilts/simpleperf, testdata comes from: - <script_dir>/testdata - """ - if os.path.isdir(self.testdata_dir): - return # already built - os.makedirs(self.testdata_dir) - - source_dirs = [ - self.script_test_dir / 'script_testdata', - self.script_test_dir / 'testdata', - self.script_dir.parent / 'testdata', - self.script_dir.parent / 'demo', - self.script_dir / 'testdata', - ] - - for source_dir in source_dirs: - if not source_dir.is_dir(): - continue - for src_path in source_dir.iterdir(): - dest_path = Path(self.testdata_dir) / src_path.name - if dest_path.exists(): - continue - if src_path.is_file(): - shutil.copyfile(src_path, dest_path) - elif src_path.is_dir(): - shutil.copytree(src_path, dest_path) - - def get_32bit_abi(self): - return self.adb.get_property('ro.product.cpu.abilist32').strip().split(',')[0] - - def write_progress(self, progress): - if self.progress_fh: - self.progress_fh.write(progress + '\n') - self.progress_fh.flush() - - -TEST_HELPER = TestHelper() + cls.device_features = output.split() + return cls.device_features + + @classmethod + def is_trace_offcpu_supported(cls): + return 'trace-offcpu' in cls.get_device_features() + + @classmethod + def get_32bit_abi(cls): + return cls.adb.get_property('ro.product.cpu.abilist32').strip().split(',')[0] + + @classmethod + def write_progress(cls, progress: str): + if cls.progress_conn: + cls.progress_conn.send(progress) class TestBase(unittest.TestCase): def setUp(self): """ Run each test in a separate dir. """ - self.test_dir = TEST_HELPER.test_dir('%s.%s' % ( - self.__class__.__name__, self._testMethodName)) - os.makedirs(self.test_dir) - self.saved_cwd = os.getcwd() + self.test_dir = TestHelper.get_test_dir( + '%s.%s' % (self.__class__.__name__, self._testMethodName)) + self.test_dir.mkdir() os.chdir(self.test_dir) - TEST_LOGGER.writeln('begin test %s.%s' % (self.__class__.__name__, self._testMethodName)) - self.start_time = time.time() + TestHelper.log('begin test %s.%s' % (self.__class__.__name__, self._testMethodName)) def run(self, result=None): + start_time = time.time() ret = super(TestBase, self).run(result) if result.errors and result.errors[-1][0] == self: status = 'FAILED' @@ -164,30 +134,29 @@ class TestBase(unittest.TestCase): else: status = 'OK' - time_taken = time.time() - self.start_time - TEST_LOGGER.writeln( + time_taken = time.time() - start_time + TestHelper.log( 'end test %s.%s %s (%.3fs)' % (self.__class__.__name__, self._testMethodName, status, time_taken)) if status != 'OK': - TEST_LOGGER.writeln(err_info) + TestHelper.log(err_info) # Remove test data for passed tests to save space. - os.chdir(self.saved_cwd) if status == 'OK': - shutil.rmtree(self.test_dir) - TEST_HELPER.write_progress( + remove(self.test_dir) + TestHelper.write_progress( '%s.%s %s' % (self.__class__.__name__, self._testMethodName, status)) return ret def run_cmd(self, args: List[str], return_output=False, drop_output=True) -> str: if args[0] == 'report_html.py' or args[0] == INFERNO_SCRIPT: - args += TEST_HELPER.browser_option - if TEST_HELPER.ndk_path: + args += TestHelper.browser_option + if TestHelper.ndk_path: if args[0] in ['app_profiler.py', 'binary_cache_builder.py', 'pprof_proto_generator.py', 'report_html.py']: - args += ['--ndk_path', TEST_HELPER.ndk_path] + args += ['--ndk_path', TestHelper.ndk_path] if args[0].endswith('.py'): - args = [sys.executable, TEST_HELPER.script_path(args[0])] + args[1:] + args = [sys.executable, TestHelper.script_path(args[0])] + args[1:] use_shell = args[0].endswith('.bat') try: if return_output: @@ -199,7 +168,7 @@ class TestBase(unittest.TestCase): stdout_fd = None subproc = subprocess.Popen(args, stdout=stdout_fd, - stderr=TEST_LOGGER.log_fh, shell=use_shell) + stderr=TestHelper.log_fh, shell=use_shell) stdout_data, _ = subproc.communicate() output_data = bytes_to_str(stdout_data) returncode = subproc.returncode diff --git a/simpleperf/scripts/test/tools_test.py b/simpleperf/scripts/test/tools_test.py index bd093b29..c70c6dc4 100644 --- a/simpleperf/scripts/test/tools_test.py +++ b/simpleperf/scripts/test/tools_test.py @@ -15,9 +15,10 @@ # limitations under the License. import os + from simpleperf_utils import (is_elf_file, Addr2Nearestline, Objdump, ReadElf, SourceFileSearcher, is_windows, remove) -from . test_utils import TestBase, TEST_HELPER +from . test_utils import TestBase, TestHelper class TestTools(TestBase): @@ -26,7 +27,7 @@ class TestTools(TestBase): self.run_addr2nearestline_test(False) def run_addr2nearestline_test(self, with_function_name): - binary_cache_path = TEST_HELPER.testdata_dir + binary_cache_path = TestHelper.testdata_dir test_map = { '/simpleperf_runtest_two_functions_arm64': [ { @@ -95,7 +96,7 @@ class TestTools(TestBase): } ], } - addr2line = Addr2Nearestline(TEST_HELPER.ndk_path, binary_cache_path, with_function_name) + addr2line = Addr2Nearestline(TestHelper.ndk_path, binary_cache_path, with_function_name) for dso_path in test_map: test_addrs = test_map[dso_path] for test_addr in test_addrs: @@ -136,7 +137,7 @@ class TestTools(TestBase): (dso_path, test_addr['addr'], expected_source, actual_source)) def test_objdump(self): - binary_cache_path = TEST_HELPER.testdata_dir + binary_cache_path = TestHelper.testdata_dir test_map = { '/simpleperf_runtest_two_functions_arm64': { 'start_addr': 0x668, @@ -175,7 +176,7 @@ class TestTools(TestBase): ], }, } - objdump = Objdump(TEST_HELPER.ndk_path, binary_cache_path) + objdump = Objdump(TestHelper.ndk_path, binary_cache_path) for dso_path in test_map: dso = test_map[dso_path] dso_info = objdump.get_dso_info(dso_path) @@ -222,10 +223,10 @@ class TestTools(TestBase): 'arch': 'x86', } } - readelf = ReadElf(TEST_HELPER.ndk_path) + readelf = ReadElf(TestHelper.ndk_path) for dso_path in test_map: dso_info = test_map[dso_path] - path = os.path.join(TEST_HELPER.testdata_dir, dso_path) + path = os.path.join(TestHelper.testdata_dir, dso_path) self.assertEqual(dso_info['arch'], readelf.get_arch(path)) if 'build_id' in dso_info: self.assertEqual(dso_info['build_id'], readelf.get_build_id(path), dso_path) @@ -237,11 +238,11 @@ class TestTools(TestBase): def test_source_file_searcher(self): searcher = SourceFileSearcher( - [TEST_HELPER.testdata_path('SimpleperfExampleWithNative'), - TEST_HELPER.testdata_path('SimpleperfExampleOfKotlin')]) + [TestHelper.testdata_path('SimpleperfExampleWithNative'), + TestHelper.testdata_path('SimpleperfExampleOfKotlin')]) def format_path(path): - return os.path.join(TEST_HELPER.testdata_dir, path.replace('/', os.sep)) + return os.path.join(TestHelper.testdata_dir, path.replace('/', os.sep)) # Find a C++ file with pure file name. self.assertEqual( format_path('SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'), @@ -262,7 +263,7 @@ class TestTools(TestBase): searcher.get_real_path('MainActivity.kt')) def test_is_elf_file(self): - self.assertTrue(is_elf_file(TEST_HELPER.testdata_path( + self.assertTrue(is_elf_file(TestHelper.testdata_path( 'simpleperf_runtest_two_functions_arm'))) with open('not_elf', 'wb') as fh: fh.write(b'\x90123') |