Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fail fast if no suitable devices found #34

Merged
merged 5 commits into from
Sep 24, 2024
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: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
fail-fast: false
matrix:
os: [ ubuntu-latest, macos-latest, windows-latest ]
python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11' ]
python-version: [ '3.8', '3.9', '3.10', '3.11' ]
name: ${{ matrix.os }}-Python-${{ matrix.python-version }}
steps:
- uses: actions/checkout@v4
Expand All @@ -32,7 +32,7 @@ jobs:
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.10'
uses: actions/setup-node@v4
with:
node-version: '16'
node-version: '20'

- name: Install appium and adb
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.10'
Expand Down
37 changes: 26 additions & 11 deletions stf_appium_client/StfClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from stf_client.api.user_api import UserApi
from stf_client.api.devices_api import DevicesApi

STATUS_ONLINE = 3


class StfClient(Logger):
DEFAULT_ALLOCATION_TIMEOUT_SECONDS = 900
Expand Down Expand Up @@ -148,11 +150,12 @@ def release(self, device: dict) -> None:
device['owner'] = None
self.logger.info(f'{serial}: released')

def list_devices(self, requirements: dict, fields: str = "") -> list:
def list_devices(self, requirements: dict, fields: str = "", available_filter: bool = True) -> list:
"""
Get list of devices filtered by given requirements and optional extra fields
:param requirements: filter dictionary
:param fields: extra fields to include
:param available_filter: filter only available devices
:return: list of objects that represent devices
"""
req_keys = list(requirements.keys())
Expand All @@ -165,15 +168,15 @@ def list_devices(self, requirements: dict, fields: str = "") -> list:
fields = uniq(req_keys)

predicate = requirements.copy()

predicate.update(
dict(
present=True,
ready=True,
using=False,
owner=None,
status=3) # 3=Online
)
if available_filter:
jupe marked this conversation as resolved.
Show resolved Hide resolved
predicate.update(
dict(
present=True,
ready=True,
using=False,
owner=None,
status=STATUS_ONLINE)
)

self.logger.debug(
f"Find devices with requirements: {json.dumps(requirements)}, using fields: {','.join(fields)}")
Expand All @@ -182,6 +185,12 @@ def list_devices(self, requirements: dict, fields: str = "") -> list:

return filter_(devices, predicate)

def list_online_devices(self, requirements: dict):
suitable_devices = self.list_devices(requirements=requirements)
online = filter(lambda device: device.get('status') == STATUS_ONLINE, suitable_devices)
return list(online)


def find_and_allocate(self, requirements: dict,
timeout_seconds: int = DEFAULT_ALLOCATION_TIMEOUT_SECONDS,
shuffle: bool = True) -> dict:
Expand All @@ -197,7 +206,7 @@ def find_and_allocate(self, requirements: dict,
:raises DeviceNotFound: suitable device not found or all devices are allocated already
"""
NotConnectedError.invariant(self._client, 'Not connected')
suitable_devices = self.list_devices(requirements=requirements)
suitable_devices = self.list_online_devices(requirements=requirements)
DeviceNotFound.invariant(len(suitable_devices), 'no suitable devices found')
if shuffle:
random.shuffle(suitable_devices)
Expand Down Expand Up @@ -233,6 +242,12 @@ def find_wait_and_allocate(self,
:return: device dictionary
"""
wait_until = time.time() + wait_timeout

# Fail fast if no suitable devices
suitable_devices = self.list_devices(requirements=requirements, available_filter=False)
if not suitable_devices:
raise DeviceNotFound(f'No suitable devices found ({json.dumps(requirements)})')

print(f'wait_until: {wait_until}')
while True:
remaining_time = int(wait_until - time.time())
Expand Down
10 changes: 9 additions & 1 deletion test/test_StfClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ class MockResp:
with self.assertRaises(DeviceNotFound):
self.client.find_and_allocate({})

def test_fail_fast(self):

class MockResp:
devices = [{'serial': 123, 'present': True, 'ready': True, 'using': False, 'owner': None, 'status': 1}]
self.DevicesApi.return_value.get_devices = MagicMock(return_value=MockResp())
with self.assertRaises(DeviceNotFound):
self.client.find_and_allocate({})

def test_list_devices(self):
available = {'serial': 123, 'present': True, 'ready': True, 'using': False, 'owner': None, 'status': 3}
self.client.get_devices = MagicMock(return_value=[available])
Expand Down Expand Up @@ -205,7 +213,7 @@ def test_allocation_context_first_success(self):
@patch('time.sleep', side_effect=MagicMock())
def test_allocation_context_wait_success(self, mock_sleep):
dev1 = {'serial': '123', 'present': True, 'ready': True, 'using': False, 'owner': None, 'status': 3}
self.client.get_devices = MagicMock(side_effect=[[], [dev1]])
self.client.get_devices = MagicMock(side_effect=[[dev1], [], [dev1]])
url = '123'

with self.client.allocation_context({"serial": '123'}) as device:
Expand Down
Loading