Skip to content

Commit 4356950

Browse files
dbnicholsonrtibbles
authored andcommitted
Include worker test in on-device test app
Exercise the `--worker` option in the on-device test app. This requires raising the SDK version to 30.
1 parent e9ee66b commit 4356950

File tree

9 files changed

+385
-5
lines changed

9 files changed

+385
-5
lines changed

ci/makefiles/android.mk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ ANDROID_NDK_VERSION_LEGACY ?= 21e
66
ANDROID_SDK_TOOLS_VERSION ?= 6514223
77
ANDROID_SDK_BUILD_TOOLS_VERSION ?= 29.0.3
88
ANDROID_HOME ?= $(HOME)/.android
9-
ANDROID_API_LEVEL ?= 27
9+
ANDROID_API_LEVEL ?= 30
1010

1111
# per OS dictionary-like
1212
UNAME_S := $(shell uname -s)

testapps/on_device_unit_tests/setup.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,39 +42,42 @@
4242
'requirements':
4343
'sqlite3,libffi,openssl,pyjnius,kivy,python3,requests,urllib3,'
4444
'chardet,idna',
45-
'android-api': 27,
45+
'android-api': 30,
4646
'ndk-api': 21,
4747
'dist-name': 'bdist_unit_tests_app',
4848
'arch': 'armeabi-v7a',
4949
'bootstrap': 'sdl2',
5050
'permissions': ['INTERNET', 'VIBRATE'],
5151
'orientation': ['portrait', 'landscape'],
5252
'service': 'P4a_test_service:app_service.py',
53+
'worker': 'P4a_test_worker:app_worker.py',
5354
},
5455
'aab':
5556
{
5657
'requirements':
5758
'sqlite3,libffi,openssl,pyjnius,kivy,python3,requests,urllib3,'
5859
'chardet,idna',
59-
'android-api': 27,
60+
'android-api': 30,
6061
'ndk-api': 21,
6162
'dist-name': 'bdist_unit_tests_app',
6263
'arch': 'armeabi-v7a',
6364
'bootstrap': 'sdl2',
6465
'permissions': ['INTERNET', 'VIBRATE'],
6566
'orientation': ['portrait', 'landscape'],
6667
'service': 'P4a_test_service:app_service.py',
68+
'worker': 'P4a_test_worker:app_worker.py',
6769
},
6870
'aar':
6971
{
7072
'requirements': 'python3',
71-
'android-api': 27,
73+
'android-api': 30,
7274
'ndk-api': 21,
7375
'dist-name': 'bdist_unit_tests_app',
7476
'arch': 'arm64-v8a',
7577
'bootstrap': 'service_library',
7678
'permissions': ['INTERNET', 'VIBRATE'],
7779
'service': 'P4a_test_service:app_service.py',
80+
'worker': 'P4a_test_worker:app_worker.py',
7881
}
7982
}
8083

testapps/on_device_unit_tests/test_app/app_flask.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
get_failed_unittests_from,
2727
vibrate_with_pyjnius,
2828
get_android_python_activity,
29+
get_work_manager,
2930
set_device_orientation,
3031
setup_lifecycle_callbacks,
3132
skip_if_not_running_from_android_device,
@@ -37,10 +38,12 @@ def __init__(self, *args, **kwargs):
3738
super().__init__(*args, **kwargs)
3839
setup_lifecycle_callbacks()
3940
self.service_running = False
41+
self.work_request = None
4042

4143
def get_status(self):
4244
return jsonify({
4345
'service_running': self.service_running,
46+
'worker_running': self.worker_running,
4447
})
4548

4649
@property
@@ -62,6 +65,58 @@ def service_stop(self):
6265
self.service.stop(activity)
6366
self.service_running = False
6467

