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
9 changes: 5 additions & 4 deletions android_env/components/config_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,6 @@ class GPUMode(enum.Enum):
class EmulatorLauncherConfig:
"""Config class for EmulatorLauncher."""

# NOTE: If `adb_port`, `emulator_console_port` and `grpc_port` are defined
# (i.e. not all equal to 0), it is assumed that the emulator they point to
# exists already and EmulatorLauncher will be skipped.

# Filesystem path to the `emulator` binary.
emulator_path: str = '~/Android/Sdk/emulator/emulator'
# Filesystem path to the Android SDK root.
Expand Down Expand Up @@ -138,6 +134,11 @@ class EmulatorLauncherConfig:
emulator_console_port: int = 0
# Port for gRPC communication with the emulator.
grpc_port: int = 0
# Whether to connect to an existing emulator.
# If True, we connect to the emulator at the specified ports and do not
# launch.
# If False, we launch a new emulator (ports must be pre-allocated).
connect_to_existing: bool = False


@dataclasses.dataclass
Expand Down
73 changes: 17 additions & 56 deletions android_env/components/simulators/emulator/emulator_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import grpc
import immutabledict
import numpy as np
import portpicker

from android_env.proto import emulator_controller_pb2
from android_env.proto import emulator_controller_pb2_grpc
Expand All @@ -54,50 +53,6 @@
})


def _is_existing_emulator_provided(
launcher_config: config_classes.EmulatorLauncherConfig,
) -> bool:
"""Returns true if all necessary args were provided."""

return bool(
launcher_config.adb_port
and launcher_config.emulator_console_port
and launcher_config.grpc_port
)


def _pick_adb_port() -> int:
"""Tries to pick a port in the recommended range 5555-5585.

If no such port can be found, will return a random unused port. More info:
https://developer.android.com/studio/command-line/adb#howadbworks.

Returns:
port: an available port for adb.
"""

for p in range(5555, 5587, 2):
if portpicker.is_port_free(p):
return p
return portpicker.pick_unused_port()


def _pick_emulator_grpc_port() -> int:
"""Tries to pick the recommended port for grpc.

If no such port can be found, will return a random unused port. More info:
https://android.googlesource.com/platform/external/qemu/+/emu-master-dev/android/android-grpc/docs/.

Returns:
port: an available port for emulator grpc.
"""

if portpicker.is_port_free(8554):
return 8554
else:
return portpicker.pick_unused_port()


class EmulatorBootError(errors.SimulatorError):
"""Raised when an emulator failed to boot."""

