Module concur.testing
Routines for automated testing.
The current automation/testing setup is work-in-progress, and the interface may change radically in future versions. There are some usability issues that I am not entirely happy with.
See the tests directory for usage examples.
Expand source code
""" Routines for automated testing.
The current automation/testing setup is work-in-progress, and the interface may change radically
in future versions. There are some usability issues that I am not entirely happy with.
See the [tests directory](https://github.com/potocpav/python-concur/tree/master/tests) for usage examples.
"""
import os
import imgui
import numpy as np # for floating point ranges
from functools import partial
from concur.integrations.puppet import PuppetRenderer, main
from concur.draw import polyline
from concur.core import orr, optional
__pdoc__ = dict(test=False)
def test(widget_gen, slow=None, draw_cursor=True, width=512, height=512, *args, **argv):
return main(
lambda puppet_renderer: widget_gen(draw_cursor, Testing(puppet_renderer, slow)),
"Automatic Tester",
width, height,
*args, **argv)
def test_widget(f):
""" Function decorator for testing functions.
Dead simple usage example, which just displays a button for a moment is:
```python
@c.testing.test_widget
def test_example(tester):
yield from c.orr([c.button("Test Button"), tester.pause()])
if __name__ == '__main__':
test_example()
```
This can be invoked either directly (`python test_example.py`), or using PyTest (`pytest -k test_example`).
To slow the test down, set the environmental variable `SLOW_TEST=1`:
```bash
SLOW_TEST=1 python test_example.py
# or
SLOW_TEST=1 pytest -k test_example
```
The decorated testing function takes a single argument `tester`, which contains a `Testing` class instance.
This class provides convenient functions for user input automation, wrapping the raw user interaction
primitives from `concur.integrations.puppet.PuppetRenderer`.
"""
def widget_gen(draw_cursor, tester):
io = imgui.get_io()
io.mouse_draw_cursor = draw_cursor
yield from f(tester)
def g(*args, **argv):
draw_cursor = 'draw_cursor' in argv and argv['draw_cursor']
return test(widget_gen, *args, **argv)
return g
def benchmark_widget(f_gen):
""" Benchmark a widget (experimental).
See tests/test_draw.py for example usage.
"""
f = f_gen()
def widget(_):
for _ in range(240):
next(f)
yield
def g(benchmark):
benchmark.pedantic(main, (widget, "Perf Tester", 512, 512), dict(fps=None), rounds=1)
return g
class Testing(object):
""" Must be used in conjunction with the `concur.integrations.puppet` backend.
To setup all the plumbing effortlessly, use the `test_widget` decorator.
All the methods in this class are widgets, and they can be composed as usual using
`concur.core.orr`, `yield from`, and friends.
"""
def __init__(self, puppet_renderer, slow=None):
assert isinstance(puppet_renderer, PuppetRenderer)
self.puppet = puppet_renderer
if slow is None:
self.slow = 'SLOW_TEST' in os.environ and os.environ['SLOW_TEST'] == '1'
else:
self.slow = slow
self.marked = {}
def click(self, button=0):
"Click a given mouse button."
self.puppet.mouse_dn(button)
if self.slow:
for i in range(10):
yield
yield
self.puppet.mouse_up(button)
def click_next(self):
"Click the next widget."
x, y = imgui.get_cursor_screen_pos()
yield from self.move_cursor(x + 5, y + 5)
yield from self.pause()
yield from self.click()
# Give the widget time to react
yield
yield
def mark(self, name, widget):
""" Display a widget, but mark it with a name so it can be interacted with at a later point
using methods such as `click_marked`. """
while True:
self.marked[name] = imgui.get_cursor_screen_pos()
try:
next(widget)
except StopIteration as e:
return e.value
yield
def click_marked(self, name, x=5, y=5):
"Click the given `marked` widget. Optionally, specify the click offset `x, y` coords."
if name not in self.marked:
raise ValueError(f"Name '{name}' was not previously marked.")
x0, y0 = self.marked[name]
yield from self.move_cursor(x0 + x, y0 + y)
yield from self.pause()
yield from self.click()
yield
def move_cursor(self, x, y):
"Move cursor to a given position."
io = imgui.get_io()
ox, oy = io.mouse_pos
yield
if self.slow:
for f in np.linspace(0, 1, 30):
self.puppet.set_mouse_pos(x * f + ox * (1 - f), y * f + oy * (1 - f))
yield
else:
self.puppet.set_mouse_pos(x, y)
yield
def scroll_up(self):
"Scroll up."
self.puppet.scroll_up()
yield
def scroll_dn(self):
"Scroll down."
self.puppet.scroll_dn()
yield
def mouse_up(self, button=0):
"Release the given mouse button."
self.puppet.mouse_up(button)
yield
def mouse_dn(self, button=0):
"Push the given mouse button."
self.puppet.mouse_dn(button)
yield
def write_char(self, ch):
"Write a given character."
self.puppet.write_char(ch)
yield
def pause(self, nframes=0):
"""Pause for a specified number of frames.
If `nframes` <= 0, the pause length depends on the
environment variable `TEST_SLOW`.
"""
if nframes <= 0:
if self.slow:
for _ in range(30):
yield
yield
else:
for _ in range(nframes):
yield
Functions
def benchmark_widget(f_gen)
-
Benchmark a widget (experimental).
See tests/test_draw.py for example usage.
Expand source code
def benchmark_widget(f_gen): """ Benchmark a widget (experimental). See tests/test_draw.py for example usage. """ f = f_gen() def widget(_): for _ in range(240): next(f) yield def g(benchmark): benchmark.pedantic(main, (widget, "Perf Tester", 512, 512), dict(fps=None), rounds=1) return g
def test_widget(f)
-
Function decorator for testing functions.
Dead simple usage example, which just displays a button for a moment is:
@c.testing.test_widget def test_example(tester): yield from c.orr([c.button("Test Button"), tester.pause()]) if __name__ == '__main__': test_example()
This can be invoked either directly (
python test_example.py
), or using PyTest (pytest -k test_example
). To slow the test down, set the environmental variableSLOW_TEST=1
:SLOW_TEST=1 python test_example.py # or SLOW_TEST=1 pytest -k test_example
The decorated testing function takes a single argument
tester
, which contains aTesting
class instance. This class provides convenient functions for user input automation, wrapping the raw user interaction primitives fromPuppetRenderer
.Expand source code
def test_widget(f): """ Function decorator for testing functions. Dead simple usage example, which just displays a button for a moment is: ```python @c.testing.test_widget def test_example(tester): yield from c.orr([c.button("Test Button"), tester.pause()]) if __name__ == '__main__': test_example() ``` This can be invoked either directly (`python test_example.py`), or using PyTest (`pytest -k test_example`). To slow the test down, set the environmental variable `SLOW_TEST=1`: ```bash SLOW_TEST=1 python test_example.py # or SLOW_TEST=1 pytest -k test_example ``` The decorated testing function takes a single argument `tester`, which contains a `Testing` class instance. This class provides convenient functions for user input automation, wrapping the raw user interaction primitives from `concur.integrations.puppet.PuppetRenderer`. """ def widget_gen(draw_cursor, tester): io = imgui.get_io() io.mouse_draw_cursor = draw_cursor yield from f(tester) def g(*args, **argv): draw_cursor = 'draw_cursor' in argv and argv['draw_cursor'] return test(widget_gen, *args, **argv) return g
Classes
class Testing (puppet_renderer, slow=None)
-
Must be used in conjunction with the
concur.integrations.puppet
backend.To setup all the plumbing effortlessly, use the
test_widget()
decorator.All the methods in this class are widgets, and they can be composed as usual using
orr()
,yield from
, and friends.Expand source code
class Testing(object): """ Must be used in conjunction with the `concur.integrations.puppet` backend. To setup all the plumbing effortlessly, use the `test_widget` decorator. All the methods in this class are widgets, and they can be composed as usual using `concur.core.orr`, `yield from`, and friends. """ def __init__(self, puppet_renderer, slow=None): assert isinstance(puppet_renderer, PuppetRenderer) self.puppet = puppet_renderer if slow is None: self.slow = 'SLOW_TEST' in os.environ and os.environ['SLOW_TEST'] == '1' else: self.slow = slow self.marked = {} def click(self, button=0): "Click a given mouse button." self.puppet.mouse_dn(button) if self.slow: for i in range(10): yield yield self.puppet.mouse_up(button) def click_next(self): "Click the next widget." x, y = imgui.get_cursor_screen_pos() yield from self.move_cursor(x + 5, y + 5) yield from self.pause() yield from self.click() # Give the widget time to react yield yield def mark(self, name, widget): """ Display a widget, but mark it with a name so it can be interacted with at a later point using methods such as `click_marked`. """ while True: self.marked[name] = imgui.get_cursor_screen_pos() try: next(widget) except StopIteration as e: return e.value yield def click_marked(self, name, x=5, y=5): "Click the given `marked` widget. Optionally, specify the click offset `x, y` coords." if name not in self.marked: raise ValueError(f"Name '{name}' was not previously marked.") x0, y0 = self.marked[name] yield from self.move_cursor(x0 + x, y0 + y) yield from self.pause() yield from self.click() yield def move_cursor(self, x, y): "Move cursor to a given position." io = imgui.get_io() ox, oy = io.mouse_pos yield if self.slow: for f in np.linspace(0, 1, 30): self.puppet.set_mouse_pos(x * f + ox * (1 - f), y * f + oy * (1 - f)) yield else: self.puppet.set_mouse_pos(x, y) yield def scroll_up(self): "Scroll up." self.puppet.scroll_up() yield def scroll_dn(self): "Scroll down." self.puppet.scroll_dn() yield def mouse_up(self, button=0): "Release the given mouse button." self.puppet.mouse_up(button) yield def mouse_dn(self, button=0): "Push the given mouse button." self.puppet.mouse_dn(button) yield def write_char(self, ch): "Write a given character." self.puppet.write_char(ch) yield def pause(self, nframes=0): """Pause for a specified number of frames. If `nframes` <= 0, the pause length depends on the environment variable `TEST_SLOW`. """ if nframes <= 0: if self.slow: for _ in range(30): yield yield else: for _ in range(nframes): yield
Methods
def click(self, button=0)
-
Click a given mouse button.
Expand source code
def click(self, button=0): "Click a given mouse button." self.puppet.mouse_dn(button) if self.slow: for i in range(10): yield yield self.puppet.mouse_up(button)
def click_marked(self, name, x=5, y=5)
-
Click the given
marked
widget. Optionally, specify the click offsetx, y
coords.Expand source code
def click_marked(self, name, x=5, y=5): "Click the given `marked` widget. Optionally, specify the click offset `x, y` coords." if name not in self.marked: raise ValueError(f"Name '{name}' was not previously marked.") x0, y0 = self.marked[name] yield from self.move_cursor(x0 + x, y0 + y) yield from self.pause() yield from self.click() yield
def click_next(self)
-
Click the next widget.
Expand source code
def click_next(self): "Click the next widget." x, y = imgui.get_cursor_screen_pos() yield from self.move_cursor(x + 5, y + 5) yield from self.pause() yield from self.click() # Give the widget time to react yield yield
def mark(self, name, widget)
-
Display a widget, but mark it with a name so it can be interacted with at a later point using methods such as
click_marked
.Expand source code
def mark(self, name, widget): """ Display a widget, but mark it with a name so it can be interacted with at a later point using methods such as `click_marked`. """ while True: self.marked[name] = imgui.get_cursor_screen_pos() try: next(widget) except StopIteration as e: return e.value yield
def mouse_dn(self, button=0)
-
Push the given mouse button.
Expand source code
def mouse_dn(self, button=0): "Push the given mouse button." self.puppet.mouse_dn(button) yield
def mouse_up(self, button=0)
-
Release the given mouse button.
Expand source code
def mouse_up(self, button=0): "Release the given mouse button." self.puppet.mouse_up(button) yield
def move_cursor(self, x, y)
-
Move cursor to a given position.
Expand source code
def move_cursor(self, x, y): "Move cursor to a given position." io = imgui.get_io() ox, oy = io.mouse_pos yield if self.slow: for f in np.linspace(0, 1, 30): self.puppet.set_mouse_pos(x * f + ox * (1 - f), y * f + oy * (1 - f)) yield else: self.puppet.set_mouse_pos(x, y) yield
def pause(self, nframes=0)
-
Pause for a specified number of frames.
If
nframes
<= 0, the pause length depends on the environment variableTEST_SLOW
.Expand source code
def pause(self, nframes=0): """Pause for a specified number of frames. If `nframes` <= 0, the pause length depends on the environment variable `TEST_SLOW`. """ if nframes <= 0: if self.slow: for _ in range(30): yield yield else: for _ in range(nframes): yield
def scroll_dn(self)
-
Scroll down.
Expand source code
def scroll_dn(self): "Scroll down." self.puppet.scroll_dn() yield
def scroll_up(self)
-
Scroll up.
Expand source code
def scroll_up(self): "Scroll up." self.puppet.scroll_up() yield
def write_char(self, ch)
-
Write a given character.
Expand source code
def write_char(self, ch): "Write a given character." self.puppet.write_char(ch) yield