68+
@property
69+
@skip_if_not_running_from_android_device
70+
def P4a_test_workerWorker(self):
71+
from jnius import autoclass
72+
73+
return autoclass('org.test.unit_tests_app.P4a_test_workerWorker')
74+
75+
@skip_if_not_running_from_android_device
76+
def worker_start(self):
77+
from jnius import autoclass
78+
79+
if self.worker_running:
80+
return
81+
82+
OneTimeWorkRequestBuilder = autoclass('androidx.work.OneTimeWorkRequest$Builder')
83+
data = self.P4a_test_workerWorker.buildInputData('10')
84+
self.work_request = (
85+
OneTimeWorkRequestBuilder(self.P4a_test_workerWorker._class)
86+
.setInputData(data)
87+
.build()
88+
)
89+
work_manager = get_work_manager()
90+
op = work_manager.enqueue(self.work_request)
91+
op.getResult().get()
92+
93+
@skip_if_not_running_from_android_device
94+
def worker_stop(self):
95+
if self.worker_running:
96+
work_manager = get_work_manager()
97+
op = work_manager.cancelWorkById(self.work_request.getId())
98+
op.getResult().get()
99+
100+
@property
101+
@skip_if_not_running_from_android_device
102+
def work_info(self):
103+
if self.work_request is None:
104+
return None
105+
106+
work_manager = get_work_manager()
107+
return work_manager.getWorkInfoById(self.work_request.getId()).get()
108+
109+
@property
110+
@skip_if_not_running_from_android_device
111+
def worker_running(self):
112+
info = self.work_info
113+
if info is None:
114+
print('Work request not started')
115+
return False
116+
state = info.getState()
117+
print('Work request state:', state.toString())
118+
return not state.isFinished()
119+
65120

66121
app = App(__name__)
67122
TESTS_TO_PERFORM = dict()
@@ -91,6 +146,7 @@ def index():
91146
'index.html',
92147
platform='Android' if RUNNING_ON_ANDROID else 'Desktop',
93148
service_running=current_app.service_running,
149+
worker_running=current_app.worker_running,
94150
)
95151

96152

@@ -198,3 +254,23 @@ def service():
198254
else:
199255
current_app.service_stop()
200256
return ('', 204)
257+
258+
259+
@app.route('/worker')
260+
def worker():
261+
if not RUNNING_ON_ANDROID:
262+
print(NON_ANDROID_DEVICE_MSG, '...cancelled worker.')
263+
return (NON_ANDROID_DEVICE_MSG, 400)
264+
args = request.args
265+
if 'action' not in args:
266+
print('ERROR: asked to manage worker but no action specified')
267+
return ('No action specified', 400)
268+
269+
action = args['action']
270+
if action == 'start':
271+
current_app.worker_start()
272+
elif action == 'stop':
273+
current_app.worker_stop()
274+
else:
275+
return ('Invalid action "{}"'.format(action), 400)
276+
return ('', 204)

testapps/on_device_unit_tests/test_app/app_kivy.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
import subprocess
44

5+
from android.runnable import run_on_ui_thread
56
from os.path import split
67

78
from kivy.app import App
89
from kivy.clock import Clock
10+
from kivy.event import EventDispatcher
911
from kivy.properties import (
1012
BooleanProperty,
13+
BoundedNumericProperty,
1114
DictProperty,
1215
ListProperty,
1316
StringProperty,
@@ -19,11 +22,13 @@
1922
get_android_python_activity,
2023
get_failed_unittests_from,
2124
get_images_with_extension,
25+
get_work_manager,
2226
load_kv_from,
2327
raise_error,
2428
run_test_suites_into_buffer,
2529
setup_lifecycle_callbacks,
2630
vibrate_with_pyjnius,
31+
work_info_observer,
2732
)
2833
from widgets import TestImage
2934

