Ajout du GUI
This commit is contained in:
11
kivy/tests/__init__.py
Normal file
11
kivy/tests/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from kivy.tests.common import GraphicUnitTest, UnitTestTouch, UTMotionEvent, \
|
||||
async_run
|
||||
try:
|
||||
from kivy.tests.async_common import UnitKivyApp
|
||||
except SyntaxError:
|
||||
# async app tests would be skipped due to async_run forcing it to skip so
|
||||
# it's ok to be None as it won't be used anyway
|
||||
UnitKivyApp = None
|
||||
|
||||
__all__ = ('GraphicUnitTest', 'UnitTestTouch', 'UTMotionEvent', 'async_run',
|
||||
'UnitKivyApp')
|
||||
BIN
kivy/tests/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/async_common.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/async_common.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/common.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/common.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/conftest.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/conftest.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/fixtures.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/fixtures.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/perf_test_textinput.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/perf_test_textinput.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_animations.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_animations.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_app.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_app.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_audio.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_audio.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_benchmark.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_benchmark.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_clipboard.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_clipboard.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_clock.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_clock.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_compat.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_compat.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_config.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_config.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_coverage.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_coverage.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_doc_gallery.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_doc_gallery.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_environ_cli.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_environ_cli.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_fbo_py2py3.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_fbo_py2py3.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_filechooser.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_filechooser.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_filechooser_unicode.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_filechooser_unicode.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_fonts.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_fonts.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_graphics.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_graphics.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_image.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_image.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_imageloader.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_imageloader.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_invalid_lang.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_invalid_lang.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_kivy_init.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_kivy_init.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_knspace.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_knspace.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_lang.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_lang.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_lang_complex.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_lang_complex.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
kivy/tests/__pycache__/test_logger.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_logger.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_metrics.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_metrics.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_module_inspector.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_module_inspector.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_motion_event.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_motion_event.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_mouse_hover_event.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_mouse_hover_event.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_mouse_multitouchsim.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_mouse_multitouchsim.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_multistroke.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_multistroke.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_properties.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_properties.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_resources.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_resources.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_rst_replace.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_rst_replace.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_screen.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_screen.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_storage.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_storage.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_uix_actionbar.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_uix_actionbar.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_uix_anchorlayout.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_uix_anchorlayout.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_uix_asyncimage.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_uix_asyncimage.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_uix_boxlayout.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_uix_boxlayout.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_uix_bubble.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_uix_bubble.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_uix_carousel.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_uix_carousel.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_uix_dropdown.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_uix_dropdown.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_uix_gridlayout.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_uix_gridlayout.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_uix_layout.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_uix_layout.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_uix_modal.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_uix_modal.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
kivy/tests/__pycache__/test_uix_relativelayout.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_uix_relativelayout.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_uix_scrollview.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_uix_scrollview.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_uix_slider.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_uix_slider.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_uix_stacklayout.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_uix_stacklayout.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_uix_textinput.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_uix_textinput.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
kivy/tests/__pycache__/test_uix_videoplayer.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_uix_videoplayer.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_uix_widget.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_uix_widget.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_urlrequest.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_urlrequest.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_utils.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_utils.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_vector.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_vector.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_video.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_video.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_weakmethod.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_weakmethod.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_widget.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_widget.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_widget_walk.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_widget_walk.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_window_base.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_window_base.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/test_window_info.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/test_window_info.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/tests/__pycache__/visual_test_label.cpython-310.pyc
Normal file
BIN
kivy/tests/__pycache__/visual_test_label.cpython-310.pyc
Normal file
Binary file not shown.
568
kivy/tests/async_common.py
Normal file
568
kivy/tests/async_common.py
Normal file
@@ -0,0 +1,568 @@
|
||||
"""
|
||||
.. warning::
|
||||
|
||||
The classes in this file are internal and may well be removed to an
|
||||
external kivy-pytest package or similar in the future. Use at your own
|
||||
risk.
|
||||
"""
|
||||
import random
|
||||
import time
|
||||
import math
|
||||
import os
|
||||
from collections import deque
|
||||
|
||||
from kivy.tests import UnitTestTouch
|
||||
|
||||
__all__ = ('UnitKivyApp', )
|
||||
|
||||
|
||||
class AsyncUnitTestTouch(UnitTestTouch):
|
||||
|
||||
def __init__(self, *largs, **kwargs):
|
||||
self.grab_exclusive_class = None
|
||||
self.is_touch = True
|
||||
super(AsyncUnitTestTouch, self).__init__(*largs, **kwargs)
|
||||
|
||||
def touch_down(self, *args):
|
||||
self.eventloop._dispatch_input("begin", self)
|
||||
|
||||
def touch_move(self, x, y):
|
||||
win = self.eventloop.window
|
||||
self.move({
|
||||
"x": x / (win.width - 1.0),
|
||||
"y": y / (win.height - 1.0)
|
||||
})
|
||||
self.eventloop._dispatch_input("update", self)
|
||||
|
||||
def touch_up(self, *args):
|
||||
self.eventloop._dispatch_input("end", self)
|
||||
|
||||
|
||||
_unique_value = object
|
||||
|
||||
|
||||
class WidgetResolver(object):
|
||||
"""It assumes that the widget tree strictly forms a DAG.
|
||||
"""
|
||||
|
||||
base_widget = None
|
||||
|
||||
matched_widget = None
|
||||
|
||||
_kwargs_filter = {}
|
||||
|
||||
_funcs_filter = []
|
||||
|
||||
def __init__(self, base_widget, **kwargs):
|
||||
self.base_widget = base_widget
|
||||
self._kwargs_filter = {}
|
||||
self._funcs_filter = []
|
||||
super(WidgetResolver, self).__init__(**kwargs)
|
||||
|
||||
def __call__(self):
|
||||
if self.matched_widget is not None:
|
||||
return self.matched_widget
|
||||
|
||||
if not self._kwargs_filter and not self._funcs_filter:
|
||||
return self.base_widget
|
||||
return None
|
||||
|
||||
def match(self, **kwargs_filter):
|
||||
self._kwargs_filter.update(kwargs_filter)
|
||||
|
||||
def match_funcs(self, funcs_filter=()):
|
||||
self._funcs_filter.extend(funcs_filter)
|
||||
|
||||
def check_widget(self, widget):
|
||||
if not all(func(widget) for func in self._funcs_filter):
|
||||
return False
|
||||
|
||||
for attr, val in self._kwargs_filter.items():
|
||||
if getattr(widget, attr, _unique_value) != val:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def not_found(self, op):
|
||||
raise ValueError(
|
||||
'Cannot find widget matching <{}, {}> starting from base '
|
||||
'widget "{}" doing "{}" traversal'.format(
|
||||
self._kwargs_filter, self._funcs_filter, self.base_widget, op))
|
||||
|
||||
def down(self, **kwargs_filter):
|
||||
self.match(**kwargs_filter)
|
||||
check = self.check_widget
|
||||
|
||||
fifo = deque([self.base_widget])
|
||||
while fifo:
|
||||
widget = fifo.popleft()
|
||||
if check(widget):
|
||||
return WidgetResolver(base_widget=widget)
|
||||
|
||||
fifo.extend(widget.children)
|
||||
|
||||
self.not_found('down')
|
||||
|
||||
def up(self, **kwargs_filter):
|
||||
self.match(**kwargs_filter)
|
||||
check = self.check_widget
|
||||
|
||||
parent = self.base_widget
|
||||
while parent is not None:
|
||||
if check(parent):
|
||||
return WidgetResolver(base_widget=parent)
|
||||
|
||||
new_parent = parent.parent
|
||||
# Window is its own parent oO
|
||||
if new_parent is parent:
|
||||
break
|
||||
parent = new_parent
|
||||
|
||||
self.not_found('up')
|
||||
|
||||
def family_up(self, **kwargs_filter):
|
||||
self.match(**kwargs_filter)
|
||||
check = self.check_widget
|
||||
|
||||
base_widget = self.base_widget
|
||||
already_checked_base = None
|
||||
while base_widget is not None:
|
||||
fifo = deque([base_widget])
|
||||
while fifo:
|
||||
widget = fifo.popleft()
|
||||
# don't check the child we checked before moving up
|
||||
if widget is already_checked_base:
|
||||
continue
|
||||
|
||||
if check(widget):
|
||||
return WidgetResolver(base_widget=widget)
|
||||
|
||||
fifo.extend(widget.children)
|
||||
|
||||
already_checked_base = base_widget
|
||||
new_base_widget = base_widget.parent
|
||||
# Window is its own parent oO
|
||||
if new_base_widget is base_widget:
|
||||
break
|
||||
base_widget = new_base_widget
|
||||
|
||||
self.not_found('family_up')
|
||||
|
||||
|
||||
class UnitKivyApp(object):
|
||||
"""Base class to use with async test apps.
|
||||
|
||||
.. warning::
|
||||
|
||||
The classes in this file are internal and may well be removed to an
|
||||
external kivy-pytest package or similar in the future. Use at your own
|
||||
risk.
|
||||
"""
|
||||
|
||||
app_has_started = False
|
||||
|
||||
app_has_stopped = False
|
||||
|
||||
async_sleep = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def started_app(*largs):
|
||||
self.app_has_started = True
|
||||
self.fbind('on_start', started_app)
|
||||
|
||||
def stopped_app(*largs):
|
||||
self.app_has_stopped = True
|
||||
self.fbind('on_stop', stopped_app)
|
||||
|
||||
def set_async_lib(self, async_lib):
|
||||
from kivy.clock import Clock
|
||||
if async_lib is not None:
|
||||
Clock.init_async_lib(async_lib)
|
||||
self.async_sleep = Clock._async_lib.sleep
|
||||
|
||||
async def async_run(self, async_lib=None):
|
||||
self.set_async_lib(async_lib)
|
||||
return await super(UnitKivyApp, self).async_run(async_lib=async_lib)
|
||||
|
||||
def resolve_widget(self, base_widget=None):
|
||||
if base_widget is None:
|
||||
from kivy.core.window import Window
|
||||
base_widget = Window
|
||||
return WidgetResolver(base_widget=base_widget)
|
||||
|
||||
async def wait_clock_frames(self, n, sleep_time=1 / 60.):
|
||||
from kivy.clock import Clock
|
||||
frames_start = Clock.frames
|
||||
while Clock.frames < frames_start + n:
|
||||
await self.async_sleep(sleep_time)
|
||||
|
||||
def get_widget_pos_pixel(self, widget, positions):
|
||||
from kivy.graphics import Fbo, ClearColor, ClearBuffers
|
||||
|
||||
canvas_parent_index = -2
|
||||
if widget.parent is not None:
|
||||
canvas_parent_index = widget.parent.canvas.indexof(widget.canvas)
|
||||
if canvas_parent_index > -1:
|
||||
widget.parent.canvas.remove(widget.canvas)
|
||||
|
||||
w, h = int(widget.width), int(widget.height)
|
||||
fbo = Fbo(size=(w, h), with_stencilbuffer=True)
|
||||
|
||||
with fbo:
|
||||
ClearColor(0, 0, 0, 0)
|
||||
ClearBuffers()
|
||||
|
||||
fbo.add(widget.canvas)
|
||||
fbo.draw()
|
||||
pixels = fbo.pixels
|
||||
fbo.remove(widget.canvas)
|
||||
|
||||
if widget.parent is not None and canvas_parent_index > -1:
|
||||
widget.parent.canvas.insert(canvas_parent_index, widget.canvas)
|
||||
|
||||
values = []
|
||||
for x, y in positions:
|
||||
x = int(x)
|
||||
y = int(y)
|
||||
i = y * w * 4 + x * 4
|
||||
values.append(tuple(pixels[i:i + 4]))
|
||||
|
||||
return values
|
||||
|
||||
async def do_touch_down_up(
|
||||
self, pos=None, widget=None, duration=.2, pos_jitter=None,
|
||||
widget_jitter=False, jitter_dt=1 / 15., end_on_pos=False):
|
||||
if widget is None:
|
||||
x, y = pos
|
||||
else:
|
||||
if pos is None:
|
||||
x, y = widget.to_window(*widget.center)
|
||||
else:
|
||||
x, y = widget.to_window(*pos, initial=False)
|
||||
touch = AsyncUnitTestTouch(x, y)
|
||||
|
||||
ts = time.perf_counter()
|
||||
touch.touch_down()
|
||||
await self.wait_clock_frames(1)
|
||||
yield 'down', touch.pos
|
||||
|
||||
if not pos_jitter and not widget_jitter:
|
||||
await self.async_sleep(duration)
|
||||
touch.touch_up()
|
||||
await self.wait_clock_frames(1)
|
||||
yield 'up', touch.pos
|
||||
|
||||
return
|
||||
|
||||
moved = False
|
||||
if pos_jitter:
|
||||
dx, dy = pos_jitter
|
||||
else:
|
||||
dx = widget.width / 2.
|
||||
dy = widget.height / 2.
|
||||
|
||||
while time.perf_counter() - ts < duration:
|
||||
moved = True
|
||||
await self.async_sleep(jitter_dt)
|
||||
|
||||
touch.touch_move(
|
||||
x + (random.random() * 2 - 1) * dx,
|
||||
y + (random.random() * 2 - 1) * dy
|
||||
)
|
||||
await self.wait_clock_frames(1)
|
||||
yield 'move', touch.pos
|
||||
|
||||
if end_on_pos and moved:
|
||||
touch.touch_move(x, y)
|
||||
await self.wait_clock_frames(1)
|
||||
yield 'move', touch.pos
|
||||
|
||||
touch.touch_up()
|
||||
await self.wait_clock_frames(1)
|
||||
yield 'up', touch.pos
|
||||
|
||||
async def do_touch_drag(
|
||||
self, pos=None, widget=None,
|
||||
widget_loc=('center_x', 'center_y'), dx=0, dy=0,
|
||||
target_pos=None, target_widget=None, target_widget_offset=(0, 0),
|
||||
target_widget_loc=('center_x', 'center_y'), long_press=0,
|
||||
duration=.2, drag_n=5):
|
||||
"""Initiates a touch down, followed by some dragging to a target
|
||||
location, ending with a touch up.
|
||||
|
||||
`origin`: These parameters specify where the drag starts.
|
||||
- If ``widget`` is None, it starts at ``pos`` (in window coordinates).
|
||||
If ``dx``/``dy`` is used, it is in the window coordinate system also.
|
||||
- If ``pos`` is None, it starts on the ``widget`` as specified by
|
||||
``widget_loc``. If ``dx``/``dy`` is used, it is in the ``widget``
|
||||
coordinate system.
|
||||
- If neither is None, it starts at ``pos``, but in the ``widget``'s
|
||||
coordinate system (:meth:`~kivy.uix.widget.Widget.to_window` is used
|
||||
on it). If ``dx``/``dy`` is used, it is in the ``widget``
|
||||
coordinate system.
|
||||
|
||||
`target`: These parameters specify where the drag ends.
|
||||
- If ``target_pos`` and ``target_widget`` is None, then ``dx`` and
|
||||
``dy`` is used relative to the position where the drag started.
|
||||
- If ``target_widget`` is None, it ends at ``target_pos``
|
||||
(in window coordinates).
|
||||
- If ``target_pos`` is None, it ends on the ``target_widget`` as
|
||||
specified by ``target_widget_loc``. ``target_widget_offset``, is an
|
||||
additional ``(x, y)`` offset relative to ``target_widget_loc``.
|
||||
- If neither is None, it starts at ``target_pos``, but in the
|
||||
``target_widget``'s coordinate system
|
||||
(:meth:`~kivy.uix.widget.Widget.to_window` is used on it).
|
||||
|
||||
When ``widget`` and/or ``target_widget`` are specified, ``widget_loc``
|
||||
and ``target_widget_loc``, respectively, indicate where on the widget
|
||||
the drag starts/ends. It is a a tuple with property names of the widget
|
||||
to loop up to get the value. The default is
|
||||
``('center_x', 'center_y')`` so the drag would start/end in the
|
||||
widget's center.
|
||||
"""
|
||||
if widget is None:
|
||||
x, y = pos
|
||||
tx, ty = x + dx, y + dy
|
||||
else:
|
||||
if pos is None:
|
||||
w_x = getattr(widget, widget_loc[0])
|
||||
w_y = getattr(widget, widget_loc[1])
|
||||
x, y = widget.to_window(w_x, w_y)
|
||||
tx, ty = widget.to_window(w_x + dx, w_y + dy)
|
||||
else:
|
||||
x, y = widget.to_window(*pos, initial=False)
|
||||
tx, ty = widget.to_window(
|
||||
pos[0] + dx, pos[1] + dy, initial=False)
|
||||
|
||||
if target_pos is not None:
|
||||
if target_widget is None:
|
||||
tx, ty = target_pos
|
||||
else:
|
||||
tx, ty = target_pos = target_widget.to_window(
|
||||
*target_pos, initial=False)
|
||||
elif target_widget is not None:
|
||||
x_off, y_off = target_widget_offset
|
||||
w_x = getattr(target_widget, target_widget_loc[0]) + x_off
|
||||
w_y = getattr(target_widget, target_widget_loc[1]) + y_off
|
||||
tx, ty = target_pos = target_widget.to_window(w_x, w_y)
|
||||
else:
|
||||
target_pos = tx, ty
|
||||
|
||||
touch = AsyncUnitTestTouch(x, y)
|
||||
|
||||
touch.touch_down()
|
||||
await self.wait_clock_frames(1)
|
||||
if long_press:
|
||||
await self.async_sleep(long_press)
|
||||
yield 'down', touch.pos
|
||||
|
||||
dx = (tx - x) / drag_n
|
||||
dy = (ty - y) / drag_n
|
||||
|
||||
ts0 = time.perf_counter()
|
||||
for i in range(drag_n):
|
||||
await self.async_sleep(
|
||||
max(0., duration - (time.perf_counter() - ts0)) / (drag_n - i))
|
||||
|
||||
touch.touch_move(x + (i + 1) * dx, y + (i + 1) * dy)
|
||||
await self.wait_clock_frames(1)
|
||||
yield 'move', touch.pos
|
||||
|
||||
if touch.pos != target_pos:
|
||||
touch.touch_move(*target_pos)
|
||||
await self.wait_clock_frames(1)
|
||||
yield 'move', touch.pos
|
||||
|
||||
touch.touch_up()
|
||||
await self.wait_clock_frames(1)
|
||||
yield 'up', touch.pos
|
||||
|
||||
async def do_touch_drag_follow(
|
||||
self, pos=None, widget=None,
|
||||
widget_loc=('center_x', 'center_y'),
|
||||
target_pos=None, target_widget=None, target_widget_offset=(0, 0),
|
||||
target_widget_loc=('center_x', 'center_y'), long_press=0,
|
||||
duration=.2, drag_n=5, max_n=25):
|
||||
"""Very similar to :meth:`do_touch_drag`, except it follows the target
|
||||
widget, even if the target widget moves as a result of the drag, the
|
||||
drag will follow it until it's on the target widget.
|
||||
|
||||
`origin`: These parameters specify where the drag starts.
|
||||
- If ``widget`` is None, it starts at ``pos`` (in window coordinates).
|
||||
- If ``pos`` is None, it starts on the ``widget`` as specified by
|
||||
``widget_loc``.
|
||||
- If neither is None, it starts at ``pos``, but in the ``widget``'s
|
||||
coordinate system (:meth:`~kivy.uix.widget.Widget.to_window` is used
|
||||
on it).
|
||||
|
||||
`target`: These parameters specify where the drag ends.
|
||||
- If ``target_pos`` is None, it ends on the ``target_widget`` as
|
||||
specified by ``target_widget_loc``. ``target_widget_offset``, is an
|
||||
additional ``(x, y)`` offset relative to ``target_widget_loc``.
|
||||
- If ``target_pos`` is not None, it starts at ``target_pos``, but in
|
||||
the ``target_widget``'s coordinate system
|
||||
(:meth:`~kivy.uix.widget.Widget.to_window` is used on it).
|
||||
|
||||
When ``widget`` and/or ``target_widget`` are specified, ``widget_loc``
|
||||
and ``target_widget_loc``, respectively, indicate where on the widget
|
||||
the drag starts/ends. It is a a tuple with property names of the widget
|
||||
to loop up to get the value. The default is
|
||||
``('center_x', 'center_y')`` so the drag would start/end in the
|
||||
widget's center.
|
||||
"""
|
||||
if widget is None:
|
||||
x, y = pos
|
||||
else:
|
||||
if pos is None:
|
||||
w_x = getattr(widget, widget_loc[0])
|
||||
w_y = getattr(widget, widget_loc[1])
|
||||
x, y = widget.to_window(w_x, w_y)
|
||||
else:
|
||||
x, y = widget.to_window(*pos, initial=False)
|
||||
|
||||
if target_widget is None:
|
||||
raise ValueError('target_widget must be specified')
|
||||
|
||||
def get_target():
|
||||
if target_pos is not None:
|
||||
return target_widget.to_window(*target_pos, initial=False)
|
||||
else:
|
||||
x_off, y_off = target_widget_offset
|
||||
wt_x = getattr(target_widget, target_widget_loc[0]) + x_off
|
||||
wt_y = getattr(target_widget, target_widget_loc[1]) + y_off
|
||||
return target_widget.to_window(wt_x, wt_y)
|
||||
|
||||
touch = AsyncUnitTestTouch(x, y)
|
||||
|
||||
touch.touch_down()
|
||||
await self.wait_clock_frames(1)
|
||||
if long_press:
|
||||
await self.async_sleep(long_press)
|
||||
yield 'down', touch.pos
|
||||
|
||||
ts0 = time.perf_counter()
|
||||
tx, ty = get_target()
|
||||
i = 0
|
||||
while not (math.isclose(touch.x, tx) and math.isclose(touch.y, ty)):
|
||||
if i >= max_n:
|
||||
raise Exception(
|
||||
'Exceeded the maximum number of iterations, '
|
||||
'but {} != {}'.format(touch.pos, (tx, ty)))
|
||||
|
||||
rem_i = max(1, drag_n - i)
|
||||
rem_t = max(0., duration - (time.perf_counter() - ts0)) / rem_i
|
||||
i += 1
|
||||
await self.async_sleep(rem_t)
|
||||
|
||||
x, y = touch.pos
|
||||
touch.touch_move(x + (tx - x) / rem_i, y + (ty - y) / rem_i)
|
||||
await self.wait_clock_frames(1)
|
||||
yield 'move', touch.pos
|
||||
tx, ty = get_target()
|
||||
|
||||
touch.touch_up()
|
||||
await self.wait_clock_frames(1)
|
||||
yield 'up', touch.pos
|
||||
|
||||
async def do_touch_drag_path(
|
||||
self, path, axis_widget=None, long_press=0, duration=.2):
|
||||
"""Drags the touch along the specified path.
|
||||
|
||||
:parameters:
|
||||
|
||||
`path`: list
|
||||
A list of position tuples the touch will follow. The first
|
||||
item is used for the touch down and the rest for the move.
|
||||
`axis_widget`: a Widget
|
||||
If None, the path coordinates is in window coordinates,
|
||||
otherwise, we will first transform the path coordinates
|
||||
to window coordinates using
|
||||
:meth:`~kivy.uix.widget.Widget.to_window` of the specified
|
||||
widget.
|
||||
"""
|
||||
if axis_widget is not None:
|
||||
path = [axis_widget.to_window(*p, initial=False) for p in path]
|
||||
x, y = path[0]
|
||||
path = path[1:]
|
||||
|
||||
touch = AsyncUnitTestTouch(x, y)
|
||||
|
||||
touch.touch_down()
|
||||
await self.wait_clock_frames(1)
|
||||
if long_press:
|
||||
await self.async_sleep(long_press)
|
||||
yield 'down', touch.pos
|
||||
|
||||
ts0 = time.perf_counter()
|
||||
n = len(path)
|
||||
for i, (x2, y2) in enumerate(path):
|
||||
await self.async_sleep(
|
||||
max(0., duration - (time.perf_counter() - ts0)) / (n - i))
|
||||
|
||||
touch.touch_move(x2, y2)
|
||||
await self.wait_clock_frames(1)
|
||||
yield 'move', touch.pos
|
||||
|
||||
touch.touch_up()
|
||||
await self.wait_clock_frames(1)
|
||||
yield 'up', touch.pos
|
||||
|
||||
async def do_keyboard_key(
|
||||
self, key, modifiers=(), duration=.05, num_press=1):
|
||||
from kivy.core.window import Window
|
||||
if key == ' ':
|
||||
key = 'spacebar'
|
||||
key_lower = key.lower()
|
||||
key_code = Window._system_keyboard.string_to_keycode(key_lower)
|
||||
|
||||
known_modifiers = {'shift', 'alt', 'ctrl', 'meta'}
|
||||
if set(modifiers) - known_modifiers:
|
||||
raise ValueError('Unknown modifiers "{}"'.
|
||||
format(set(modifiers) - known_modifiers))
|
||||
|
||||
special_keys = {
|
||||
27: 'escape',
|
||||
9: 'tab',
|
||||
8: 'backspace',
|
||||
13: 'enter',
|
||||
127: 'del',
|
||||
271: 'enter',
|
||||
273: 'up',
|
||||
274: 'down',
|
||||
275: 'right',
|
||||
276: 'left',
|
||||
278: 'home',
|
||||
279: 'end',
|
||||
280: 'pgup',
|
||||
281: 'pgdown',
|
||||
300: 'numlock',
|
||||
301: 'capslock',
|
||||
145: 'screenlock',
|
||||
}
|
||||
|
||||
text = None
|
||||
try:
|
||||
text = chr(key_code)
|
||||
if key_lower != key:
|
||||
text = key
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
dt = duration / num_press
|
||||
for i in range(num_press):
|
||||
await self.async_sleep(dt)
|
||||
|
||||
Window.dispatch('on_key_down', key_code, 0, text, modifiers)
|
||||
if (key not in known_modifiers and
|
||||
key_code not in special_keys and
|
||||
not (known_modifiers & set(modifiers))):
|
||||
Window.dispatch('on_textinput', text)
|
||||
|
||||
await self.wait_clock_frames(1)
|
||||
yield 'down', (key, key_code, 0, text, modifiers)
|
||||
|
||||
Window.dispatch('on_key_up', key_code, 0)
|
||||
await self.wait_clock_frames(1)
|
||||
yield 'up', (key, key_code, 0, text, modifiers)
|
||||
524
kivy/tests/common.py
Normal file
524
kivy/tests/common.py
Normal file
@@ -0,0 +1,524 @@
|
||||
'''
|
||||
This is a extended unittest module for Kivy, to make unittests based on
|
||||
graphics with an OpenGL context.
|
||||
|
||||
The idea is to render a Widget tree, and after 1, 2 or more frames, a
|
||||
screenshot will be made and be compared to the original one.
|
||||
If no screenshot exists for the current test, the very first one will be used.
|
||||
|
||||
The screenshots live in the 'kivy/tests/results' folder and are in PNG format,
|
||||
320x240 pixels.
|
||||
'''
|
||||
|
||||
__all__ = (
|
||||
'GraphicUnitTest', 'UnitTestTouch', 'UTMotionEvent', 'async_run',
|
||||
'requires_graphics', 'ensure_web_server')
|
||||
|
||||
import unittest
|
||||
import logging
|
||||
import pytest
|
||||
import sys
|
||||
from functools import partial
|
||||
import os
|
||||
import threading
|
||||
from kivy.graphics.cgl import cgl_get_backend_name
|
||||
from kivy.input.motionevent import MotionEvent
|
||||
log = logging.getLogger('unittest')
|
||||
|
||||
|
||||
_base = object
|
||||
if 'mock' != cgl_get_backend_name():
|
||||
# check what the gl backend might be, we can't know for sure
|
||||
# what it'll be until actually initialized by the window.
|
||||
_base = unittest.TestCase
|
||||
|
||||
make_screenshots = os.environ.get('KIVY_UNITTEST_SCREENSHOTS')
|
||||
http_server = None
|
||||
http_server_ready = threading.Event()
|
||||
kivy_eventloop = os.environ.get('KIVY_EVENTLOOP', 'asyncio')
|
||||
|
||||
|
||||
def requires_graphics(func):
|
||||
if 'mock' == cgl_get_backend_name():
|
||||
return pytest.mark.skip(
|
||||
reason='Skipping because gl backend is set to mock')(func)
|
||||
return func
|
||||
|
||||
|
||||
def ensure_web_server(root=None):
|
||||
if http_server is not None:
|
||||
return True
|
||||
|
||||
if not root:
|
||||
root = os.path.join(os.path.dirname(__file__), "..", "..")
|
||||
need_chdir = sys.version_info.major == 3 and sys.version_info.minor <= 6
|
||||
curr_dir = os.getcwd()
|
||||
|
||||
def _start_web_server():
|
||||
global http_server
|
||||
from http.server import SimpleHTTPRequestHandler
|
||||
from socketserver import TCPServer
|
||||
|
||||
try:
|
||||
if need_chdir:
|
||||
os.chdir(root)
|
||||
handler = SimpleHTTPRequestHandler
|
||||
else:
|
||||
handler = partial(SimpleHTTPRequestHandler, directory=root)
|
||||
|
||||
http_server = TCPServer(
|
||||
("", 8000), handler, bind_and_activate=False)
|
||||
http_server.daemon_threads = True
|
||||
http_server.allow_reuse_address = True
|
||||
http_server.server_bind()
|
||||
http_server.server_activate()
|
||||
http_server_ready.set()
|
||||
http_server.serve_forever()
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
http_server = None
|
||||
http_server_ready.set()
|
||||
|
||||
if need_chdir:
|
||||
os.chdir(curr_dir)
|
||||
|
||||
th = threading.Thread(target=_start_web_server)
|
||||
th.daemon = True
|
||||
th.start()
|
||||
http_server_ready.wait()
|
||||
if http_server is None:
|
||||
raise Exception("Unable to start webserver")
|
||||
|
||||
|
||||
class GraphicUnitTest(_base):
|
||||
framecount = 0
|
||||
|
||||
def _force_refresh(self, *largs):
|
||||
# this prevent in some case to be stuck if the screen doesn't refresh
|
||||
# and we wait for a number of self.framecount that never goes down
|
||||
from kivy.base import EventLoop
|
||||
win = EventLoop.window
|
||||
if win and win.canvas:
|
||||
win.canvas.ask_update()
|
||||
|
||||
def render(self, root, framecount=1):
|
||||
'''Call rendering process using the `root` widget.
|
||||
The screenshot will be done in `framecount` frames.
|
||||
'''
|
||||
from kivy.base import runTouchApp
|
||||
from kivy.clock import Clock
|
||||
self.framecount = framecount
|
||||
try:
|
||||
Clock.schedule_interval(self._force_refresh, 1)
|
||||
runTouchApp(root)
|
||||
finally:
|
||||
Clock.unschedule(self._force_refresh)
|
||||
|
||||
# reset for the next test, but nobody will know if it will be used :/
|
||||
if self.test_counter != 0:
|
||||
self.tearDown(fake=True)
|
||||
self.setUp()
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
'''Extend the run of unittest, to check if results directory have been
|
||||
found. If no results directory exists, the test will be ignored.
|
||||
'''
|
||||
from os.path import join, dirname, exists
|
||||
results_dir = join(dirname(__file__), 'results')
|
||||
if make_screenshots and not exists(results_dir):
|
||||
log.warning('No result directory found, cancel test.')
|
||||
os.mkdir(results_dir)
|
||||
self.test_counter = 0
|
||||
self.results_dir = results_dir
|
||||
self.test_failed = False
|
||||
return super(GraphicUnitTest, self).run(*args, **kwargs)
|
||||
|
||||
def setUp(self):
|
||||
'''Prepare the graphic test, with:
|
||||
- Window size fixed to 320x240
|
||||
- Default kivy configuration
|
||||
- Without any kivy input
|
||||
'''
|
||||
|
||||
# use default kivy configuration (don't load user file.)
|
||||
from os import environ
|
||||
environ['KIVY_USE_DEFAULTCONFIG'] = '1'
|
||||
|
||||
# force window size + remove all inputs
|
||||
from kivy.config import Config
|
||||
Config.set('graphics', 'width', '320')
|
||||
Config.set('graphics', 'height', '240')
|
||||
for items in Config.items('input'):
|
||||
Config.remove_option('input', items[0])
|
||||
|
||||
# bind ourself for the later screenshot
|
||||
from kivy.core.window import Window
|
||||
self.Window = Window
|
||||
Window.bind(on_flip=self.on_window_flip)
|
||||
|
||||
# ensure our window is correctly created
|
||||
Window.create_window()
|
||||
Window.register()
|
||||
Window.initialized = True
|
||||
Window.close = lambda *s: None
|
||||
self.clear_window_and_event_loop()
|
||||
|
||||
def clear_window_and_event_loop(self):
|
||||
from kivy.base import EventLoop
|
||||
window = self.Window
|
||||
for child in window.children[:]:
|
||||
window.remove_widget(child)
|
||||
window.canvas.before.clear()
|
||||
window.canvas.clear()
|
||||
window.canvas.after.clear()
|
||||
EventLoop.touches.clear()
|
||||
for post_proc in EventLoop.postproc_modules:
|
||||
if hasattr(post_proc, 'touches'):
|
||||
post_proc.touches.clear()
|
||||
elif hasattr(post_proc, 'last_touches'):
|
||||
post_proc.last_touches.clear()
|
||||
|
||||
def on_window_flip(self, window):
|
||||
'''Internal method to be called when the window have just displayed an
|
||||
image.
|
||||
When an image is showed, we decrement our framecount. If framecount is
|
||||
come to 0, we are taking the screenshot.
|
||||
|
||||
The screenshot is done in a temporary place, and is compared to the
|
||||
original one -> test ok/ko.
|
||||
If no screenshot is available in the results directory, a new one will
|
||||
be created.
|
||||
'''
|
||||
from kivy.base import EventLoop
|
||||
from tempfile import mkstemp
|
||||
from os.path import join, exists
|
||||
from os import unlink, close
|
||||
from shutil import move, copy
|
||||
|
||||
# don't save screenshot until we have enough frames.
|
||||
# log.debug('framecount %d' % self.framecount)
|
||||
# ! check if there is 'framecount', otherwise just
|
||||
# ! assume zero e.g. if handling runTouchApp manually
|
||||
self.framecount = getattr(self, 'framecount', 0) - 1
|
||||
if self.framecount > 0:
|
||||
return
|
||||
|
||||
# don't create screenshots if not requested manually
|
||||
if not make_screenshots:
|
||||
EventLoop.stop()
|
||||
return
|
||||
|
||||
reffn = None
|
||||
match = False
|
||||
try:
|
||||
# just get a temporary name
|
||||
fd, tmpfn = mkstemp(suffix='.png', prefix='kivyunit-')
|
||||
close(fd)
|
||||
unlink(tmpfn)
|
||||
|
||||
# get a filename for the current unit test
|
||||
self.test_counter += 1
|
||||
test_uid = '%s-%d.png' % (
|
||||
'_'.join(self.id().split('.')[-2:]),
|
||||
self.test_counter)
|
||||
|
||||
# capture the screen
|
||||
log.info('Capturing screenshot for %s' % test_uid)
|
||||
tmpfn = window.screenshot(tmpfn)
|
||||
log.info('Capture saved at %s' % tmpfn)
|
||||
|
||||
# search the file to compare to
|
||||
reffn = join(self.results_dir, test_uid)
|
||||
log.info('Compare with %s' % reffn)
|
||||
|
||||
# get sourcecode
|
||||
import inspect
|
||||
frame = inspect.getouterframes(inspect.currentframe())[6]
|
||||
sourcecodetab, line = inspect.getsourcelines(frame[0])
|
||||
line = frame[2] - line
|
||||
currentline = sourcecodetab[line]
|
||||
sourcecodetab[line] = '<span style="color: red;">%s</span>' % (
|
||||
currentline)
|
||||
sourcecode = ''.join(sourcecodetab)
|
||||
sourcecodetab[line] = '>>>>>>>>\n%s<<<<<<<<\n' % currentline
|
||||
sourcecodeask = ''.join(sourcecodetab)
|
||||
|
||||
if not exists(reffn):
|
||||
log.info('No image reference, move %s as ref ?' % test_uid)
|
||||
if self.interactive_ask_ref(sourcecodeask, tmpfn, self.id()):
|
||||
move(tmpfn, reffn)
|
||||
tmpfn = reffn
|
||||
log.info('Image used as reference')
|
||||
match = True
|
||||
else:
|
||||
log.info('Image discarded')
|
||||
else:
|
||||
from kivy.core.image import Image as CoreImage
|
||||
s1 = CoreImage(tmpfn, keep_data=True)
|
||||
sd1 = s1.image._data[0].data
|
||||
s2 = CoreImage(reffn, keep_data=True)
|
||||
sd2 = s2.image._data[0].data
|
||||
if sd1 != sd2:
|
||||
log.critical(
|
||||
'%s at render() #%d, images are different.' % (
|
||||
self.id(), self.test_counter))
|
||||
if self.interactive_ask_diff(sourcecodeask,
|
||||
tmpfn, reffn, self.id()):
|
||||
log.critical('user ask to use it as ref.')
|
||||
move(tmpfn, reffn)
|
||||
tmpfn = reffn
|
||||
match = True
|
||||
else:
|
||||
self.test_failed = True
|
||||
else:
|
||||
match = True
|
||||
|
||||
# generate html
|
||||
from os.path import join, dirname, exists, basename
|
||||
from os import mkdir
|
||||
build_dir = join(dirname(__file__), 'build')
|
||||
if not exists(build_dir):
|
||||
mkdir(build_dir)
|
||||
copy(reffn, join(build_dir, 'ref_%s' % basename(reffn)))
|
||||
if tmpfn != reffn:
|
||||
copy(tmpfn, join(build_dir, 'test_%s' % basename(reffn)))
|
||||
with open(join(build_dir, 'index.html'), 'at') as fd:
|
||||
color = '#ffdddd' if not match else '#ffffff'
|
||||
fd.write('<div style="background-color: %s">' % color)
|
||||
fd.write('<h2>%s #%d</h2>' % (self.id(), self.test_counter))
|
||||
fd.write('<table><tr><th>Reference</th>'
|
||||
'<th>Test</th>'
|
||||
'<th>Comment</th>')
|
||||
fd.write('<tr><td><img src="ref_%s"/></td>' %
|
||||
basename(reffn))
|
||||
if tmpfn != reffn:
|
||||
fd.write('<td><img src="test_%s"/></td>' %
|
||||
basename(reffn))
|
||||
else:
|
||||
fd.write('<td>First time, no comparison.</td>')
|
||||
fd.write('<td><pre>%s</pre></td>' % sourcecode)
|
||||
fd.write('</table></div>')
|
||||
finally:
|
||||
try:
|
||||
if reffn != tmpfn:
|
||||
unlink(tmpfn)
|
||||
except:
|
||||
pass
|
||||
EventLoop.stop()
|
||||
|
||||
def tearDown(self, fake=False):
|
||||
'''When the test is finished, stop the application, and unbind our
|
||||
current flip callback.
|
||||
'''
|
||||
from kivy.base import stopTouchApp
|
||||
from kivy.core.window import Window
|
||||
Window.unbind(on_flip=self.on_window_flip)
|
||||
self.clear_window_and_event_loop()
|
||||
self.Window = None
|
||||
stopTouchApp()
|
||||
if not fake and self.test_failed:
|
||||
self.assertTrue(False)
|
||||
super(GraphicUnitTest, self).tearDown()
|
||||
|
||||
def interactive_ask_ref(self, code, imagefn, testid):
|
||||
from os import environ
|
||||
if 'UNITTEST_INTERACTIVE' not in environ:
|
||||
return True
|
||||
|
||||
from tkinter import Tk, Label, LEFT, RIGHT, BOTTOM, Button
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
self.retval = False
|
||||
|
||||
root = Tk()
|
||||
|
||||
def do_close():
|
||||
root.destroy()
|
||||
|
||||
def do_yes():
|
||||
self.retval = True
|
||||
do_close()
|
||||
|
||||
image = Image.open(imagefn)
|
||||
photo = ImageTk.PhotoImage(image)
|
||||
Label(root, text='The test %s\nhave no reference.' % testid).pack()
|
||||
Label(root, text='Use this image as a reference ?').pack()
|
||||
Label(root, text=code, justify=LEFT).pack(side=RIGHT)
|
||||
Label(root, image=photo).pack(side=LEFT)
|
||||
Button(root, text='Use as reference', command=do_yes).pack(side=BOTTOM)
|
||||
Button(root, text='Discard', command=do_close).pack(side=BOTTOM)
|
||||
root.mainloop()
|
||||
|
||||
return self.retval
|
||||
|
||||
def interactive_ask_diff(self, code, tmpfn, reffn, testid):
|
||||
from os import environ
|
||||
if 'UNITTEST_INTERACTIVE' not in environ:
|
||||
return False
|
||||
|
||||
from tkinter import Tk, Label, LEFT, RIGHT, BOTTOM, Button
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
self.retval = False
|
||||
|
||||
root = Tk()
|
||||
|
||||
def do_close():
|
||||
root.destroy()
|
||||
|
||||
def do_yes():
|
||||
self.retval = True
|
||||
do_close()
|
||||
|
||||
phototmp = ImageTk.PhotoImage(Image.open(tmpfn))
|
||||
photoref = ImageTk.PhotoImage(Image.open(reffn))
|
||||
Label(root, text='The test %s\nhave generated an different'
|
||||
'image as the reference one..' % testid).pack()
|
||||
Label(root, text='Which one is good ?').pack()
|
||||
Label(root, text=code, justify=LEFT).pack(side=RIGHT)
|
||||
Label(root, image=phototmp).pack(side=RIGHT)
|
||||
Label(root, image=photoref).pack(side=LEFT)
|
||||
Button(root, text='Use the new image -->',
|
||||
command=do_yes).pack(side=BOTTOM)
|
||||
Button(root, text='<-- Use the reference',
|
||||
command=do_close).pack(side=BOTTOM)
|
||||
root.mainloop()
|
||||
|
||||
return self.retval
|
||||
|
||||
def advance_frames(self, count):
|
||||
'''Render the new frames and:
|
||||
|
||||
* tick the Clock
|
||||
* dispatch input from all registered providers
|
||||
* flush all the canvas operations
|
||||
* redraw Window canvas if necessary
|
||||
'''
|
||||
from kivy.base import EventLoop
|
||||
for i in range(count):
|
||||
EventLoop.idle()
|
||||
|
||||
|
||||
class UnitTestTouch(MotionEvent):
|
||||
'''Custom MotionEvent representing a single touch. Similar to `on_touch_*`
|
||||
methods from the Widget class, this one introduces:
|
||||
|
||||
* touch_down
|
||||
* touch_move
|
||||
* touch_up
|
||||
|
||||
Create a new touch with::
|
||||
|
||||
touch = UnitTestTouch(x, y)
|
||||
|
||||
then you press it on the default position with::
|
||||
|
||||
touch.touch_down()
|
||||
|
||||
or move it or even release with these simple calls::
|
||||
|
||||
touch.touch_move(new_x, new_y)
|
||||
touch.touch_up()
|
||||
'''
|
||||
|
||||
def __init__(self, x, y):
|
||||
'''Create a MotionEvent instance with X and Y of the first
|
||||
position a touch is at.
|
||||
'''
|
||||
from kivy.base import EventLoop
|
||||
self.eventloop = EventLoop
|
||||
win = EventLoop.window
|
||||
super(UnitTestTouch, self).__init__(
|
||||
# device, (tuio) id, args
|
||||
self.__class__.__name__, 99, {
|
||||
"x": x / (win.width - 1.0),
|
||||
"y": y / (win.height - 1.0),
|
||||
},
|
||||
is_touch=True,
|
||||
type_id='touch'
|
||||
)
|
||||
# set profile to accept x, y and pos properties
|
||||
self.profile = ['pos']
|
||||
|
||||
def touch_down(self, *args):
|
||||
self.eventloop.post_dispatch_input("begin", self)
|
||||
|
||||
def touch_move(self, x, y):
|
||||
win = self.eventloop.window
|
||||
self.move({
|
||||
"x": x / (win.width - 1.0),
|
||||
"y": y / (win.height - 1.0)
|
||||
})
|
||||
self.eventloop.post_dispatch_input("update", self)
|
||||
|
||||
def touch_up(self, *args):
|
||||
self.eventloop.post_dispatch_input("end", self)
|
||||
|
||||
def depack(self, args):
|
||||
# set sx/sy properties to ratio (e.g. X / win.width)
|
||||
self.sx = args['x']
|
||||
self.sy = args['y']
|
||||
# run depack after we set the values
|
||||
super().depack(args)
|
||||
|
||||
|
||||
# https://gist.github.com/tito/f111b6916aa6a4ed0851
|
||||
# subclass for touch event in unit test
|
||||
class UTMotionEvent(MotionEvent):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('is_touch', True)
|
||||
kwargs.setdefault('type_id', 'touch')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.profile = ['pos']
|
||||
|
||||
def depack(self, args):
|
||||
self.sx = args['x']
|
||||
self.sy = args['y']
|
||||
super().depack(args)
|
||||
|
||||
|
||||
def async_run(func=None, app_cls_func=None):
|
||||
def inner_func(func):
|
||||
if 'mock' == cgl_get_backend_name():
|
||||
return pytest.mark.skip(
|
||||
reason='Skipping because gl backend is set to mock')(func)
|
||||
|
||||
if sys.version_info[0] < 3 or sys.version_info[1] <= 5:
|
||||
return pytest.mark.skip(
|
||||
reason='Skipping because graphics tests are not supported on '
|
||||
'py3.5, only on py3.6+')(func)
|
||||
|
||||
if app_cls_func is not None:
|
||||
func = pytest.mark.parametrize(
|
||||
"kivy_app", [[app_cls_func], ], indirect=True)(func)
|
||||
|
||||
if kivy_eventloop == 'asyncio':
|
||||
try:
|
||||
import pytest_asyncio
|
||||
return pytest.mark.asyncio(func)
|
||||
except ImportError:
|
||||
return pytest.mark.skip(
|
||||
reason='KIVY_EVENTLOOP == "asyncio" but '
|
||||
'"pytest-asyncio" is not installed')(func)
|
||||
elif kivy_eventloop == 'trio':
|
||||
try:
|
||||
import trio
|
||||
from pytest_trio import trio_fixture
|
||||
func._force_trio_fixture = True
|
||||
return func
|
||||
except ImportError:
|
||||
return pytest.mark.skip(
|
||||
reason='KIVY_EVENTLOOP == "trio" but '
|
||||
'"pytest-trio" is not installed')(func)
|
||||
else:
|
||||
return pytest.mark.skip(
|
||||
reason='KIVY_EVENTLOOP must be set to either of "asyncio" or '
|
||||
'"trio" to run async tests')(func)
|
||||
|
||||
if func is None:
|
||||
return inner_func
|
||||
|
||||
return inner_func(func)
|
||||
33
kivy/tests/conftest.py
Normal file
33
kivy/tests/conftest.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import pytest
|
||||
import os
|
||||
|
||||
kivy_eventloop = os.environ.get('KIVY_EVENTLOOP', 'asyncio')
|
||||
|
||||
try:
|
||||
from .fixtures import kivy_app, kivy_clock, kivy_metrics, \
|
||||
kivy_exception_manager
|
||||
except SyntaxError:
|
||||
# async app tests would be skipped due to async_run forcing it to skip so
|
||||
# it's ok to fail here as it won't be used anyway
|
||||
pass
|
||||
|
||||
if kivy_eventloop != 'trio':
|
||||
@pytest.fixture()
|
||||
def nursery():
|
||||
pass
|
||||
|
||||
|
||||
def pytest_runtest_makereport(item, call):
|
||||
# from https://docs.pytest.org/en/latest/example/simple.html
|
||||
if "incremental" in item.keywords:
|
||||
if call.excinfo is not None:
|
||||
parent = item.parent
|
||||
parent._previousfailed = item
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
# from https://docs.pytest.org/en/latest/example/simple.html
|
||||
if "incremental" in item.keywords:
|
||||
previousfailed = getattr(item.parent, "_previousfailed", None)
|
||||
if previousfailed is not None:
|
||||
pytest.xfail("previous test failed (%s)" % previousfailed.name)
|
||||
17
kivy/tests/coverage_lang.kv
Normal file
17
kivy/tests/coverage_lang.kv
Normal file
@@ -0,0 +1,17 @@
|
||||
#:import kivy kivy
|
||||
|
||||
<SomeWidget@Widget>:
|
||||
on_x: self.something = 42
|
||||
height: self.width
|
||||
width: 78
|
||||
on_y:
|
||||
self.another = 23
|
||||
self.home = 78
|
||||
canvas.before:
|
||||
Color:
|
||||
rgb: 1, 1, 1
|
||||
|
||||
Widget:
|
||||
size: 55, self.y + 10
|
||||
SomeWidget:
|
||||
size_hint_x: .5
|
||||
2
kivy/tests/data/test.ini
Normal file
2
kivy/tests/data/test.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
[section]
|
||||
key=value
|
||||
169
kivy/tests/fixtures.py
Normal file
169
kivy/tests/fixtures.py
Normal file
@@ -0,0 +1,169 @@
|
||||
import pytest
|
||||
import gc
|
||||
import weakref
|
||||
import time
|
||||
import os.path
|
||||
|
||||
__all__ = ('kivy_clock', 'kivy_metrics', 'kivy_exception_manager', 'kivy_app')
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def kivy_clock():
|
||||
from kivy.context import Context
|
||||
from kivy.clock import ClockBase
|
||||
|
||||
context = Context(init=False)
|
||||
context['Clock'] = ClockBase()
|
||||
context.push()
|
||||
|
||||
from kivy.clock import Clock
|
||||
Clock._max_fps = 0
|
||||
|
||||
try:
|
||||
Clock.start_clock()
|
||||
yield Clock
|
||||
Clock.stop_clock()
|
||||
finally:
|
||||
context.pop()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def kivy_metrics():
|
||||
from kivy.context import Context
|
||||
from kivy.metrics import MetricsBase, Metrics
|
||||
from kivy._metrics import dispatch_pixel_scale
|
||||
|
||||
context = Context(init=False)
|
||||
context['Metrics'] = MetricsBase()
|
||||
context.push()
|
||||
# need to do it to reset the global value
|
||||
dispatch_pixel_scale()
|
||||
|
||||
try:
|
||||
yield Metrics
|
||||
finally:
|
||||
context.pop()
|
||||
Metrics._set_cached_scaling()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def kivy_exception_manager():
|
||||
from kivy.context import Context
|
||||
from kivy.base import ExceptionManagerBase, ExceptionManager
|
||||
|
||||
context = Context(init=False)
|
||||
context['ExceptionManager'] = ExceptionManagerBase()
|
||||
context.push()
|
||||
|
||||
try:
|
||||
yield ExceptionManager
|
||||
finally:
|
||||
context.pop()
|
||||
|
||||
|
||||
# keep track of all the kivy app fixtures so that we can check that it
|
||||
# properly dies
|
||||
apps = []
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
async def kivy_app(request, nursery):
|
||||
gc.collect()
|
||||
if apps:
|
||||
last_app, last_request = apps.pop()
|
||||
assert last_app() is None, \
|
||||
'Memory leak: failed to release app for test ' + repr(last_request)
|
||||
|
||||
from os import environ
|
||||
environ['KIVY_USE_DEFAULTCONFIG'] = '1'
|
||||
|
||||
# force window size + remove all inputs
|
||||
from kivy.config import Config
|
||||
Config.set('graphics', 'width', '320')
|
||||
Config.set('graphics', 'height', '240')
|
||||
for items in Config.items('input'):
|
||||
Config.remove_option('input', items[0])
|
||||
|
||||
from kivy.core.window import Window
|
||||
from kivy.context import Context
|
||||
from kivy.clock import ClockBase
|
||||
from kivy.factory import FactoryBase, Factory
|
||||
from kivy.app import App
|
||||
from kivy.lang.builder import BuilderBase, Builder
|
||||
from kivy.base import stopTouchApp
|
||||
from kivy import kivy_data_dir
|
||||
from kivy.logger import LoggerHistory
|
||||
|
||||
kivy_eventloop = environ.get('KIVY_EVENTLOOP', 'asyncio')
|
||||
if kivy_eventloop == 'asyncio':
|
||||
pytest.importorskip(
|
||||
'pytest_asyncio',
|
||||
reason='KIVY_EVENTLOOP == "asyncio" but '
|
||||
'"pytest_asyncio" is not installed')
|
||||
async_lib = 'asyncio'
|
||||
elif kivy_eventloop == 'trio':
|
||||
pytest.importorskip(
|
||||
'pytest_trio',
|
||||
reason='KIVY_EVENTLOOP == "trio" but '
|
||||
'"pytest_trio" is not installed')
|
||||
async_lib = 'trio'
|
||||
else:
|
||||
pytest.skip(
|
||||
'KIVY_EVENTLOOP must be set to either of "asyncio" or '
|
||||
'"trio" to run async tests')
|
||||
|
||||
context = Context(init=False)
|
||||
context['Clock'] = ClockBase(async_lib=async_lib)
|
||||
|
||||
# have to make sure all global kv files are loaded before this because
|
||||
# globally read kv files (e.g. on module import) will not be loaded again
|
||||
# in the new builder, except if manually loaded, which we don't do
|
||||
context['Factory'] = FactoryBase.create_from(Factory)
|
||||
context['Builder'] = BuilderBase.create_from(Builder)
|
||||
context.push()
|
||||
|
||||
Window.create_window()
|
||||
Window.register()
|
||||
Window.initialized = True
|
||||
Window.canvas.clear()
|
||||
|
||||
app = request.param[0]()
|
||||
app.set_async_lib(async_lib)
|
||||
|
||||
if async_lib == 'asyncio':
|
||||
import asyncio
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(app.async_run())
|
||||
else:
|
||||
nursery.start_soon(app.async_run)
|
||||
from kivy.clock import Clock
|
||||
Clock._max_fps = 0
|
||||
|
||||
ts = time.perf_counter()
|
||||
while not app.app_has_started:
|
||||
await app.async_sleep(.1)
|
||||
if time.perf_counter() - ts >= 10:
|
||||
raise TimeoutError()
|
||||
|
||||
await app.wait_clock_frames(5)
|
||||
|
||||
yield app
|
||||
|
||||
stopTouchApp()
|
||||
|
||||
ts = time.perf_counter()
|
||||
while not app.app_has_stopped:
|
||||
await app.async_sleep(.1)
|
||||
if time.perf_counter() - ts >= 10:
|
||||
raise TimeoutError()
|
||||
|
||||
for child in Window.children[:]:
|
||||
Window.remove_widget(child)
|
||||
context.pop()
|
||||
|
||||
# release all the resources
|
||||
del context
|
||||
LoggerHistory.clear_history()
|
||||
apps.append((weakref.ref(app), request))
|
||||
del app
|
||||
gc.collect()
|
||||
195
kivy/tests/perf_test_textinput.py
Normal file
195
kivy/tests/perf_test_textinput.py
Normal file
@@ -0,0 +1,195 @@
|
||||
from kivy.app import App
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
from kivy.lang import Builder
|
||||
from kivy.resources import resource_find
|
||||
from kivy.clock import Clock
|
||||
|
||||
import timeit
|
||||
|
||||
Builder.load_string('''
|
||||
<PerfApp>:
|
||||
value: 0
|
||||
but: but.__self__
|
||||
slider: slider
|
||||
text_input: text_input
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
TextInput:
|
||||
id: text_input
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
size_hint: 1, .2
|
||||
BoxLayout:
|
||||
Button:
|
||||
id: but
|
||||
text: 'Start Test'
|
||||
on_release: root.start_test() if self.text == 'Start Test'\
|
||||
else ''
|
||||
Slider:
|
||||
id: slider
|
||||
min: 0
|
||||
max: 100
|
||||
value: root.value
|
||||
''')
|
||||
|
||||
|
||||
class PerfApp(App, FloatLayout):
|
||||
|
||||
def build(self):
|
||||
return self
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(PerfApp, self).__init__(**kwargs)
|
||||
self.tests = []
|
||||
tests = (self.load_large_text, self.stress_insert,
|
||||
self.stress_del, self.stress_selection)
|
||||
for test in tests:
|
||||
but = type(self.but)(text=test.__name__)
|
||||
self.but.parent.add_widget(but)
|
||||
but.test = test
|
||||
self.tests.append(but)
|
||||
self.test_done = True
|
||||
|
||||
def load_large_text(self, *largs):
|
||||
print('loading uix/textinput.py....')
|
||||
self.test_done = False
|
||||
fd = open(resource_find('uix/textinput.py'), 'r')
|
||||
print('putting text in textinput')
|
||||
|
||||
def load_text(*l):
|
||||
self.text_input.text = fd.read()
|
||||
|
||||
t = timeit.Timer(load_text)
|
||||
ttk = t.timeit(1)
|
||||
fd.close()
|
||||
import resource
|
||||
print('mem usage after test')
|
||||
print(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024, 'MB')
|
||||
print('------------------------------------------')
|
||||
print('Loaded', len(self.text_input._lines), 'lines', ttk, 'secs')
|
||||
print('------------------------------------------')
|
||||
self.test_done = True
|
||||
|
||||
def stress_del(self, *largs):
|
||||
self.test_done = False
|
||||
text_input = self.text_input
|
||||
self.lt = len_text = len(text_input.text)
|
||||
target = len_text - (210 * 9)
|
||||
self.tot_time = 0
|
||||
ev = None
|
||||
|
||||
def dlt(*l):
|
||||
if len(text_input.text) <= target:
|
||||
ev.cancel()
|
||||
print('Done!')
|
||||
m_len = len(text_input._lines)
|
||||
print('deleted 210 characters 9 times')
|
||||
import resource
|
||||
print('mem usage after test')
|
||||
print(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss /
|
||||
1024, 'MB')
|
||||
print('total lines in text input:', m_len)
|
||||
print('--------------------------------------')
|
||||
print('total time elapsed:', self.tot_time)
|
||||
print('--------------------------------------')
|
||||
self.test_done = True
|
||||
return
|
||||
text_input.select_text(self.lt - 220, self.lt - 10)
|
||||
text_input.delete_selection()
|
||||
self.lt -= 210
|
||||
text_input.scroll_y -= 100
|
||||
self.tot_time += l[0]
|
||||
ev()
|
||||
ev = Clock.create_trigger(dlt)
|
||||
ev()
|
||||
|
||||
def stress_insert(self, *largs):
|
||||
self.test_done = False
|
||||
text_input = self.text_input
|
||||
text_input.select_all()
|
||||
text_input.copy(text_input.selection_text)
|
||||
text_input.cursor = text_input.get_cursor_from_index(
|
||||
text_input.selection_to)
|
||||
len_text = len(text_input._lines)
|
||||
self.tot_time = 0
|
||||
ev = None
|
||||
|
||||
def pste(*l):
|
||||
if len(text_input._lines) >= (len_text) * 9:
|
||||
ev.cancel()
|
||||
print('Done!')
|
||||
m_len = len(text_input._lines)
|
||||
print('pasted', len_text, 'lines',
|
||||
round((m_len - len_text) / len_text), 'times')
|
||||
import resource
|
||||
print('mem usage after test')
|
||||
print(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss /
|
||||
1024, 'MB')
|
||||
print('total lines in text input:', m_len)
|
||||
print('--------------------------------------')
|
||||
print('total time elapsed:', self.tot_time)
|
||||
print('--------------------------------------')
|
||||
self.test_done = True
|
||||
return
|
||||
self.tot_time += l[0]
|
||||
text_input.paste()
|
||||
ev()
|
||||
ev = Clock.create_trigger(pste)
|
||||
ev()
|
||||
|
||||
def stress_selection(self, *largs):
|
||||
self.test_done = False
|
||||
text_input = self.text_input
|
||||
self.tot_time = 0
|
||||
old_selection_from = text_input.selection_from - 210
|
||||
ev = None
|
||||
|
||||
def pste(*l):
|
||||
if text_input.selection_from >= old_selection_from:
|
||||
ev.cancel()
|
||||
print('Done!')
|
||||
import resource
|
||||
print('mem usage after test')
|
||||
print(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss /
|
||||
1024, 'MB')
|
||||
print('--------------------------------------')
|
||||
print('total time elapsed:', self.tot_time)
|
||||
print('--------------------------------------')
|
||||
self.test_done = True
|
||||
return
|
||||
text_input.select_text(text_input.selection_from - 1,
|
||||
text_input.selection_to)
|
||||
ev()
|
||||
ev = Clock.create_trigger(pste)
|
||||
ev()
|
||||
|
||||
def start_test(self, *largs):
|
||||
self.but.text = 'test started'
|
||||
self.slider.max = len(self.tests)
|
||||
ev = None
|
||||
|
||||
def test(*l):
|
||||
if self.test_done:
|
||||
try:
|
||||
but = self.tests[int(self.slider.value)]
|
||||
self.slider.value += 1
|
||||
but.state = 'down'
|
||||
print('=====================')
|
||||
print('Test:', but.text)
|
||||
print('=====================')
|
||||
but.test(but)
|
||||
except IndexError:
|
||||
for but in self.tests:
|
||||
but.state = 'normal'
|
||||
self.but.text = 'Start Test'
|
||||
self.slider.value = 0
|
||||
print('===================')
|
||||
print('All Tests Completed')
|
||||
print('===================')
|
||||
ev.cancel()
|
||||
|
||||
ev = Clock.schedule_interval(test, 1)
|
||||
|
||||
|
||||
if __name__ in ('__main__', ):
|
||||
PerfApp().run()
|
||||
Binary file not shown.
Binary file not shown.
12
kivy/tests/pyinstaller/simple_widget/main.py
Normal file
12
kivy/tests/pyinstaller/simple_widget/main.py
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
from project.widget import MyWidget
|
||||
|
||||
if __name__ == '__main__':
|
||||
w = MyWidget()
|
||||
|
||||
assert w.x == w.y
|
||||
w.y = 868
|
||||
assert w.x == 868
|
||||
w.y = 370
|
||||
assert w.x == 370
|
||||
37
kivy/tests/pyinstaller/simple_widget/main.spec
Normal file
37
kivy/tests/pyinstaller/simple_widget/main.spec
Normal file
@@ -0,0 +1,37 @@
|
||||
# -*- mode: python -*-
|
||||
|
||||
block_cipher = None
|
||||
from kivy_deps import sdl2, glew
|
||||
from kivy.tools.packaging.pyinstaller_hooks import runtime_hooks, hookspath
|
||||
import os
|
||||
|
||||
|
||||
a = Analysis(['main.py'],
|
||||
pathex=[os.environ['__KIVY_PYINSTALLER_DIR']],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[os.environ['__KIVY_PYINSTALLER_DIR']],
|
||||
runtime_hooks=runtime_hooks(),
|
||||
excludes=['numpy', 'ffpyplayer'],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher)
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
exclude_binaries=True,
|
||||
name='main',
|
||||
debug=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=True )
|
||||
coll = COLLECT(exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
*[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],
|
||||
strip=False,
|
||||
upx=True,
|
||||
name='main')
|
||||
Binary file not shown.
Binary file not shown.
12
kivy/tests/pyinstaller/simple_widget/project/widget.py
Normal file
12
kivy/tests/pyinstaller/simple_widget/project/widget.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
|
||||
class MyWidget(Widget):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(MyWidget, self).__init__(**kwargs)
|
||||
|
||||
def callback(*l):
|
||||
self.x = self.y
|
||||
self.fbind('y', callback)
|
||||
callback()
|
||||
115
kivy/tests/pyinstaller/test_pyinstaller.py
Normal file
115
kivy/tests/pyinstaller/test_pyinstaller.py
Normal file
@@ -0,0 +1,115 @@
|
||||
import pytest
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
if sys.platform != 'win32':
|
||||
pytestmark = pytest.mark.skip(
|
||||
"PyInstaller is currently only tested on Windows")
|
||||
else:
|
||||
try:
|
||||
import PyInstaller
|
||||
except ImportError:
|
||||
pytestmark = pytest.mark.skip("PyInstaller is not available")
|
||||
|
||||
|
||||
@pytest.mark.incremental
|
||||
class PyinstallerBase(object):
|
||||
|
||||
pinstall_path = ''
|
||||
|
||||
env = None
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.env = cls.get_env()
|
||||
|
||||
@classmethod
|
||||
def get_env(cls):
|
||||
env = os.environ.copy()
|
||||
env['__KIVY_PYINSTALLER_DIR'] = cls.pinstall_path
|
||||
|
||||
if 'PYTHONPATH' not in env:
|
||||
env['PYTHONPATH'] = cls.pinstall_path
|
||||
else:
|
||||
env['PYTHONPATH'] = cls.pinstall_path + os.sep + env['PYTHONPATH']
|
||||
return env
|
||||
|
||||
@classmethod
|
||||
def get_run_env(cls):
|
||||
return os.environ.copy()
|
||||
|
||||
def test_project(self):
|
||||
try:
|
||||
# check that the project works normally before packaging
|
||||
subprocess.check_output(
|
||||
[sys.executable or 'python',
|
||||
os.path.join(self.pinstall_path, 'main.py')],
|
||||
stderr=subprocess.STDOUT, env=self.env)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e.output.decode('utf8'))
|
||||
raise
|
||||
|
||||
def test_packaging(self):
|
||||
dist = os.path.join(self.pinstall_path, 'dist')
|
||||
build = os.path.join(self.pinstall_path, 'build')
|
||||
try:
|
||||
# create pyinstaller package
|
||||
subprocess.check_output(
|
||||
[sys.executable or 'python', '-m', 'PyInstaller',
|
||||
os.path.join(self.pinstall_path, 'main.spec'),
|
||||
'--distpath', dist, '--workpath', build],
|
||||
stderr=subprocess.STDOUT, env=self.env)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e.output.decode('utf8'))
|
||||
raise
|
||||
|
||||
def test_packaged_project(self):
|
||||
try:
|
||||
# test package
|
||||
subprocess.check_output(
|
||||
os.path.join(self.pinstall_path, 'dist', 'main', 'main'),
|
||||
stderr=subprocess.STDOUT, env=self.get_run_env())
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e.output.decode('utf8'))
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
shutil.rmtree(
|
||||
os.path.join(cls.pinstall_path, '__pycache__'),
|
||||
ignore_errors=True)
|
||||
shutil.rmtree(
|
||||
os.path.join(cls.pinstall_path, 'build'), ignore_errors=True)
|
||||
shutil.rmtree(
|
||||
os.path.join(cls.pinstall_path, 'dist'), ignore_errors=True)
|
||||
shutil.rmtree(
|
||||
os.path.join(cls.pinstall_path, 'project', '__pycache__'),
|
||||
ignore_errors=True)
|
||||
|
||||
|
||||
class TestSimpleWidget(PyinstallerBase):
|
||||
|
||||
pinstall_path = os.path.join(os.path.dirname(__file__), 'simple_widget')
|
||||
|
||||
|
||||
class TestVideoWidget(PyinstallerBase):
|
||||
|
||||
pinstall_path = os.path.join(os.path.dirname(__file__), 'video_widget')
|
||||
|
||||
@classmethod
|
||||
def get_env(cls):
|
||||
env = super(TestVideoWidget, cls).get_env()
|
||||
import kivy
|
||||
env['__KIVY_VIDEO_TEST_FNAME'] = os.path.abspath(os.path.join(
|
||||
kivy.kivy_examples_dir, "widgets", "cityCC0.mpg"))
|
||||
return env
|
||||
|
||||
@classmethod
|
||||
def get_run_env(cls):
|
||||
env = super(TestVideoWidget, cls).get_run_env()
|
||||
import kivy
|
||||
env['__KIVY_VIDEO_TEST_FNAME'] = os.path.abspath(os.path.join(
|
||||
kivy.kivy_examples_dir, "widgets", "cityCC0.mpg"))
|
||||
return env
|
||||
Binary file not shown.
6
kivy/tests/pyinstaller/video_widget/main.py
Normal file
6
kivy/tests/pyinstaller/video_widget/main.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from project import VideoApp
|
||||
|
||||
if __name__ == '__main__':
|
||||
from kivy.core.video import Video
|
||||
assert Video is not None
|
||||
VideoApp().run()
|
||||
50
kivy/tests/pyinstaller/video_widget/main.spec
Normal file
50
kivy/tests/pyinstaller/video_widget/main.spec
Normal file
@@ -0,0 +1,50 @@
|
||||
# -*- mode: python -*-
|
||||
|
||||
block_cipher = None
|
||||
from kivy_deps import sdl2, glew
|
||||
from kivy.tools.packaging.pyinstaller_hooks import runtime_hooks, hookspath
|
||||
import os
|
||||
|
||||
deps = list(sdl2.dep_bins + glew.dep_bins)
|
||||
try:
|
||||
import ffpyplayer
|
||||
deps.extend(ffpyplayer.dep_bins)
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
from kivy_deps import gstreamer
|
||||
deps.extend(gstreamer.dep_bins)
|
||||
except ImportError:
|
||||
pass
|
||||
print('deps are: ', deps)
|
||||
|
||||
|
||||
a = Analysis(['main.py'],
|
||||
pathex=[os.environ['__KIVY_PYINSTALLER_DIR']],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[os.environ['__KIVY_PYINSTALLER_DIR']],
|
||||
runtime_hooks=runtime_hooks(),
|
||||
excludes=['numpy',],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher)
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
exclude_binaries=True,
|
||||
name='main',
|
||||
debug=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=True )
|
||||
coll = COLLECT(exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
*[Tree(p) for p in deps],
|
||||
strip=False,
|
||||
upx=True,
|
||||
name='main')
|
||||
38
kivy/tests/pyinstaller/video_widget/project/__init__.py
Normal file
38
kivy/tests/pyinstaller/video_widget/project/__init__.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from kivy.app import App
|
||||
from kivy.uix.videoplayer import VideoPlayer
|
||||
from kivy.clock import Clock
|
||||
import os
|
||||
import time
|
||||
|
||||
|
||||
class VideoApp(App):
|
||||
|
||||
player = None
|
||||
|
||||
start_t = None
|
||||
|
||||
def build(self):
|
||||
self.player = player = VideoPlayer(
|
||||
source=os.environ['__KIVY_VIDEO_TEST_FNAME'], volume=0)
|
||||
|
||||
self.player.fbind('position', self.check_position)
|
||||
Clock.schedule_once(self.start_player, 0)
|
||||
Clock.schedule_interval(self.stop_player, 1)
|
||||
return player
|
||||
|
||||
def start_player(self, *args):
|
||||
self.player.state = 'play'
|
||||
self.start_t = time.perf_counter()
|
||||
|
||||
def check_position(self, *args):
|
||||
if self.player.position > 0.1:
|
||||
self.stop_player()
|
||||
|
||||
def stop_player(self, *args):
|
||||
if time.perf_counter() - self.start_t > 10:
|
||||
assert self.player.duration > 0
|
||||
assert self.player.position > 0
|
||||
self.stop()
|
||||
else:
|
||||
if self.player.position > 0 and self.player.duration > 0:
|
||||
self.stop()
|
||||
Binary file not shown.
BIN
kivy/tests/sample1.ogg
Normal file
BIN
kivy/tests/sample1.ogg
Normal file
Binary file not shown.
415
kivy/tests/test_animations.py
Normal file
415
kivy/tests/test_animations.py
Normal file
@@ -0,0 +1,415 @@
|
||||
'''
|
||||
Animations tests
|
||||
================
|
||||
'''
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def ec_cls():
|
||||
class EventCounter:
|
||||
def __init__(self, anim):
|
||||
self.n_start = 0
|
||||
self.n_progress = 0
|
||||
self.n_complete = 0
|
||||
anim.bind(on_start=self.on_start,
|
||||
on_progress=self.on_progress,
|
||||
on_complete=self.on_complete)
|
||||
|
||||
def on_start(self, anim, widget):
|
||||
self.n_start += 1
|
||||
|
||||
def on_progress(self, anim, widget, progress):
|
||||
self.n_progress += 1
|
||||
|
||||
def on_complete(self, anim, widget):
|
||||
self.n_complete += 1
|
||||
|
||||
def assert_(self, n_start, n_progress_greater_than_zero, n_complete):
|
||||
assert self.n_start == n_start
|
||||
if n_progress_greater_than_zero:
|
||||
assert self.n_progress > 0
|
||||
else:
|
||||
assert self.n_progress == 0
|
||||
assert self.n_complete == n_complete
|
||||
return EventCounter
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def cleanup():
|
||||
from kivy.animation import Animation
|
||||
Animation.cancel_all(None)
|
||||
|
||||
|
||||
def no_animations_being_played():
|
||||
from kivy.animation import Animation
|
||||
return len(Animation._instances) == 0
|
||||
|
||||
|
||||
def sleep(t):
|
||||
from time import time, sleep
|
||||
from kivy.clock import Clock
|
||||
tick = Clock.tick
|
||||
deadline = time() + t
|
||||
while time() < deadline:
|
||||
sleep(.01)
|
||||
tick()
|
||||
|
||||
|
||||
class TestAnimation:
|
||||
|
||||
def test_start_animation(self):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a = Animation(x=100, d=1)
|
||||
w = Widget()
|
||||
a.start(w)
|
||||
sleep(1.5)
|
||||
assert w.x == pytest.approx(100)
|
||||
assert no_animations_being_played()
|
||||
|
||||
def test_animation_duration_0(self):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a = Animation(x=100, d=0)
|
||||
w = Widget()
|
||||
a.start(w)
|
||||
sleep(.5)
|
||||
assert no_animations_being_played()
|
||||
|
||||
def test_cancel_all(self):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a1 = Animation(x=100)
|
||||
a2 = Animation(y=100)
|
||||
w1 = Widget()
|
||||
w2 = Widget()
|
||||
a1.start(w1)
|
||||
a1.start(w2)
|
||||
a2.start(w1)
|
||||
a2.start(w2)
|
||||
assert not no_animations_being_played()
|
||||
Animation.cancel_all(None)
|
||||
assert no_animations_being_played()
|
||||
|
||||
def test_cancel_all_2(self):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a1 = Animation(x=100)
|
||||
a2 = Animation(y=100)
|
||||
w1 = Widget()
|
||||
w2 = Widget()
|
||||
a1.start(w1)
|
||||
a1.start(w2)
|
||||
a2.start(w1)
|
||||
a2.start(w2)
|
||||
assert not no_animations_being_played()
|
||||
Animation.cancel_all(None, 'x', 'z')
|
||||
assert not no_animations_being_played()
|
||||
Animation.cancel_all(None, 'y')
|
||||
assert no_animations_being_played()
|
||||
|
||||
def test_stop_animation(self):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a = Animation(x=100, d=1)
|
||||
w = Widget()
|
||||
a.start(w)
|
||||
sleep(.5)
|
||||
a.stop(w)
|
||||
assert w.x != pytest.approx(100)
|
||||
assert w.x != pytest.approx(0)
|
||||
assert no_animations_being_played()
|
||||
|
||||
def test_stop_all(self):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a = Animation(x=100, d=1)
|
||||
w = Widget()
|
||||
a.start(w)
|
||||
sleep(.5)
|
||||
Animation.stop_all(w)
|
||||
assert no_animations_being_played()
|
||||
|
||||
def test_stop_all_2(self):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a = Animation(x=100, d=1)
|
||||
w = Widget()
|
||||
a.start(w)
|
||||
sleep(.5)
|
||||
Animation.stop_all(w, 'x')
|
||||
assert no_animations_being_played()
|
||||
|
||||
def test_duration(self):
|
||||
from kivy.animation import Animation
|
||||
a = Animation(x=100, d=1)
|
||||
assert a.duration == 1
|
||||
|
||||
def test_transition(self):
|
||||
from kivy.animation import Animation, AnimationTransition
|
||||
a = Animation(x=100, t='out_bounce')
|
||||
assert a.transition is AnimationTransition.out_bounce
|
||||
|
||||
def test_animated_properties(self):
|
||||
from kivy.animation import Animation
|
||||
a = Animation(x=100)
|
||||
assert a.animated_properties == {'x': 100, }
|
||||
|
||||
def test_animated_instruction(self):
|
||||
from kivy.graphics import Scale
|
||||
from kivy.animation import Animation
|
||||
a = Animation(x=100, d=1)
|
||||
instruction = Scale(3)
|
||||
a.start(instruction)
|
||||
assert a.animated_properties == {'x': 100, }
|
||||
assert instruction.x == pytest.approx(3)
|
||||
sleep(1.5)
|
||||
assert instruction.x == pytest.approx(100)
|
||||
assert no_animations_being_played()
|
||||
|
||||
def test_weakref(self):
|
||||
import gc
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
w = Widget()
|
||||
a = Animation(x=100)
|
||||
a.start(w.proxy_ref)
|
||||
del w
|
||||
gc.collect()
|
||||
try:
|
||||
sleep(1.)
|
||||
except ReferenceError:
|
||||
pass
|
||||
assert no_animations_being_played()
|
||||
|
||||
|
||||
class TestSequence:
|
||||
|
||||
def test_cancel_all(self):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a = Animation(x=100) + Animation(x=0)
|
||||
w = Widget()
|
||||
a.start(w)
|
||||
sleep(.5)
|
||||
Animation.cancel_all(w)
|
||||
assert no_animations_being_played()
|
||||
|
||||
def test_cancel_all_2(self):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a = Animation(x=100) + Animation(x=0)
|
||||
w = Widget()
|
||||
a.start(w)
|
||||
sleep(.5)
|
||||
Animation.cancel_all(w, 'x')
|
||||
assert no_animations_being_played()
|
||||
|
||||
def test_stop_all(self):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a = Animation(x=100) + Animation(x=0)
|
||||
w = Widget()
|
||||
a.start(w)
|
||||
sleep(.5)
|
||||
Animation.stop_all(w)
|
||||
assert no_animations_being_played()
|
||||
|
||||
def test_stop_all_2(self):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a = Animation(x=100) + Animation(x=0)
|
||||
w = Widget()
|
||||
a.start(w)
|
||||
sleep(.5)
|
||||
Animation.stop_all(w, 'x')
|
||||
assert no_animations_being_played()
|
||||
|
||||
def test_count_events(self, ec_cls):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a = Animation(x=100, d=.5) + Animation(x=0, d=.5)
|
||||
w = Widget()
|
||||
ec = ec_cls(a)
|
||||
ec1 = ec_cls(a.anim1)
|
||||
ec2 = ec_cls(a.anim2)
|
||||
a.start(w)
|
||||
|
||||
# right after the animation starts
|
||||
ec.assert_(1, False, 0)
|
||||
ec1.assert_(1, False, 0)
|
||||
ec2.assert_(0, False, 0)
|
||||
sleep(.2)
|
||||
|
||||
# during the first half of the animation
|
||||
ec.assert_(1, True, 0)
|
||||
ec1.assert_(1, True, 0)
|
||||
ec2.assert_(0, False, 0)
|
||||
sleep(.5)
|
||||
|
||||
# during the second half of the animation
|
||||
ec.assert_(1, True, 0)
|
||||
ec1.assert_(1, True, 1)
|
||||
ec2.assert_(1, True, 0)
|
||||
sleep(.5)
|
||||
|
||||
# after the animation completed
|
||||
ec.assert_(1, True, 1)
|
||||
ec1.assert_(1, True, 1)
|
||||
ec2.assert_(1, True, 1)
|
||||
assert no_animations_being_played()
|
||||
|
||||
def test_have_properties_to_animate(self):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a = Animation(x=100) + Animation(x=0)
|
||||
w = Widget()
|
||||
assert not a.have_properties_to_animate(w)
|
||||
a.start(w)
|
||||
assert a.have_properties_to_animate(w)
|
||||
a.stop(w)
|
||||
assert not a.have_properties_to_animate(w)
|
||||
assert no_animations_being_played()
|
||||
|
||||
def test_animated_properties(self):
|
||||
from kivy.animation import Animation
|
||||
a = Animation(x=100, y=200) + Animation(x=0)
|
||||
assert a.animated_properties == {'x': 0, 'y': 200, }
|
||||
|
||||
def test_transition(self):
|
||||
from kivy.animation import Animation
|
||||
a = Animation(x=100) + Animation(x=0)
|
||||
with pytest.raises(AttributeError):
|
||||
a.transition
|
||||
|
||||
|
||||
class TestRepetitiveSequence:
|
||||
|
||||
def test_stop(self):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a = Animation(x=100) + Animation(x=0)
|
||||
a.repeat = True
|
||||
w = Widget()
|
||||
a.start(w)
|
||||
a.stop(w)
|
||||
assert no_animations_being_played()
|
||||
|
||||
def test_count_events(self, ec_cls):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a = Animation(x=100, d=.5) + Animation(x=0, d=.5)
|
||||
a.repeat = True
|
||||
w = Widget()
|
||||
ec = ec_cls(a)
|
||||
ec1 = ec_cls(a.anim1)
|
||||
ec2 = ec_cls(a.anim2)
|
||||
a.start(w)
|
||||
|
||||
# right after the animation starts
|
||||
ec.assert_(1, False, 0)
|
||||
ec1.assert_(1, False, 0)
|
||||
ec2.assert_(0, False, 0)
|
||||
sleep(.2)
|
||||
|
||||
# during the first half of the first round of the animation
|
||||
ec.assert_(1, True, 0)
|
||||
ec1.assert_(1, True, 0)
|
||||
ec2.assert_(0, False, 0)
|
||||
sleep(.5)
|
||||
|
||||
# during the second half of the first round of the animation
|
||||
ec.assert_(1, True, 0)
|
||||
ec1.assert_(1, True, 1)
|
||||
ec2.assert_(1, True, 0)
|
||||
sleep(.5)
|
||||
|
||||
# during the first half of the second round of the animation
|
||||
ec.assert_(1, True, 0)
|
||||
ec1.assert_(2, True, 1)
|
||||
ec2.assert_(1, True, 1)
|
||||
sleep(.5)
|
||||
|
||||
# during the second half of the second round of the animation
|
||||
ec.assert_(1, True, 0)
|
||||
ec1.assert_(2, True, 2)
|
||||
ec2.assert_(2, True, 1)
|
||||
a.stop(w)
|
||||
|
||||
# after the animation stopped
|
||||
ec.assert_(1, True, 1)
|
||||
ec1.assert_(2, True, 2)
|
||||
ec2.assert_(2, True, 2)
|
||||
assert no_animations_being_played()
|
||||
|
||||
|
||||
class TestParallel:
|
||||
|
||||
def test_have_properties_to_animate(self):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a = Animation(x=100) & Animation(y=100)
|
||||
w = Widget()
|
||||
assert not a.have_properties_to_animate(w)
|
||||
a.start(w)
|
||||
assert a.have_properties_to_animate(w)
|
||||
a.stop(w)
|
||||
assert not a.have_properties_to_animate(w)
|
||||
assert no_animations_being_played()
|
||||
|
||||
def test_cancel_property(self):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a = Animation(x=100) & Animation(y=100)
|
||||
w = Widget()
|
||||
a.start(w)
|
||||
a.cancel_property(w, 'x')
|
||||
assert not no_animations_being_played()
|
||||
a.stop(w)
|
||||
assert no_animations_being_played()
|
||||
|
||||
def test_animated_properties(self):
|
||||
from kivy.animation import Animation
|
||||
a = Animation(x=100) & Animation(y=100)
|
||||
assert a.animated_properties == {'x': 100, 'y': 100, }
|
||||
|
||||
def test_transition(self):
|
||||
from kivy.animation import Animation
|
||||
a = Animation(x=100) & Animation(y=100)
|
||||
with pytest.raises(AttributeError):
|
||||
a.transition
|
||||
|
||||
def test_count_events(self, ec_cls):
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.widget import Widget
|
||||
a = Animation(x=100) & Animation(y=100, d=.5)
|
||||
w = Widget()
|
||||
ec = ec_cls(a)
|
||||
ec1 = ec_cls(a.anim1)
|
||||
ec2 = ec_cls(a.anim2)
|
||||
a.start(w)
|
||||
|
||||
# right after the animation started
|
||||
ec.assert_(1, False, 0)
|
||||
ec1.assert_(1, False, 0)
|
||||
ec2.assert_(1, False, 0)
|
||||
sleep(.2)
|
||||
|
||||
# during the first half of the animation
|
||||
ec.assert_(1, False, 0) # n_progress is still 0 !!
|
||||
ec1.assert_(1, True, 0)
|
||||
ec2.assert_(1, True, 0)
|
||||
sleep(.5)
|
||||
|
||||
# during the second half of the animation
|
||||
ec.assert_(1, False, 0) # n_progress is still 0 !!
|
||||
ec1.assert_(1, True, 0)
|
||||
ec2.assert_(1, True, 1)
|
||||
sleep(.5)
|
||||
|
||||
# after the animation compeleted
|
||||
ec.assert_(1, False, 1) # n_progress is still 0 !
|
||||
ec1.assert_(1, True, 1)
|
||||
ec2.assert_(1, True, 1)
|
||||
assert no_animations_being_played()
|
||||
216
kivy/tests/test_app.py
Normal file
216
kivy/tests/test_app.py
Normal file
@@ -0,0 +1,216 @@
|
||||
from os import name
|
||||
import os.path
|
||||
from math import isclose
|
||||
from textwrap import dedent
|
||||
|
||||
from kivy.app import App
|
||||
from kivy.clock import Clock
|
||||
from kivy import lang
|
||||
from kivy.tests import GraphicUnitTest, async_run, UnitKivyApp
|
||||
|
||||
|
||||
class AppTest(GraphicUnitTest):
|
||||
def test_start_raw_app(self):
|
||||
lang._delayed_start = None
|
||||
a = App()
|
||||
Clock.schedule_once(a.stop, .1)
|
||||
a.run()
|
||||
|
||||
def test_start_app_with_kv(self):
|
||||
class TestKvApp(App):
|
||||
pass
|
||||
|
||||
lang._delayed_start = None
|
||||
a = TestKvApp()
|
||||
Clock.schedule_once(a.stop, .1)
|
||||
a.run()
|
||||
|
||||
def test_user_data_dir(self):
|
||||
a = App()
|
||||
data_dir = a.user_data_dir
|
||||
assert os.path.exists(data_dir)
|
||||
|
||||
def test_directory(self):
|
||||
a = App()
|
||||
assert os.path.exists(a.directory)
|
||||
|
||||
def test_name(self):
|
||||
class NameTest(App):
|
||||
pass
|
||||
|
||||
a = NameTest()
|
||||
assert a.name == 'nametest'
|
||||
|
||||
|
||||
def basic_app():
|
||||
from kivy.app import App
|
||||
from kivy.uix.label import Label
|
||||
|
||||
class TestApp(UnitKivyApp, App):
|
||||
def build(self):
|
||||
return Label(text='Hello, World!')
|
||||
|
||||
return TestApp()
|
||||
|
||||
|
||||
@async_run(app_cls_func=basic_app)
|
||||
async def test_basic_app(kivy_app):
|
||||
assert kivy_app.root.text == 'Hello, World!'
|
||||
|
||||
|
||||
def button_app():
|
||||
from kivy.app import App
|
||||
from kivy.uix.togglebutton import ToggleButton
|
||||
|
||||
class TestApp(UnitKivyApp, App):
|
||||
def build(self):
|
||||
return ToggleButton(text='Hello, World!')
|
||||
|
||||
return TestApp()
|
||||
|
||||
|
||||
@async_run(app_cls_func=button_app)
|
||||
async def test_button_app(kivy_app):
|
||||
assert kivy_app.root.text == 'Hello, World!'
|
||||
assert kivy_app.root.state == 'normal'
|
||||
|
||||
async for state, touch_pos in kivy_app.do_touch_down_up(
|
||||
widget=kivy_app.root, widget_jitter=True):
|
||||
pass
|
||||
|
||||
assert kivy_app.root.state == 'down'
|
||||
|
||||
|
||||
def scatter_app():
|
||||
from kivy.app import App
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.scatter import Scatter
|
||||
|
||||
class TestApp(UnitKivyApp, App):
|
||||
def build(self):
|
||||
label = Label(text='Hello, World!', size=('200dp', '200dp'))
|
||||
scatter = Scatter(do_scale=False, do_rotation=False)
|
||||
scatter.add_widget(label)
|
||||
return scatter
|
||||
|
||||
return TestApp()
|
||||
|
||||
|
||||
@async_run(app_cls_func=scatter_app)
|
||||
async def test_drag_app(kivy_app):
|
||||
scatter = kivy_app.root
|
||||
assert tuple(scatter.pos) == (0, 0)
|
||||
|
||||
async for state, touch_pos in kivy_app.do_touch_drag(
|
||||
pos=(100, 100), target_pos=(200, 200)):
|
||||
pass
|
||||
|
||||
assert isclose(scatter.x, 100)
|
||||
assert isclose(scatter.y, 100)
|
||||
|
||||
|
||||
def text_app():
|
||||
from kivy.app import App
|
||||
from kivy.uix.textinput import TextInput
|
||||
|
||||
class TestApp(UnitKivyApp, App):
|
||||
def build(self):
|
||||
return TextInput()
|
||||
|
||||
return TestApp()
|
||||
|
||||
|
||||
@async_run(app_cls_func=text_app)
|
||||
async def test_text_app(kivy_app):
|
||||
text = kivy_app.root
|
||||
assert text.text == ''
|
||||
|
||||
# activate widget
|
||||
async for state, touch_pos in kivy_app.do_touch_down_up(widget=text):
|
||||
pass
|
||||
|
||||
async for state, value in kivy_app.do_keyboard_key(key='A', num_press=4):
|
||||
pass
|
||||
async for state, value in kivy_app.do_keyboard_key(key='q', num_press=3):
|
||||
pass
|
||||
|
||||
assert text.text == 'AAAAqqq'
|
||||
|
||||
|
||||
def graphics_app():
|
||||
from kivy.app import App
|
||||
from kivy.uix.widget import Widget
|
||||
from kivy.graphics import Color, Rectangle
|
||||
|
||||
class TestApp(UnitKivyApp, App):
|
||||
def build(self):
|
||||
widget = Widget()
|
||||
with widget.canvas:
|
||||
Color(1, 0, 0, 1)
|
||||
Rectangle(pos=(0, 0), size=(100, 100))
|
||||
Color(0, 1, 0, 1)
|
||||
Rectangle(pos=(100, 0), size=(100, 100))
|
||||
return widget
|
||||
|
||||
return TestApp()
|
||||
|
||||
|
||||
@async_run(app_cls_func=graphics_app)
|
||||
async def test_graphics_app(kivy_app):
|
||||
widget = kivy_app.root
|
||||
(r1, g1, b1, a1), (r2, g2, b2, a2) = kivy_app.get_widget_pos_pixel(
|
||||
widget, [(50, 50), (150, 50)])
|
||||
|
||||
assert not g1 and not b1 and not r2 and not b2
|
||||
assert r1 > 50 and a1 > 50 and g2 > 50 and a2 > 50
|
||||
|
||||
|
||||
def kv_app_ref_app():
|
||||
from kivy.app import App
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import ObjectProperty
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
class MyWidget(Widget):
|
||||
|
||||
obj = ObjectProperty(None)
|
||||
|
||||
Builder.load_string(dedent(
|
||||
"""
|
||||
<MyWidget>:
|
||||
obj: app.__self__
|
||||
"""))
|
||||
|
||||
class TestApp(UnitKivyApp, App):
|
||||
def build(self):
|
||||
return MyWidget()
|
||||
|
||||
return TestApp()
|
||||
|
||||
|
||||
@async_run(app_cls_func=kv_app_ref_app)
|
||||
async def test_leak_app_kv_property(kivy_app):
|
||||
# just tests whether the app is gc'd after the test is complete
|
||||
pass
|
||||
|
||||
|
||||
def kv_app_default_ref_app():
|
||||
from kivy.app import App
|
||||
from kivy.lang import Builder
|
||||
|
||||
class TestApp(UnitKivyApp, App):
|
||||
def build(self):
|
||||
# create property in kv and set app to it
|
||||
return Builder.load_string(dedent(
|
||||
"""
|
||||
Widget:
|
||||
obj: app.__self__
|
||||
"""))
|
||||
|
||||
return TestApp()
|
||||
|
||||
|
||||
@async_run(app_cls_func=kv_app_default_ref_app)
|
||||
async def test_leak_app_default_kv_property(kivy_app):
|
||||
# just tests whether the app is gc'd after the test is complete
|
||||
pass
|
||||
69
kivy/tests/test_audio.py
Normal file
69
kivy/tests/test_audio.py
Normal file
@@ -0,0 +1,69 @@
|
||||
'''
|
||||
Audio tests
|
||||
===========
|
||||
'''
|
||||
|
||||
import unittest
|
||||
import os
|
||||
import pytest
|
||||
if os.environ.get('KIVY_TEST_AUDIO') == '0':
|
||||
pytestmark = pytest.mark.skip("Audio is not available")
|
||||
|
||||
SAMPLE_FILE = os.path.join(os.path.dirname(__file__), 'sample1.ogg')
|
||||
SAMPLE_LENGTH = 1.402
|
||||
DELTA = SAMPLE_LENGTH * 0.01
|
||||
DELAY = 0.2
|
||||
|
||||
|
||||
class AudioTestCase(unittest.TestCase):
|
||||
|
||||
def get_sound(self):
|
||||
import os
|
||||
assert os.path.exists(SAMPLE_FILE)
|
||||
from kivy.core import audio
|
||||
return audio.SoundLoader.load(SAMPLE_FILE)
|
||||
|
||||
def test_length_simple(self):
|
||||
sound = self.get_sound()
|
||||
volume = sound.volume = 0.75
|
||||
length = sound.length
|
||||
self.assertAlmostEqual(SAMPLE_LENGTH, length, delta=DELTA)
|
||||
# ensure that the gstreamer play/stop doesn't mess up the volume
|
||||
assert volume == sound.volume
|
||||
|
||||
def test_length_playing(self):
|
||||
import time
|
||||
sound = self.get_sound()
|
||||
sound.play()
|
||||
try:
|
||||
time.sleep(DELAY)
|
||||
length = sound.length
|
||||
self.assertAlmostEqual(SAMPLE_LENGTH, length, delta=DELTA)
|
||||
finally:
|
||||
sound.stop()
|
||||
self.assertAlmostEqual(SAMPLE_LENGTH, length, delta=DELTA)
|
||||
|
||||
def test_length_stopped(self):
|
||||
import time
|
||||
sound = self.get_sound()
|
||||
sound.play()
|
||||
try:
|
||||
time.sleep(DELAY)
|
||||
finally:
|
||||
sound.stop()
|
||||
length = sound.length
|
||||
self.assertAlmostEqual(SAMPLE_LENGTH, length, delta=DELTA)
|
||||
|
||||
|
||||
class AudioGstreamerTestCase(AudioTestCase):
|
||||
|
||||
def make_sound(self, source):
|
||||
from kivy.core.audio import audio_gstreamer
|
||||
return audio_gstreamer.SoundGstreamer(source)
|
||||
|
||||
|
||||
class AudioPygameTestCase(AudioTestCase):
|
||||
|
||||
def make_sound(self, source):
|
||||
from kivy.core.audio import audio_pygame
|
||||
return audio_pygame.SoundPygame(source)
|
||||
292
kivy/tests/test_benchmark.py
Normal file
292
kivy/tests/test_benchmark.py
Normal file
@@ -0,0 +1,292 @@
|
||||
import pytest
|
||||
from string import ascii_letters
|
||||
from random import randint
|
||||
import gc
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def kivy_benchmark(benchmark, kivy_clock):
|
||||
from kivy.core.window import Window
|
||||
from kivy.cache import Cache
|
||||
from kivy.utils import platform
|
||||
import kivy
|
||||
from kivy.core.gl import glGetString, GL_VENDOR, GL_RENDERER, GL_VERSION
|
||||
from kivy.context import Context
|
||||
from kivy.clock import ClockBase
|
||||
from kivy.factory import FactoryBase, Factory
|
||||
from kivy.lang.builder import BuilderBase, Builder
|
||||
|
||||
context = Context(init=False)
|
||||
context['Clock'] = ClockBase()
|
||||
context['Factory'] = FactoryBase.create_from(Factory)
|
||||
context['Builder'] = BuilderBase.create_from(Builder)
|
||||
|
||||
for category in list(Cache._objects.keys()):
|
||||
if category not in Cache._categories:
|
||||
continue
|
||||
|
||||
for key in list(Cache._objects[category].keys()):
|
||||
Cache.remove(category, key)
|
||||
|
||||
gc.collect()
|
||||
|
||||
benchmark.extra_info['platform'] = str(sys.platform)
|
||||
benchmark.extra_info['python_version'] = str(sys.version)
|
||||
benchmark.extra_info['python_api'] = str(sys.api_version)
|
||||
benchmark.extra_info['kivy_platform'] = platform
|
||||
benchmark.extra_info['kivy_version'] = kivy.__version__
|
||||
benchmark.extra_info['gl_vendor'] = str(glGetString(GL_VENDOR))
|
||||
benchmark.extra_info['gl_renderer'] = str(glGetString(GL_RENDERER))
|
||||
benchmark.extra_info['gl_version'] = str(glGetString(GL_VERSION))
|
||||
|
||||
context.push()
|
||||
try:
|
||||
yield benchmark
|
||||
finally:
|
||||
context.pop()
|
||||
|
||||
|
||||
def test_event_dispatcher_creation(kivy_benchmark):
|
||||
from kivy.event import EventDispatcher
|
||||
|
||||
class Event(EventDispatcher):
|
||||
pass
|
||||
# create one just so we don't incur loading cost
|
||||
e = Event()
|
||||
kivy_benchmark(Event)
|
||||
|
||||
|
||||
def test_widget_creation(kivy_benchmark):
|
||||
from kivy.uix.widget import Widget
|
||||
# create one just so we don't incur loading cost
|
||||
w = Widget()
|
||||
kivy_benchmark(Widget)
|
||||
|
||||
|
||||
def test_kv_widget_creation(kivy_benchmark):
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
class MyWidget(Widget):
|
||||
pass
|
||||
|
||||
Builder.load_string("""
|
||||
<MyWidget>:
|
||||
width: 55
|
||||
height: 37
|
||||
x: self.width + 5
|
||||
y: self.height + 32
|
||||
""")
|
||||
|
||||
# create one just so we don't incur loading cost
|
||||
w = MyWidget()
|
||||
kivy_benchmark(MyWidget)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('test_component', ['create', 'set'])
|
||||
def test_complex_kv_widget(kivy_benchmark, test_component):
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
class MyWidget(Widget):
|
||||
pass
|
||||
|
||||
Builder.load_string("""
|
||||
<MyWidget>:
|
||||
width: 1
|
||||
height: '{}dp'.format(self.width + 1)
|
||||
x: self.height + 1
|
||||
y: self.x + 1
|
||||
size_hint_min: self.size_hint
|
||||
size_hint_max_y: self.size_hint_min_y
|
||||
size_hint_max_x: self.size_hint_min_x
|
||||
opacity: sum(self.size_hint_min) + sum(self.size_hint_max)
|
||||
""")
|
||||
|
||||
# create one just so we don't incur loading cost
|
||||
widget = MyWidget()
|
||||
w = 0
|
||||
sh = 0
|
||||
|
||||
def set_value():
|
||||
nonlocal w, sh
|
||||
w += 1
|
||||
sh += 1
|
||||
widget.width = w
|
||||
widget.size_hint = sh, sh
|
||||
|
||||
if test_component == 'create':
|
||||
kivy_benchmark(MyWidget)
|
||||
else:
|
||||
kivy_benchmark(set_value)
|
||||
|
||||
|
||||
def get_event_class(name, args, kwargs):
|
||||
from kivy.event import EventDispatcher
|
||||
import kivy.properties
|
||||
from kivy.properties import BooleanProperty, ReferenceListProperty, \
|
||||
AliasProperty
|
||||
|
||||
if name == 'AliasProperty':
|
||||
class Event(EventDispatcher):
|
||||
def get_a(self):
|
||||
return 0
|
||||
|
||||
def set_a(self, value):
|
||||
pass
|
||||
a = AliasProperty(get_a, set_a)
|
||||
|
||||
elif name == 'ReferenceListProperty':
|
||||
class Event(EventDispatcher):
|
||||
|
||||
a1 = BooleanProperty(0)
|
||||
a2 = BooleanProperty(0)
|
||||
a = ReferenceListProperty(a1, a2)
|
||||
|
||||
else:
|
||||
cls = getattr(kivy.properties, name)
|
||||
|
||||
class Event(EventDispatcher):
|
||||
|
||||
a = cls(*args, **kwargs)
|
||||
|
||||
return Event
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name,args,kwargs', [
|
||||
('NumericProperty', (0,), {}),
|
||||
('ObjectProperty', (None,), {}),
|
||||
('VariableListProperty', ([0, 0, 0, 0],), {}),
|
||||
('BoundedNumericProperty', (1, ), {'min': 0, 'max': 2}),
|
||||
('DictProperty', ({}, ), {}),
|
||||
('ColorProperty', ([1, 1, 1, 1],), {}),
|
||||
('BooleanProperty', (False,), {}),
|
||||
('OptionProperty', ('a',), {'options': ['a', 'b']}),
|
||||
('StringProperty', ('',), {}),
|
||||
('ListProperty', ([],), {}),
|
||||
('AliasProperty', (), {}),
|
||||
('ReferenceListProperty', (), {}),
|
||||
])
|
||||
def test_property_creation(kivy_benchmark, name, args, kwargs):
|
||||
event_cls = get_event_class(name, args, kwargs)
|
||||
|
||||
# create one just so we don't incur loading cost
|
||||
e = event_cls()
|
||||
kivy_benchmark(event_cls)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name,args,kwargs,val,reset_val', [
|
||||
('NumericProperty', (0,), {}, 10, 0),
|
||||
('NumericProperty', (0,), {}, '10dp', 0),
|
||||
('NumericProperty', (0,), {}, [10, 'dp'], 0),
|
||||
('ObjectProperty', (None,), {}, 5, 0),
|
||||
('VariableListProperty', ([0, 0, 0, 0],), {}, [2, 4], [0]),
|
||||
('BoundedNumericProperty', (1, ), {'min': 0, 'max': 2}, .5, 1),
|
||||
('DictProperty', ({}, ), {}, {'name': 1}, {}),
|
||||
('ColorProperty', ([1, 1, 1, 1],), {}, 'red', [1, 1, 1, 1]),
|
||||
('BooleanProperty', (False,), {}, True, False),
|
||||
('OptionProperty', ('a',), {'options': ['a', 'b']}, 'b', 'a'),
|
||||
('StringProperty', ('',), {}, 'a', ''),
|
||||
('ListProperty', ([],), {}, [1, 2], []),
|
||||
('AliasProperty', (0,), {}, 1, 0),
|
||||
('ReferenceListProperty', ((1, 2),), {}, (3, 4), (1, 2)),
|
||||
])
|
||||
@pytest.mark.parametrize('exclude_first', [True, False])
|
||||
def test_property_set(
|
||||
kivy_benchmark, name, args, kwargs, val, reset_val, exclude_first):
|
||||
event_cls = get_event_class(name, args, kwargs)
|
||||
|
||||
# create one just so we don't incur loading cost
|
||||
e = event_cls()
|
||||
|
||||
def set_property():
|
||||
e.a = reset_val
|
||||
e.a = val
|
||||
|
||||
if exclude_first:
|
||||
set_property()
|
||||
kivy_benchmark(set_property)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('n', [1, 10, 100, 1_000])
|
||||
def test_widget_empty_draw(kivy_benchmark, n):
|
||||
from kivy.graphics import RenderContext
|
||||
from kivy.uix.widget import Widget
|
||||
ctx = RenderContext()
|
||||
root = Widget()
|
||||
for x in range(n):
|
||||
root.add_widget(Widget())
|
||||
ctx.add(root.canvas)
|
||||
|
||||
kivy_benchmark(ctx.draw)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('n', [1, 10, 100, 1_000])
|
||||
def test_widget_dispatch_touch(kivy_benchmark, n):
|
||||
from kivy.tests.common import UnitTestTouch
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
root = Widget()
|
||||
for x in range(10):
|
||||
parent = Widget()
|
||||
for y in range(n):
|
||||
parent.add_widget(Widget())
|
||||
root.add_widget(parent)
|
||||
|
||||
touch = UnitTestTouch(10, 10)
|
||||
|
||||
def dispatch():
|
||||
root.dispatch('on_touch_down', touch)
|
||||
root.dispatch('on_touch_move', touch)
|
||||
root.dispatch('on_touch_up', touch)
|
||||
|
||||
kivy_benchmark(dispatch)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('n', [1, 10, 100, 1_000])
|
||||
@pytest.mark.parametrize('name', ['label', 'button'])
|
||||
@pytest.mark.parametrize('tick', ['tick', 'no_tick'])
|
||||
def test_random_label_create(kivy_benchmark, n, name, tick):
|
||||
from kivy.clock import Clock
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.button import Button
|
||||
label = Label(text='*&^%')
|
||||
button = Button(text='*&^%')
|
||||
cls = Label if name == 'label' else Button
|
||||
|
||||
labels = []
|
||||
k = len(ascii_letters)
|
||||
for x in range(n):
|
||||
label = [ascii_letters[randint(0, k - 1)] for _ in range(10)]
|
||||
labels.append(''.join(label))
|
||||
|
||||
def make_labels():
|
||||
o = []
|
||||
for text in labels:
|
||||
o.append(cls(text=text))
|
||||
|
||||
if tick == 'tick':
|
||||
Clock.tick()
|
||||
kivy_benchmark(make_labels)
|
||||
|
||||
|
||||
def test_parse_kv(kivy_benchmark):
|
||||
from kivy.lang import Builder
|
||||
suffix = 0
|
||||
|
||||
def parse_kv():
|
||||
nonlocal suffix
|
||||
Builder.load_string(f"""
|
||||
<MyWidget{suffix}>:
|
||||
width: 55
|
||||
height: 37
|
||||
x: self.width + 5
|
||||
y: self.height + 32
|
||||
""")
|
||||
suffix += 1
|
||||
|
||||
# create one just so we don't incur loading cost
|
||||
parse_kv()
|
||||
kivy_benchmark(parse_kv)
|
||||
BIN
kivy/tests/test_button.png
Normal file
BIN
kivy/tests/test_button.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
42
kivy/tests/test_clipboard.py
Normal file
42
kivy/tests/test_clipboard.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from kivy.tests.common import GraphicUnitTest
|
||||
|
||||
|
||||
class ClipboardTestCase(GraphicUnitTest):
|
||||
|
||||
def setUp(self):
|
||||
from kivy.core.clipboard import Clipboard
|
||||
self._clippy = Clipboard
|
||||
clippy_types = Clipboard.get_types()
|
||||
cliptype = clippy_types[0]
|
||||
if 'UTF8_STRING' in clippy_types:
|
||||
cliptype = 'UTF8_STRING'
|
||||
self._cliptype = cliptype
|
||||
super(ClipboardTestCase, self).setUp()
|
||||
|
||||
def test_clipboard_not_dummy(self):
|
||||
clippy = self._clippy
|
||||
if clippy.__class__.__name__ == 'ClipboardDummy':
|
||||
self.fail('Something went wrong "dummy" clipboard is being used')
|
||||
|
||||
def test_clipboard_paste(self):
|
||||
clippy = self._clippy
|
||||
try:
|
||||
clippy.paste()
|
||||
except:
|
||||
self.fail(
|
||||
'Can not get data from clipboard')
|
||||
|
||||
def test_clipboard_copy(self):
|
||||
clippy = self._clippy
|
||||
try:
|
||||
clippy.copy(u"Hello World")
|
||||
except:
|
||||
self.fail(
|
||||
'Can not get put data to clipboard')
|
||||
|
||||
def test_clipboard_copy_paste(self):
|
||||
clippy = self._clippy
|
||||
txt1 = u"Hello 1"
|
||||
clippy.copy(txt1)
|
||||
ret = clippy.paste()
|
||||
self.assertEqual(txt1, ret)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user