Skip to content
Merged
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
7 changes: 7 additions & 0 deletions android_env/components/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ class CheckInstallError(StepCommandError):
ERROR_CODE = 9


class CheckDumpsysError(StepCommandError):
"""Raised when the check_dumpsys success check is not met."""

ERROR_CODE = 10


def from_code(code: int, msg: str = '') -> AndroidEnvError | None:
"""Returns an AndroidEnvError instance from the given arguments."""

Expand All @@ -92,6 +98,7 @@ def from_code(code: int, msg: str = '') -> AndroidEnvError | None:
7: StepCommandError,
8: WaitForAppScreenError,
9: CheckInstallError,
10: CheckDumpsysError,
}

if code in code_to_error:
Expand Down
49 changes: 49 additions & 0 deletions android_env/components/setup_step_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def __init__(self, adb_call_parser: adb_call_parser_lib.AdbCallParser):
'error_count_adb_request': 0,
'error_count_wait_for_app_screen': 0,
'error_count_check_install': 0,
'error_count_check_dumpsys': 0,
'error_count_wait_for_message': 0,
'total_time_waiting_for_app_screen': 0.0,
}
Expand Down Expand Up @@ -107,6 +108,16 @@ def _process_step_command(self, step_cmd: task_pb2.SetupStep) -> None:
success_condition.check_install.package_name,
num_tries, max_retries)

except errors.CheckDumpsysError as error:
latest_error = error
self._stats['error_count_check_dumpsys'] += 1
logging.exception(
'Dumpsys check for service [%r] failed. Try %d of %d.',
success_condition.check_dumpsys.service,
num_tries,
max_retries,
)

raise errors.StepCommandError(
f'Step failed: [{step_cmd}]') from latest_error

Expand Down Expand Up @@ -155,6 +166,8 @@ def _check_success(
self._stats['total_time_waiting_for_app_screen'] += wait_time
case 'check_install':
self._check_install(success_condition.check_install)
case 'check_dumpsys':
self._check_dumpsys(success_condition.check_dumpsys)
case _:
raise NotImplementedError(f'No success check called [{success_check}].')

Expand All @@ -180,3 +193,39 @@ def _check_install(self, check_install: task_pb2.CheckInstall) -> None:

logging.error('Package not found.')
raise errors.CheckInstallError()

def _check_dumpsys(self, check_dumpsys: task_pb2.CheckDumpsys) -> None:
"""Checks that the dumpsys output contains the expected string."""

service = check_dumpsys.service
expected_string = check_dumpsys.expected_string
logging.info(
'Checking dumpsys output for service [%r] matching [%r]',
service,
expected_string,
)

request = adb_pb2.AdbRequest(
dumpsys=adb_pb2.AdbRequest.DumpsysRequest(service=service)
)

start_time = time.time()
while time.time() - start_time < check_dumpsys.timeout_sec:
response = self._adb_call_parser.parse(request)
if response.status == adb_pb2.AdbResponse.Status.OK:
output = response.dumpsys.output
if isinstance(output, bytes):
output = output.decode('utf-8', errors='replace')
if expected_string in output:
logging.info(
'Done confirming dumpsys output for service [%r].', service
)
return
time.sleep(0.1)

logging.error(
'Expected string [%r] not found in dumpsys output for service [%r].',
expected_string,
service,
)
raise errors.CheckDumpsysError()
57 changes: 57 additions & 0 deletions android_env/components/setup_step_interpreter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

"""Tests for android_env.components.setup_step_interpreter."""

import time
from unittest import mock

from absl.testing import absltest
Expand Down Expand Up @@ -336,6 +337,62 @@ def test_retry_step(self):
# We expect the check to fail once and succeed on the second pass.
self.assertEqual(self._parser.parse.call_count, 2)

def test_check_dumpsys_success(self):
self._parser.parse.return_value = adb_pb2.AdbResponse(
status=adb_pb2.AdbResponse.Status.OK,
dumpsys=adb_pb2.AdbResponse.DumpsysResponse(
output=b'Service accessibility: InputService is running'
),
)
interpreter = setup_step_interpreter.SetupStepInterpreter(
adb_call_parser=self._parser
)
interpreter.interpret([
_to_proto(
task_pb2.SetupStep,
"""
success_condition: {
check_dumpsys: {
service: "accessibility"
expected_string: "InputService"
timeout_sec: 0.0001
}
}""",
)
])
self._parser.parse.assert_called_once_with(
adb_pb2.AdbRequest(
dumpsys=adb_pb2.AdbRequest.DumpsysRequest(service='accessibility')
)
)

@mock.patch.object(time, 'sleep', autospec=True)
def test_check_dumpsys_failure(self, unused_mock_sleep):
self._parser.parse.return_value = adb_pb2.AdbResponse(
status=adb_pb2.AdbResponse.Status.OK,
dumpsys=adb_pb2.AdbResponse.DumpsysResponse(
output=b'Service accessibility: OtherService'
),
)
interpreter = setup_step_interpreter.SetupStepInterpreter(
adb_call_parser=self._parser
)
with self.assertRaises(errors.StepCommandError):
interpreter.interpret([
_to_proto(
task_pb2.SetupStep,
"""
success_condition: {
check_dumpsys: {
service: "accessibility"
expected_string: "InputService"
timeout_sec: 0.0001
}
}""",
)
])
self.assertEqual(interpreter.stats()['error_count_check_dumpsys'], 3)


if __name__ == '__main__':
absltest.main()
7 changes: 7 additions & 0 deletions android_env/proto/task.proto
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ message CheckInstall {
float timeout_sec = 2;
}

message CheckDumpsys {
string service = 1; // e.g. "accessibility"
string expected_string = 2; // e.g. "InputService"
float timeout_sec = 3;
}

message Sleep {
float time_sec = 1;
}
Expand All @@ -62,6 +68,7 @@ message SuccessCondition {
oneof check {
WaitForAppScreen wait_for_app_screen = 2;
CheckInstall check_install = 3;
CheckDumpsys check_dumpsys = 4;
}
}

Expand Down
Loading