Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/clusterfuzz/_internal/system/process_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would use exit reason low memory. I'd consider that a bit more consistent

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently when memory on the device is low, CF performs a reset on the device and then continues the execution without marking the test case or the bad build check as a crash, returning a non 0 exit reason here would change that, so i don't think this one is feasable.

# 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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Loading