Skip to content

Commit

Permalink
Merge pull request #2 from nocarryr/kivyui
Browse files Browse the repository at this point in the history
Add Kivy UI
  • Loading branch information
nocarryr committed Feb 21, 2017
2 parents ddecc79 + 978a544 commit 2a0b8b1
Show file tree
Hide file tree
Showing 22 changed files with 1,628 additions and 29 deletions.
33 changes: 30 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,37 @@
dist: trusty
sudo: false
language: python
python: "3.5"
cache:
directories:
- "build/"
addons:
apt:
packages:
- libsdl2-dev
- libsdl2-ttf-dev
- libsdl2-image-dev
- libsdl2-mixer-dev
- libgstreamer1.0-dev
- gstreamer1.0-alsa
- gstreamer1.0-plugins-base
- python-dev
- libsmpeg-dev
- libswscale-dev
- libavformat-dev
- libavcodec-dev
- libjpeg-dev
- libtiff4-dev
- libX11-dev
- libmtdev-dev
- libgl1-mesa-dev
- libgles2-mesa-dev
- xvfb
- pulseaudio
install:
- pip install -U pip setuptools wheel
- pip install -r requirements-test.txt
- ./tools/install-deps.sh
- pip install -e .
script:
- py.test --cov-config .coveragerc --cov=vidhubcontrol
- ./runtests.sh
after_success:
- coveralls
3 changes: 3 additions & 0 deletions requirements-test.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
pytest>=3.0.2
pytest-asyncio
pytest-cov
pytest-xdist
git+https://github.com/The-Compiler/pytest-xvfb.git@master#egg=pytest-xvfb
coveralls
-r vidhubcontrol/kivyui/requirements.txt
15 changes: 15 additions & 0 deletions runtests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/sh
set -e

py.test --cov-config .coveragerc --cov=vidhubcontrol
mv .coverage .coverage-orig

