diff --git a/src/clusterfuzz/_internal/system/process_handler.py b/src/clusterfuzz/_internal/system/process_handler.py index 27f697e6fbd..e2b62e05c70 100644 --- a/src/clusterfuzz/_internal/system/process_handler.py +++ b/src/clusterfuzz/_internal/system/process_handler.py @@ -174,6 +174,7 @@ def run_process(cmdline, if is_android: # Clear the log upfront. android.logger.clear_log() + initial_uptime = android.adb.time_since_last_reboot() # Run the app. adb_output = android.adb.run_command( @@ -253,15 +254,26 @@ def run_process(cmdline, # waits for device to be online. time.sleep(ANDROID_CRASH_LOGCAT_WAIT_TIME) output = android.logger.log_output() + app_package = android.app.get_package_name() + process_pid = android.util.get_latest_pid_for_package(app_package) + exit_info = android.util.get_exit_info_for_pid(app_package, process_pid) - if android.constants.LOW_MEMORY_REGEX.search(output): + if android.util.activity_crashed(exit_info): + logs.warning(f'Activity Crashed with: {exit_info}') + return_code = exit_info.reason + + elif android.constants.LOW_MEMORY_REGEX.search(output): # If the device is low on memory, we should force reboot and bail out to # prevent device from getting in a frozen state. logs.info('Device is low on memory, rebooting.', output=output) android.adb.hard_reset() android.adb.wait_for_device() - elif android.adb.time_since_last_reboot() < time.time() - start_time: + elif android.adb.time_since_last_reboot() < initial_uptime: + logs.info( + 'Device rebooted mid-run', + output=f'initial uptime: {initial_uptime}, ' + f'current uptime: {android.adb.time_since_last_reboot()}') # Check if a reboot has happened, if yes, append log output before reboot # and kernel logs content to output. log_before_last_reboot = android.logger.log_output_before_last_reboot() diff --git a/src/clusterfuzz/_internal/tests/core/system/process_handler_test.py b/src/clusterfuzz/_internal/tests/core/system/process_handler_test.py index a4712cb75ca..7feb8bf9a1d 100644 --- a/src/clusterfuzz/_internal/tests/core/system/process_handler_test.py +++ b/src/clusterfuzz/_internal/tests/core/system/process_handler_test.py @@ -16,6 +16,8 @@ import unittest from unittest import mock +from clusterfuzz._internal.platforms.android import constants +from clusterfuzz._internal.platforms.android import util from clusterfuzz._internal.system import process_handler from clusterfuzz._internal.tests.test_libs import helpers as test_helpers @@ -149,3 +151,58 @@ def test_process_1_no_terminate_with_wrong_case(self): def test_process_1_no_kill_with_wrong_case(self): process_handler.terminate_processes_matching_cmd_line('/a/b/C', kill=True) self.assertEqual(0, self.mock.terminate_process.call_count) + + +class RunProcessAndroidTest(unittest.TestCase): + """Tests run_process on Android platform.""" + + def setUp(self): + test_helpers.patch_environ(self) + test_helpers.patch(self, [ + 'clusterfuzz._internal.system.environment.platform', + 'clusterfuzz._internal.platforms.android.logger.clear_log', + 'clusterfuzz._internal.platforms.android.logger.log_output', + 'clusterfuzz._internal.platforms.android.adb.time_since_last_reboot', + 'clusterfuzz._internal.platforms.android.adb.run_command', + 'clusterfuzz._internal.platforms.android.adb.get_ps_output', + 'clusterfuzz._internal.platforms.android.app.get_package_name', + 'clusterfuzz._internal.platforms.android.app.stop', + 'clusterfuzz._internal.platforms.android.util.get_latest_pid_for_package', + 'clusterfuzz._internal.platforms.android.util.get_exit_info_for_pid', + 'clusterfuzz._internal.platforms.android.util.activity_crashed', + 'time.sleep', + ]) + + self.mock.platform.return_value = 'ANDROID' + self.mock.get_package_name.return_value = 'com.example.app' + self.mock.time_since_last_reboot.return_value = 100.0 + self.mock.log_output.return_value = '' + self.mock.run_command.return_value = '' + self.mock.get_ps_output.return_value = '' + + def test_run_process_android_activity_crashed(self): + """Checks that run_process sets return_code from exit_info.reason and logs warning when an Android activity crash is detected.""" + exit_info = util.ProcessExitInfo( + reason=constants.ExitReason.CRASH_NATIVE, + reason_name='APP CRASH(NATIVE)', + subreason=0, + subreason_name='', + status=constants.ExitStatus.SIGSEGV) + self.mock.get_latest_pid_for_package.return_value = 1234 + self.mock.get_exit_info_for_pid.return_value = exit_info + self.mock.activity_crashed.return_value = True + + return_code, _, _ = process_handler.run_process( + 'am start -n com.example.app/.MainActivity') + self.mock.activity_crashed.assert_called_once_with(exit_info) + self.assertEqual(return_code, 5) + + def test_run_process_android_no_crash(self): + """Checks that run_process returns 0 return_code when Android activity has not crashed.""" + self.mock.get_latest_pid_for_package.return_value = 1234 + self.mock.get_exit_info_for_pid.return_value = None + self.mock.activity_crashed.return_value = False + + return_code, _, _ = process_handler.run_process( + 'am start -n com.example.app/.MainActivity') + self.assertEqual(return_code, 0)