@@ -34,11 +39,79 @@
3439
ScreenKeyboard:
3540
ScreenOrientation:
3641
ScreenService:
42+
ScreenWorker:
3743
'''
3844
load_kv_from('screen_unittests.kv')
3945
load_kv_from('screen_keyboard.kv')
4046
load_kv_from('screen_orientation.kv')
4147
load_kv_from('screen_service.kv')
48+
load_kv_from('screen_worker.kv')
49+
50+
51+
class Work(EventDispatcher):
52+
'''Event dispatcher for WorkRequests'''
53+
54+
progress = BoundedNumericProperty(0, min=0, max=100)
55+
state = StringProperty()
56+
running = BooleanProperty(False)
57+
58+
def __init__(self, **kwargs):
59+
super().__init__(**kwargs)
60+
61+
self._request = None
62+
self._observer = work_info_observer(self.update)
63+
self._live_data = None
64+
65+
def update(self, work_info):
66+
progress_data = work_info.getProgress()
67+
self.progress = progress_data.getInt('PROGRESS', 0)
68+
69+
state = work_info.getState()
70+
self.state = state.name()
71+
if state.isFinished():
72+
self.running = False
73+
self._live_data.removeObserver(self._observer)
74+
75+
def enqueue(self, data):
76+
from jnius import autoclass
77+
78+
OneTimeWorkRequestBuilder = autoclass('androidx.work.OneTimeWorkRequest$Builder')
79+
Worker = autoclass('org.test.unit_tests_app.P4a_test_workerWorker')
80+
81+
input_data = Worker.buildInputData(data)
82+
self._request = (
83+
OneTimeWorkRequestBuilder(Worker._class)
84+
.setInputData(input_data)
85+
.build()
86+
)
87+
88+
work_manager = get_work_manager()
89+
op = work_manager.enqueue(self._request)
90+
op.getResult().get()
91+
92+
self.running = True
93+
request_id = self._request.getId()
94+
self._live_data = work_manager.getWorkInfoByIdLiveData(request_id)
95+
self._add_observer()
96+
97+
def cancel(self):
98+
if not self.running:
99+
return
100+
101+
work_manager = get_work_manager()
102+
request_id = self._request.getId()
103+
op = work_manager.cancelWorkById(request_id)
104+
op.getResult().get()
105+
106+
@run_on_ui_thread
107+
def _add_observer(self):
108+
self._live_data.observeForever(self._observer)
109+
110+
def on_progress(self, instance, progress):
111+
print('work progress: {}%'.format(progress))
112+
113+
def on_state(self, instance, state):
114+
print('work state:', state)
42115

43116

44117
class TestKivyApp(App):
@@ -51,6 +124,7 @@ class TestKivyApp(App):
51124

52125
def build(self):
53126
self.reset_unittests_results()
127+
self.work = Work()
54128
self.sm = Builder.load_string(screen_manager_app)
55129
return self.sm
56130

@@ -165,3 +239,20 @@ def stop_service(self):
165239
service = self.service_time
166240
activity = get_android_python_activity()
167241
service.stop(activity)
242+
243+
def worker_button_pressed(self, *args):
244+
if RUNNING_ON_ANDROID:
245+
if not self.work.running:
246+
print('Starting worker')
247+
self.start_worker()
248+
else:
249+
print('Stopping worker')
250+
self.stop_worker()
251+
else:
252+
raise_error('Worker test not supported on desktop')
253+
254+
def start_worker(self):
255+
self.work.enqueue('10')
256+
257+
def stop_worker(self):
258+
self.work.cancel()
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
import time
5+
6+
from jnius import autoclass
7+
from os import environ
8+
9+
DataBuilder = autoclass('androidx.work.Data$Builder')
10+
P4a_test_workerWorker = autoclass('org.test.unit_tests_app.P4a_test_workerWorker')
11+
12+
13+
def set_progress(progress):
14+
progress_data = DataBuilder().putInt('PROGRESS', progress).build()
15+
P4a_test_workerWorker.mWorker.setProgressAsync(progress_data)
16+
17+
18+
argument = environ.get('PYTHON_SERVICE_ARGUMENT', '')
19+
print(
20+
'app_worker.py was successfully called with argument: "{}"'.format(
21+
argument,
22+
),
23+
)
24+
25+
try:
26+
duration = int(argument)
27+
except ValueError:
28+
duration = 60
29+
30+
print('Running the test worker for {} seconds'.format(duration))
31+
32+
remaining = duration
33+
while remaining > 0:
34+
print(remaining, 'seconds remaining')
35+
36+
progress = int((100.0 * (duration - remaining)) / duration)
37+
set_progress(progress)
38+
39+
remaining -= 1
40+
time.sleep(1)
41+
set_progress(100)
42+
43+
print('Exiting the test worker')

testapps/on_device_unit_tests/test_app/screen_unittests.kv

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@
4242
text: 'Test Service'
4343
font_size: sp(FONT_SIZE_SUBTITLE)
4444
on_press: root.parent.current = 'service'
45+
Button:
46+
text: 'Test Worker'
47+
font_size: sp(FONT_SIZE_SUBTITLE)
48+
on_press: root.parent.current = 'worker'
4549
Image:
4650
keep_ratio: False
4751
allow_stretch: True

0 commit comments

Comments
 (0)