for f in tests/kv/test*.py
do
echo "Running $f"
py.test --cov-append --cov-config .coveragerc --cov=vidhubcontrol "$f"
coverage combine -a .coverage-orig
mv .coverage .coverage-orig
done
coverage combine -a .coverage-orig
coverage report
6 changes: 6 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
[tool:pytest]
testpaths = tests
python_files = *.py
norecursedirs = kv
xvfb_width = 1280
xvfb_height = 720
xvfb_colordepth = 24
xvfb_args = +extension GLX
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
'vidhubcontrol-web = vidhubcontrol.sofi_ui.main:run_app',
'vidhubcontrol-server = vidhubcontrol.runserver:main',
],
'gui_scripts':[
'vidhubcontrol-ui = vidhubcontrol.kivyui.main:main',
],
},
platforms=['any'],
classifiers = [
Expand Down
25 changes: 0 additions & 25 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import os
import random
import asyncio
import string

import pytest

Expand Down Expand Up @@ -104,26 +102,3 @@ async def read_very_eager(self):
@pytest.fixture
def tempconfig(tmpdir):
return tmpdir.join('vidhubcontrol.json')

def random_string(num_chars=8):
return ''.join((random.choice(string.printable) for i in range(num_chars)))

def get_random_values(n, value_type=None):
if value_type is None:
value_type = random.choice([str, int, float])
if value_type is str:
s = set((random_string() for i in range(n)))
while len(s) < n:
s.add(random_string())
elif value_type is int:
maxint = 100
if n > maxint:
maxint = n * 4
s = set((random.randint(0, maxint) for i in range(n)))
while len(s) < n:
s.add(random.randint(0, maxint))
elif value_type is float:
s = set((random.random() * 100. for i in range(n)))
while len(s) < n:
s.add(random.random() * 100.)
return s
139 changes: 139 additions & 0 deletions tests/kv/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import os
import asyncio
import time
import pytest

KIVY_STALL_TIMEOUT = 90

@pytest.fixture
def kivy_app(tmpdir, monkeypatch):
vidhub_conf = tmpdir.join('vidhubcontrol.json')
ui_conf = tmpdir.join('vidhubcontrol-ui.ini')

monkeypatch.setenv('KIVY_UNITTEST', '1')
monkeypatch.setattr('vidhubcontrol.runserver.Config.DEFAULT_FILENAME', str(vidhub_conf))

from kivy.clock import Clock
from kivy.base import runTouchApp, stopTouchApp
from vidhubcontrol.kivyui import main as kivy_main

aio_loop = asyncio.get_event_loop()
aio_loop.set_debug(True)

class AppOverride(kivy_main.VidhubControlApp):
def get_application_config(self):
return str(ui_conf)

def on_start(self, *args, **kwargs):
super().on_start(*args, **kwargs)
self._startup_ready.set()

def run(self):
kv_dir = os.path.dirname(os.path.abspath(kivy_main.__file__))
self.kv_file = os.path.join(kv_dir, 'vidhubcontrol.kv')
self._startup_ready = asyncio.Event()
if not self.built:
self.load_config()
self.load_kv(filename=self.kv_file)
root = self.build()
if root:
self.root = root

if self.root:
from kivy.core.window import Window
Window.add_widget(self.root)

# Check if the window is already created
from kivy.base import EventLoop
window = EventLoop.window
if window:
self._app_window = window
window.set_title(self.get_application_name())
icon = self.get_application_icon()
if icon:
window.set_icon(icon)
self._install_settings_keys(window)
else:
Logger.critical("Application: No window is created."
" Terminating application run.")
return
self._kv_loop = EventLoop
self._kv_loop_running = True
self._aio_running = True
self.dispatch('on_start')
runTouchApp(self.root, slave=True)
self._aio_mainloop_future = asyncio.ensure_future(self._aio_mainloop())

def stop(self):
self.dispatch('on_stop')

stopTouchApp()

# Clear the window children
if self._app_window:
for child in self._app_window.children:
self._app_window.remove_widget(child)

self._kv_loop_running = False

async def wait_for_widget_init(self, root=None):
if root is None:
root = self.root
def check_init():
for w in root.walk():
if w.parent is None:
return False
if 'app' in w.properties() and w.app is None:
return False
return True
while not check_init():
await asyncio.sleep(0)

async def start_async(self):
self.run()
await self._startup_ready.wait()
await self.wait_for_widget_init()

async def stop_async(self):
if self._kv_loop_running:
self.stop()
await self._aio_mainloop_future
while not self.async_server.thread_stop_event.is_set():
await asyncio.sleep(.1)

async def _aio_mainloop(self):
start_ts = aio_loop.time()
while not self._kv_loop.quit:
now = aio_loop.time()
if now >= start_ts + KIVY_STALL_TIMEOUT:
print('Exiting app. Runtime exceeded threshold')
raise KeyboardInterrupt()
self._kv_loop.idle()
await asyncio.sleep(0)
self._kv_loop.exit()

app = AppOverride()
return app

@pytest.fixture
def KvEventWaiter():
class KvEventWaiter_(object):
def __init__(self):
self.aio_event = asyncio.Event()
def bind(self, obj, *events):
kwargs = {e:self.kivy_callback for e in events}
obj.bind(**kwargs)
def unbind(self, obj, *events):
kwargs = {e:self.kivy_callback for e in events}
obj.unbind(**kwargs)
async def wait(self):
await self.aio_event.wait()
self.aio_event.clear()
async def bind_and_wait(self, obj, *events):
self.aio_event.clear()
self.bind(obj, *events)
await self.wait()
def kivy_callback(self, *args, **kwargs):
self.aio_event.set()

return KvEventWaiter_
50 changes: 50 additions & 0 deletions tests/kv/test_kv_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import asyncio
import pytest


@pytest.mark.asyncio
async def test_vidhub_dropdown(kivy_app, KvEventWaiter):
from vidhubcontrol.backends import DummyBackend

kv_waiter = KvEventWaiter()
kv_waiter.bind(kivy_app, 'on_start')
kivy_app.run()
await kv_waiter.wait()

config = kivy_app.vidhub_config
dropdown = kivy_app.root.header_widget.vidhub_dropdown
assert len(dropdown.btns) == 0

kv_waiter.bind(dropdown, 'btns')
vidhub1 = await DummyBackend.create_async(device_id='dummy1', device_name='Dummy 1')
vidhub2 = await DummyBackend.create_async(device_id='dummy2', device_name='Dummy 2')
config.add_vidhub(vidhub1)
await kv_waiter.wait()
config.add_vidhub(vidhub2)
await kv_waiter.wait()
kv_waiter.unbind(dropdown, 'btns')

assert 'dummy1' in dropdown.btns
assert 'dummy2' in dropdown.btns
btn = dropdown.btns['dummy1']
assert btn.vidhub is vidhub1
assert btn.text == vidhub1.device_name == 'Dummy 1'

kv_waiter.bind(kivy_app, 'selected_vidhub')
btn.dispatch('on_release')
await kv_waiter.wait()

assert kivy_app.selected_vidhub is vidhub1

await kivy_app.wait_for_widget_init()

btn = dropdown.btns['dummy2']
assert btn.vidhub is vidhub2
assert btn.text == vidhub2.device_name == 'Dummy 2'

assert not kv_waiter.aio_event.is_set()
btn.dispatch('on_release')
await kv_waiter.wait()
assert kivy_app.selected_vidhub is vidhub2

await kivy_app.stop_async()

0 comments on commit 2a0b8b1

Please sign in to comment.