Skip to content

Commit

Permalink
Merge d645700 into 54afb12
Browse files Browse the repository at this point in the history
  • Loading branch information
JeffLIrion committed Jun 13, 2021
2 parents 54afb12 + d645700 commit 9207bab
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
if python --version 2>&1 | grep -q "Python 2"; then pip install mock rsa==4.0; fi
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
pip install .
if python --version 2>&1 | grep -q "Python 3.7" || python --version 2>&1 | grep -q "Python 3.8" || python --version 2>&1 | grep -q "Python 3.9"; then pip install aiofiles; fi
if python --version 2>&1 | grep -q "Python 3.7" || python --version 2>&1 | grep -q "Python 3.8" || python --version 2>&1 | grep -q "Python 3.9"; then pip install aiofiles adb-shell[usb]; fi
- name: Lint with pylint and flake8
run: |
if python --version 2>&1 | grep -q "Python 2" || python --version 2>&1 | grep -q "Python 3.5" || python --version 2>&1 | grep -q "Python 3.6"; then flake8 androidtv/ --exclude="androidtv/setup_async.py,androidtv/basetv/basetv_async.py,androidtv/androidtv/androidtv_async.py,androidtv/firetv/firetv_async.py,androidtv/adb_manager/adb_manager_async.py" && pylint --ignore="setup_async.py,basetv_async.py,androidtv_async.py,firetv_async.py,adb_manager_async.py" androidtv/; fi
Expand Down
40 changes: 39 additions & 1 deletion androidtv/adb_manager/adb_manager_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
from contextlib import asynccontextmanager
import logging

from adb_shell.adb_device import AdbDeviceUsb
from adb_shell.adb_device_async import AdbDeviceTcpAsync
from adb_shell.auth.sign_pythonrsa import PythonRSASigner
from adb_shell.constants import DEFAULT_PUSH_MODE, DEFAULT_READ_TIMEOUT_S
import aiofiles
from ppadb.client import Client

Expand All @@ -21,6 +23,37 @@
_LOGGER = logging.getLogger(__name__)


class AdbDeviceUsbAsync:
"""An async wrapper for the adb-shell ``AdbDeviceUsb`` class."""
def __init__(self, serial=None, port_path=None, default_transport_timeout_s=None, banner=None):
self._adb = AdbDeviceUsb(serial, port_path, default_transport_timeout_s, banner)

@property
def available(self):
"""Whether or not an ADB connection to the device has been established."""
return self._adb.available

async def close(self):
"""Close the connection via the provided transport's ``close()`` method."""
await asyncio.get_running_loop().run_in_executor(None, self._adb.close)

async def connect(self, rsa_keys=None, transport_timeout_s=None, auth_timeout_s=DEFAULT_AUTH_TIMEOUT_S, read_timeout_s=DEFAULT_READ_TIMEOUT_S, auth_callback=None):
"""Establish an ADB connection to the device."""
return await asyncio.get_running_loop().run_in_executor(None, self._adb.connect, rsa_keys, transport_timeout_s, auth_timeout_s, read_timeout_s, auth_callback)

async def pull(self, device_path, local_path, progress_callback=None, transport_timeout_s=None, read_timeout_s=DEFAULT_READ_TIMEOUT_S):
"""Pull a file from the device."""
await asyncio.get_running_loop().run_in_executor(None, self._adb.pull, device_path, local_path, progress_callback, transport_timeout_s, read_timeout_s)

async def push(self, local_path, device_path, st_mode=DEFAULT_PUSH_MODE, mtime=0, progress_callback=None, transport_timeout_s=None, read_timeout_s=DEFAULT_READ_TIMEOUT_S):
"""Push a file or directory to the device."""
await asyncio.get_running_loop().run_in_executor(None, self._adb.push, local_path, device_path, st_mode, mtime, progress_callback, transport_timeout_s, read_timeout_s)

async def shell(self, command, transport_timeout_s=None, read_timeout_s=DEFAULT_READ_TIMEOUT_S, timeout_s=None, decode=True):
"""Send an ADB shell command to the device."""
return await asyncio.get_running_loop().run_in_executor(None, self._adb.shell, command, transport_timeout_s, read_timeout_s, timeout_s, decode)


class DeviceAsync:
"""An async wrapper for the pure-python-adb ``Device`` class."""
def __init__(self, device):
Expand Down Expand Up @@ -114,7 +147,12 @@ def __init__(self, host, port, adbkey='', signer=None):
self.host = host
self.port = int(port)
self.adbkey = adbkey
self._adb = AdbDeviceTcpAsync(host=self.host, port=self.port, default_transport_timeout_s=DEFAULT_ADB_TIMEOUT_S)

if host:
self._adb = AdbDeviceTcpAsync(host=self.host, port=self.port, default_transport_timeout_s=DEFAULT_ADB_TIMEOUT_S)
else:
self._adb = AdbDeviceUsbAsync(default_transport_timeout_s=DEFAULT_ADB_TIMEOUT_S)

self._signer = signer

# keep track of whether the ADB connection is intact
Expand Down
9 changes: 7 additions & 2 deletions androidtv/adb_manager/adb_manager_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import sys
import threading

from adb_shell.adb_device import AdbDeviceTcp
from adb_shell.adb_device import AdbDeviceTcp, AdbDeviceUsb
from adb_shell.auth.sign_pythonrsa import PythonRSASigner
from ppadb.client import Client

Expand Down Expand Up @@ -77,7 +77,12 @@ def __init__(self, host, port, adbkey='', signer=None):
self.host = host
self.port = int(port)
self.adbkey = adbkey
self._adb = AdbDeviceTcp(host=self.host, port=self.port, default_transport_timeout_s=DEFAULT_ADB_TIMEOUT_S)

