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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ dist
.pytest_cache
*.pyc
venv
.venv
test_result.xml
.python-version
.python-version
build/
29 changes: 28 additions & 1 deletion ppadb/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,40 @@ def push(self, src, dest, mode=0o644, progress=None):
for item in files:
self._push(root / item, destdir / item, mode, progress)

def pull(self, src, dest):
def _pull(self, src, dest):
sync_conn = self.sync()
sync = Sync(sync_conn)

with sync_conn:
return sync.pull(src, dest)

def pull(self, src, dest):
src = PurePosixPath(src)
dest = Path(dest)

dir_string = "IS_DIR"
res = self.shell(f"[ -d {src} ] && echo {dir_string}")
if dir_string in res:
# Get all files in the dir
# Pull each
dest.mkdir(exist_ok=True)
cmd = f"ls -1a {src}"
res = self.shell(cmd)
contents_list = res.split("\n")
contents_list = [
x for x in contents_list if x != "." and x != ".." and x != ""
]
for element in contents_list:
element_src = src / element
element_dest = dest / element
self.pull(element_src, element_dest)
else:
file_string = "IS_FILE"
res = self.shell(f"[ -f {src} ] && echo {file_string}")
if file_string not in res:
raise FileNotFoundError(f"Cannot find {src} on device")
self._pull(str(src), str(dest))