Expand Down Expand Up @@ -159,17 +114,23 @@ def __init__(

# If adb_port, console_port and grpc_port are all already provided,
# we assume the emulator already exists and there's no need to launch.
if _is_existing_emulator_provided(self._config.emulator_launcher):
self._existing_emulator_provided = True
logging.info('Connecting to existing emulator "%r"',
self.adb_device_name())
else:
self._existing_emulator_provided = False
self._config.emulator_launcher.adb_port = _pick_adb_port()
self._config.emulator_launcher.emulator_console_port = (
portpicker.pick_unused_port()
if self._config.emulator_launcher.connect_to_existing:
logging.info(
'Connecting to existing emulator "%r"', self.adb_device_name()
)
self._config.emulator_launcher.grpc_port = _pick_emulator_grpc_port()
else:
launcher_config = self._config.emulator_launcher
if (
launcher_config.adb_port <= 0
or launcher_config.emulator_console_port <= 0
or launcher_config.grpc_port <= 0
):
raise ValueError(
'Ports must be allocated and set in config before instantiating '
f'EmulatorSimulator. Got: ADB={launcher_config.adb_port}, '
f'Console={launcher_config.emulator_console_port}, '
f'gRPC={launcher_config.grpc_port}'
)

self._channel = None
self._emulator_stub: emulator_controller_pb2_grpc.EmulatorControllerStub | None = (
Expand Down Expand Up @@ -202,7 +163,7 @@ def __init__(
)

# If necessary, create EmulatorLauncher.
if self._existing_emulator_provided:
if self._config.emulator_launcher.connect_to_existing:
self._logfile_path = self._config.logfile_path or None
self._launcher = None
else:
Expand Down
127 changes: 80 additions & 47 deletions android_env/components/simulators/emulator/emulator_simulator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,28 @@
from android_env.proto import state_pb2
import grpc
from PIL import Image
import portpicker

from android_env.proto import emulator_controller_pb2
from android_env.proto import emulator_controller_pb2_grpc
from android_env.proto import snapshot_service_pb2


def _launcher_config(
adb_port: int = 5555,
emulator_console_port: int = 5554,
grpc_port: int = 1234,
connect_to_existing: bool = False,
**kwargs,
) -> config_classes.EmulatorLauncherConfig:
return config_classes.EmulatorLauncherConfig(
adb_port=adb_port,
emulator_console_port=emulator_console_port,
grpc_port=grpc_port,
connect_to_existing=connect_to_existing,
**kwargs,
)


class EmulatorSimulatorTest(parameterized.TestCase):

def setUp(self):
Expand Down Expand Up @@ -95,7 +110,7 @@ def setUp(self):

def test_adb_device_name_not_empty(self):
config = config_classes.EmulatorConfig(
emulator_launcher=config_classes.EmulatorLauncherConfig(
emulator_launcher=_launcher_config(
grpc_port=1234, tmp_dir=self.create_tempdir().full_path
),
adb_controller=config_classes.AdbControllerConfig(
Expand All @@ -111,7 +126,7 @@ def test_logfile_path(self):

config = config_classes.EmulatorConfig(
logfile_path='fake/logfile/path',
emulator_launcher=config_classes.EmulatorLauncherConfig(
emulator_launcher=_launcher_config(
grpc_port=1234, tmp_dir=self.create_tempdir().full_path
),
adb_controller=config_classes.AdbControllerConfig(
Expand All @@ -130,38 +145,6 @@ def test_logfile_path(self):
mock_open.assert_called_once_with('fake/logfile/path', 'rb')
self.assertEqual(logs, 'fake_logs')

@mock.patch.object(portpicker, 'is_port_free', return_value=True)
def test_grpc_port(self, unused_mock_portpicker):

launcher_config = config_classes.EmulatorLauncherConfig(
tmp_dir=self.create_tempdir().full_path
)
config = config_classes.EmulatorConfig(
emulator_launcher=launcher_config,
adb_controller=config_classes.AdbControllerConfig(
adb_path='/my/adb',
adb_server_port=5037,
),
)
simulator = emulator_simulator.EmulatorSimulator(config)
self.assertEqual(launcher_config.grpc_port, 8554)

@mock.patch.object(portpicker, 'is_port_free', return_value=False)
def test_grpc_port_unavailable(self, unused_mock_portpicker):

launcher_config = config_classes.EmulatorLauncherConfig(
tmp_dir=self.create_tempdir().full_path
)
config = config_classes.EmulatorConfig(
emulator_launcher=launcher_config,
adb_controller=config_classes.AdbControllerConfig(
adb_path='/my/adb',
adb_server_port=5037,
),
)
simulator = emulator_simulator.EmulatorSimulator(config)
self.assertNotEqual(launcher_config.grpc_port, 8554)

def test_launch_operation_order(self):
"""Makes sure that adb_controller is started before Emulator is launched."""

Expand All @@ -174,7 +157,7 @@ def test_launch_operation_order(self):
lambda: call_order.append('launch_emulator_process')
)
config = config_classes.EmulatorConfig(
emulator_launcher=config_classes.EmulatorLauncherConfig(
emulator_launcher=_launcher_config(
grpc_port=1234, tmp_dir=self.create_tempdir().full_path
),
adb_controller=config_classes.AdbControllerConfig(
Expand All @@ -193,7 +176,7 @@ def test_launch_operation_order(self):

def test_close(self):
config = config_classes.EmulatorConfig(
emulator_launcher=config_classes.EmulatorLauncherConfig(
emulator_launcher=_launcher_config(
grpc_port=1234, tmp_dir=self.create_tempdir().full_path
),
adb_controller=config_classes.AdbControllerConfig(
Expand All @@ -216,7 +199,7 @@ def test_value_error_if_launch_attempt_params_incorrect(self):
ValueError,
emulator_simulator.EmulatorSimulator,
config=config_classes.EmulatorConfig(
emulator_launcher=config_classes.EmulatorLauncherConfig(
emulator_launcher=_launcher_config(
grpc_port=1234, tmp_dir=self.create_tempdir().full_path
),
adb_controller=config_classes.AdbControllerConfig(
Expand All @@ -228,9 +211,35 @@ def test_value_error_if_launch_attempt_params_incorrect(self):
),
)

@parameterized.parameters(
(0, 5554, 1234),
(5555, 0, 1234),
(5555, 5554, 0),
(-1, 5554, 1234),
(5555, -1, 1234),
(5555, 5554, -1),
)
def test_value_error_if_ports_not_positive(
self, adb_port, console_port, grpc_port
):
config = config_classes.EmulatorConfig(
emulator_launcher=_launcher_config(
adb_port=adb_port,
emulator_console_port=console_port,
grpc_port=grpc_port,
tmp_dir=self.create_tempdir().full_path,
),
adb_controller=config_classes.AdbControllerConfig(
adb_path='/my/adb',
adb_server_port=5037,
),
)
with self.assertRaisesRegex(ValueError, 'Ports must be allocated and set'):
emulator_simulator.EmulatorSimulator(config)

def test_launch_attempt_reboot(self):
config = config_classes.EmulatorConfig(
emulator_launcher=config_classes.EmulatorLauncherConfig(
emulator_launcher=_launcher_config(
grpc_port=1234, tmp_dir=self.create_tempdir().full_path
),
adb_controller=config_classes.AdbControllerConfig(
Expand All @@ -256,7 +265,7 @@ def test_launch_attempt_reboot(self):

def test_launch_attempt_reinstall_after_zero_attempts(self):
config = config_classes.EmulatorConfig(
emulator_launcher=config_classes.EmulatorLauncherConfig(
emulator_launcher=_launcher_config(
grpc_port=1234, tmp_dir=self.create_tempdir().full_path
),
adb_controller=config_classes.AdbControllerConfig(
Expand All @@ -283,7 +292,7 @@ def test_launch_attempt_reinstall_after_zero_attempts(self):

def test_launch_attempt_reinstall(self):
config = config_classes.EmulatorConfig(
emulator_launcher=config_classes.EmulatorLauncherConfig(
emulator_launcher=_launcher_config(
grpc_port=1234, tmp_dir=self.create_tempdir().full_path
),
adb_controller=config_classes.AdbControllerConfig(
Expand Down Expand Up @@ -315,7 +324,7 @@ def test_launch_attempt_reinstall(self):

def test_get_screenshot(self):
config = config_classes.EmulatorConfig(
emulator_launcher=config_classes.EmulatorLauncherConfig(
emulator_launcher=_launcher_config(
grpc_port=1234, tmp_dir=self.create_tempdir().full_path
),
adb_controller=config_classes.AdbControllerConfig(
Expand Down Expand Up @@ -343,7 +352,7 @@ def test_get_screenshot(self):
def test_get_screenshot_reconnects_on_grpc_error(self):
config = config_classes.EmulatorConfig(
interaction_rate_sec=0.0,
emulator_launcher=config_classes.EmulatorLauncherConfig(
emulator_launcher=_launcher_config(
grpc_port=1234, tmp_dir=self.create_tempdir().full_path
),
adb_controller=config_classes.AdbControllerConfig(
Expand Down Expand Up @@ -389,7 +398,7 @@ def test_get_screenshot_reconnects_on_grpc_error(self):

def test_load_state(self):
config = config_classes.EmulatorConfig(
emulator_launcher=config_classes.EmulatorLauncherConfig(
emulator_launcher=_launcher_config(
grpc_port=1234, tmp_dir=self.create_tempdir().full_path
),
adb_controller=config_classes.AdbControllerConfig(
Expand Down Expand Up @@ -443,7 +452,7 @@ def test_load_state(self):

def test_save_state(self):
config = config_classes.EmulatorConfig(
emulator_launcher=config_classes.EmulatorLauncherConfig(
emulator_launcher=_launcher_config(
grpc_port=1234, tmp_dir=self.create_tempdir().full_path
),
adb_controller=config_classes.AdbControllerConfig(
Expand Down Expand Up @@ -485,7 +494,7 @@ def test_save_state(self):

def test_send_touch(self):
config = config_classes.EmulatorConfig(
emulator_launcher=config_classes.EmulatorLauncherConfig(
emulator_launcher=_launcher_config(
grpc_port=1234, tmp_dir=self.create_tempdir().full_path
),
adb_controller=config_classes.AdbControllerConfig(
Expand Down Expand Up @@ -564,7 +573,7 @@ def test_send_touch(self):
)
def test_send_key(self, os_name, expected_code_type):
config = config_classes.EmulatorConfig(
emulator_launcher=config_classes.EmulatorLauncherConfig(
emulator_launcher=_launcher_config(
grpc_port=1234, tmp_dir=self.create_tempdir().full_path
),
adb_controller=config_classes.AdbControllerConfig(
Expand Down Expand Up @@ -635,6 +644,30 @@ def test_send_key(self, os_name, expected_code_type):
expected_calls, simulator._emulator_stub.sendKey.call_args_list
)

def test_connect_to_existing(self):
config = config_classes.EmulatorConfig(
emulator_launcher=_launcher_config(
adb_port=5555,
emulator_console_port=5554,
grpc_port=1234,
connect_to_existing=True,
tmp_dir=self.create_tempdir().full_path,
),
adb_controller=config_classes.AdbControllerConfig(
adb_path='/my/adb',
adb_server_port=5037,
),
)
simulator = emulator_simulator.EmulatorSimulator(config)
self.addCleanup(simulator.close)

# Launch should not try to launch the process, but should connect.
simulator.launch()

self.assertIsNone(simulator._launcher)
# It should still establish channel.
self.assertIsNotNone(simulator._emulator_stub)


if __name__ == '__main__':
absltest.main()
Loading
Loading