if host:
self._adb = AdbDeviceTcp(host=self.host, port=self.port, default_transport_timeout_s=DEFAULT_ADB_TIMEOUT_S)
else:
self._adb = AdbDeviceUsb(default_transport_timeout_s=DEFAULT_ADB_TIMEOUT_S)

self._signer = signer

# keep track of whether the ADB connection is intact
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
author_email='jefflirion@users.noreply.github.com',
packages=['androidtv', 'androidtv.adb_manager', 'androidtv.basetv', 'androidtv.androidtv', 'androidtv.firetv'],
install_requires=['adb-shell>=0.3.1', 'pure-python-adb>=0.3.0.dev0'],
extras_require={'async': ['aiofiles>=0.4.0']},
extras_require={'async': ['aiofiles>=0.4.0'], 'usb': ['adb-shell[usb]>=0.3.1']},
classifiers=[
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
Expand Down
2 changes: 2 additions & 0 deletions tests/patchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ def shell_fail_server(self, cmd):

PATCH_ADB_DEVICE_TCP = patch("androidtv.adb_manager.adb_manager_sync.AdbDeviceTcp", AdbDeviceTcpFake)

PATCH_ADB_DEVICE_USB = patch("androidtv.adb_manager.adb_manager_sync.AdbDeviceUsb", AdbDeviceTcpFake)

PATCH_ADB_SERVER_RUNTIME_ERROR = patch("{}.{}.device".format(__name__, CLIENT_FAKE_SUCCESS), side_effect=RuntimeError)


Expand Down
12 changes: 12 additions & 0 deletions tests/test_adb_manager_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,18 @@ async def test_adb_screencap_success(self):
self.assertEqual(await self.adb.screencap(), PNG_IMAGE)


class TestADBPythonUsbAsync(unittest.TestCase):
"""Test the `ADBPythonAsync` class using a USB connection."""

def test_init(self):
"""Create an `ADBPythonSync` instance with a USB connection.
"""
with patch("androidtv.adb_manager.adb_manager_async.AdbDeviceUsbAsync") as patched:
ADBPythonAsync('', 5555)
assert patched.called


class TestADBServerAsync(TestADBPythonAsync):
"""Test the `ADBServerAsync` class."""

Expand Down
41 changes: 39 additions & 2 deletions tests/test_adb_manager_async_temp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

sys.path.insert(0, '..')

from androidtv.adb_manager.adb_manager_async import ClientAsync
from adb_shell.transport.usb_transport import UsbTransport

from androidtv.adb_manager.adb_manager_async import AdbDeviceUsbAsync, ClientAsync

from .async_wrapper import awaiter
from . import patchers
Expand All @@ -13,7 +15,7 @@
class TestAsyncClientDevice(unittest.TestCase):
"""Test the ``ClientAsync`` and ``DeviceAsync`` classes defined in ``adb_manager_async.py``.
This file can be removed once true async support for using an ADB server is available.
These tests can be removed once true async support for using an ADB server is available.
"""

Expand Down Expand Up @@ -44,3 +46,38 @@ async def test_async_client_device_fail(self):
device = await client.device("serial")

self.assertFalse(device)


class TestAsyncUsb(unittest.TestCase):
"""Test the ``AdbDeviceUsbAsync`` class defined in ``adb_manager_async.py``.
These tests can be removed once true async support for using a USB connection is available.
"""

@awaiter
async def test_async_usb(self):
with patch("adb_shell.adb_device.UsbTransport.find_adb", return_value=UsbTransport("device", "setting")):
device = AdbDeviceUsbAsync()

self.assertFalse(device.available)

with patch("androidtv.adb_manager.adb_manager_async.AdbDeviceUsb.connect") as connect:
await device.connect()
assert connect.called

with patch("androidtv.adb_manager.adb_manager_async.AdbDeviceUsb.shell") as shell:
await device.shell("test")
assert shell.called

with patch("androidtv.adb_manager.adb_manager_async.AdbDeviceUsb.push") as push:
await device.push("local_path", "device_path")
assert push.called

with patch("androidtv.adb_manager.adb_manager_async.AdbDeviceUsb.pull") as pull:
await device.pull("device_path", "local_path")
assert pull.called

with patch("androidtv.adb_manager.adb_manager_async.AdbDeviceUsb.close") as close:
await device.close()
assert close.called
13 changes: 13 additions & 0 deletions tests/test_adb_manager_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

sys.path.insert(0, '..')

from adb_shell.transport.tcp_transport import TcpTransport
from androidtv.adb_manager.adb_manager_sync import _acquire, ADBPythonSync, ADBServerSync
from androidtv.exceptions import LockNotAcquiredException
from . import patchers
Expand Down Expand Up @@ -287,6 +288,18 @@ def test_adb_screencap_success(self):
self.assertEqual(self.adb.screencap(), PNG_IMAGE)


class TestADBPythonUsbSync(TestADBPythonSync):
"""Test the `ADBPythonSync` class using a USB connection."""

def setUp(self):
"""Create an `ADBPythonSync` instance with a USB connection.
"""
# Patch the real `AdbDeviceUsb` with the fake `AdbDeviceTcpFake`
with patchers.PATCH_ADB_DEVICE_USB, patchers.patch_connect(True)[self.PATCH_KEY]:
self.adb = ADBPythonSync('', 5555)


class TestADBServerSync(TestADBPythonSync):
"""Test the `ADBServerSync` class."""

Expand Down

0 comments on commit 9207bab

Please sign in to comment.