def install(
self,
path,
Expand Down
29 changes: 28 additions & 1 deletion ppadb/device_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,40 @@ async def push(self, src, dest, mode=0o644, progress=None):
for item in files:
await self._push(root / item, destdir / item, mode, progress)

async def pull(self, src, dest):
async def _pull(self, src, dest):
sync_conn = await self.sync()
sync = SyncAsync(sync_conn)

async with sync_conn:
return await sync.pull(src, dest)

async def pull(self, src, dest):
src = PurePosixPath(src)
dest = Path(dest)

dir_string = "IS_DIR"
res = await self.shell(f"[ -d {src} ] && echo {dir_string}")
if dir_string in res:
# Get all files in the dir
# Pull each
dest.mkdir(exist_ok=True)
cmd = f"ls -1a {src}"
res = await self.shell(cmd)
contents_list = res.split("\n")
contents_list = [
x for x in contents_list if x != "." and x != ".." and x != ""
]
for element in contents_list:
element_src = src / element
element_dest = dest / element
await self.pull(element_src, element_dest)
else:
file_string = "IS_FILE"
res = await self.shell(f"[ -f {src} ] && echo {file_string}")
if file_string not in res:
raise FileNotFoundError(f"Cannot find {src} on device")
await self._pull(str(src), str(dest))

async def install(
self,
path,
Expand Down
4 changes: 1 addition & 3 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@

logger = logging.getLogger(__name__)

# adb_host = "emulator"
# adb_host = "172.20.0.2"
adb_host = os.environ.get("ADB_HOST", "127.0.0.1")
adb_port = int(os.environ.get("ADB_PORT", "5037"))
device_serial = os.environ.get("DEVICE_SERIAL", "emulator-5554")
Expand Down Expand Up @@ -99,7 +97,7 @@ def emulator_console_is_connectable():
try:
console = EmulatorConsole(host=adb_host, port=emulator_port)
return console
except Exception as e:
except Exception:
return None

def is_boot_completed():
Expand Down
75 changes: 75 additions & 0 deletions test/test_device.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os
import time
import tempfile
import pathlib

import pytest
import socket
Expand Down Expand Up @@ -181,6 +183,79 @@ def progress(file_name, total_size, sent_size):
assert result[-1]["total_size"] == result[-1]["sent_size"]


@pytest.fixture(scope="function")
def test_filepaths():
filepaths = [
"toplevel/test1.txt",
"toplevel/test2.txt",
"toplevel/subdir1/test3.txt",
"toplevel/subdir1/test4.txt",
"toplevel/subdir1/subdir2/test5.txt",
"toplevel/subdir1/subdir2/test6.txt",
]
yield filepaths


@pytest.fixture(scope="function")
def populated_device(device, test_filepaths):
dirpath = "toplevel/subdir1/subdir2"

device.shell(f"mkdir -p /data/local/tmp/{dirpath}")
for path in test_filepaths:
device.shell(f"echo {path} > /data/local/tmp/{path}")

yield device

device.shell("rm -rf /data/local/tmp/toplevel")


@pytest.fixture(scope="function")
def working_dir():
with tempfile.TemporaryDirectory() as f:
yield pathlib.Path(f)


def test_pull_file(populated_device, working_dir):
populated_device.pull(
"/data/local/tmp/toplevel/test1.txt", working_dir / "test1.txt"
)
dest_path = working_dir / "test1.txt"
assert dest_path.is_file()
assert dest_path.read_text() == "toplevel/test1.txt\n"


def test_pull_dir(populated_device, working_dir):
populated_device.pull(
"/data/local/tmp/toplevel/subdir1/subdir2", working_dir / "subdir2"
)
dest_path = working_dir / "subdir2"
filepath1 = dest_path / "test5.txt"
filepath2 = dest_path / "test6.txt"
assert dest_path.is_dir()
assert filepath1.is_file()
assert filepath1.read_text() == "toplevel/subdir1/subdir2/test5.txt\n"
assert filepath2.is_file()
assert filepath2.read_text() == "toplevel/subdir1/subdir2/test6.txt\n"


def test_pull_recursive_dir(populated_device, working_dir, test_filepaths):
populated_device.pull("data/local/tmp/toplevel", working_dir / "toplevel")
assert (working_dir / "toplevel").is_dir()
assert (working_dir / "toplevel" / "subdir1").is_dir()
for path in test_filepaths:
to_check = working_dir / path
assert to_check.is_file()
assert to_check.read_text() == f"{path}\n"


def test_pull_nonexisting(device, working_dir):
src = "data/local/tmp/dklsfalkjhvcnsdfalvfds"
with pytest.raises(Exception) as excinfo:
device.pull(src, working_dir / "non_existent")

assert str(excinfo.value) == f"Cannot find {src} on device"


def test_forward(device):
device.killforward_all()
forward_map = device.list_forward()
Expand Down
4 changes: 1 addition & 3 deletions test_async/test_client_async.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
"""Unit tests for the `ClientAsync` class.

"""
"""Unit tests for the `ClientAsync` class."""

import asyncio
import sys
Expand Down
4 changes: 1 addition & 3 deletions test_async/test_connection_async.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
"""Unit tests for `ConnectionAsync` class.

"""
"""Unit tests for `ConnectionAsync` class."""

import asyncio
import sys
Expand Down
13 changes: 6 additions & 7 deletions test_async/test_device_async.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
"""Unit tests for the `DeviceAsync` class.
"""Unit tests for the `DeviceAsync` class."""

"""

import asyncio
from contextlib import asynccontextmanager
import os
import pathlib
import sys
import unittest
from unittest.mock import mock_open, patch
from unittest.mock import patch

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

Expand Down Expand Up @@ -144,6 +139,8 @@ async def test_pull(self):
with async_patch(
"asyncio.open_connection",
return_value=(FakeStreamReader(), FakeStreamWriter()),
), async_patch(
"ppadb.device_async.DeviceAsync.shell", return_value=["", "IS_FILE"]
):
with async_patch(
"{}.FakeStreamReader.read".format(__name__),
Expand All @@ -165,6 +162,8 @@ async def test_pull_fail(self):
with async_patch(
"asyncio.open_connection",
return_value=(FakeStreamReader(), FakeStreamWriter()),
), async_patch(
"ppadb.device_async.DeviceAsync.shell", return_value=["", "IS_FILE"]
):
with async_patch(
"{}.FakeStreamReader.read".format(__name__),
Expand Down