Skip to content

Commit a62c6f5

Browse files
doc: usage: test examples via doctest
Signed-off-by: Bastian Krause <bst@pengutronix.de>
1 parent 80c6583 commit a62c6f5

1 file changed

Lines changed: 93 additions & 20 deletions

File tree

doc/usage.rst

Lines changed: 93 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,17 @@ Targets
188188
Nevertheless, we explain this in the following to clarify the underlying concepts,
189189
and how to work with targets on a lower level, e.g. in strategies.
190190

191-
At the lower level, a :any:`Target` can be created directly::
191+
At the lower level, a :any:`Target` can be created directly:
192+
193+
.. doctest::
192194

193195
>>> from labgrid import Target
194196
>>> t = Target('example')
195197

196198
Next, any required :any:`Resource` objects can be created, which each represent
197-
a piece of hardware to be used with labgrid::
199+
a piece of hardware to be used with labgrid:
200+
201+
.. doctest::
198202

199203
>>> from labgrid.resource import RawSerialPort
200204
>>> rsp = RawSerialPort(t, name=None, port='/dev/ttyUSB0')
@@ -205,13 +209,17 @@ a piece of hardware to be used with labgrid::
205209
same type, you can set the name to ``None``.
206210

207211
Further on, a :any:`Driver` encapsulates logic how to work with resources.
208-
Drivers need to be created on the :any:`Target`::
212+
Drivers need to be created on the :any:`Target`:
213+
214+
.. doctest::
209215

210216
>>> from labgrid.driver import SerialDriver
211217
>>> sd = SerialDriver(t, name=None)
212218

213219
As the :any:`SerialDriver` declares a binding to a :any:`SerialPort`, the target binds it
214-
to the resource object created above::
220+
to the resource object created above:
221+
222+
.. doctest::
215223

216224
>>> sd.port
217225
RawSerialPort(target=Target(name='example', env=None), name=None, state=<BindingState.bound: 1>, avail=True, port='/dev/ttyUSB0', speed=115200)
@@ -232,7 +240,21 @@ other applications while the :any:`SerialDriver` is activated.
232240
If we use a car analogy here, binding is the process of screwing the car parts
233241
together, and activation is igniting the engine.
234242

235-
After activation, we can use the driver to do our work::
243+
After activation, we can use the driver to do our work:
244+
245+
.. testsetup:: driver-activation
246+
247+
from labgrid.resource import RawSerialPort
248+
from labgrid.driver import SerialDriver
249+
from labgrid import Target
250+
251+
t = Target('example')
252+
rsp = RawSerialPort(t, name=None, port='/dev/ttyUSB0')
253+
sd = SerialDriver(t, name=None)
254+
sd.serial.open = Mock()
255+
sd.serial.write = Mock(return_value=4)
256+
257+
.. doctest:: driver-activation
236258

237259
>>> t.activate(sd)
238260
>>> sd.write(b'test')
@@ -249,7 +271,9 @@ exception, e.g.::
249271
FileNotFoundError: [Errno 2] No such file or directory: '/dev/ttyUSB0'
250272

251273
Active drivers can be accessed by class (any :any:`Driver <labgrid.driver>` or
252-
:any:`Protocol <labgrid.protocol>`) using some syntactic sugar::
274+
:any:`Protocol <labgrid.protocol>`) using some syntactic sugar:
275+
276+
.. doctest::
253277

254278
>>> from labgrid import Target
255279
>>> from labgrid.driver.fake import FakeConsoleDriver
@@ -263,7 +287,17 @@ Active drivers can be accessed by class (any :any:`Driver <labgrid.driver>` or
263287

264288
Driver Deactivation
265289
^^^^^^^^^^^^^^^^^^^
266-
Driver deactivation works in a similar manner::
290+
Driver deactivation works in a similar manner:
291+
292+
.. testsetup:: driver-deactivation
293+
294+
from labgrid import Target
295+
from labgrid.driver.fake import FakeConsoleDriver
296+
target = Target('main')
297+
console = FakeConsoleDriver(target, 'console')
298+
target.activate(console)
299+
300+
.. doctest:: driver-deactivation
267301

268302
>>> target.deactivate(console)
269303
[FakeConsoleDriver(target=Target(name='main', env=None), name='console', state=<BindingState.bound: 1>, txdelay=0.0)]
@@ -289,7 +323,14 @@ Target Cleanup
289323
^^^^^^^^^^^^^^
290324
After you are done with the target, optionally call the cleanup method on your
291325
target. While labgrid registers an ``atexit`` handler to cleanup targets, this has
292-
the advantage that exceptions can be handled by your application::
326+
the advantage that exceptions can be handled by your application:
327+
328+
.. testsetup:: target-cleanup
329+
330+
from labgrid import Target
331+
target = Target('main')
332+
333+
.. doctest:: target-cleanup
293334

294335
>>> try:
295336
... target.cleanup()
@@ -307,6 +348,7 @@ For this use-case, labgrid can construct targets from a configuration file in
307348
YAML format:
308349

309350
.. code-block:: yaml
351+
:name: example-env.yaml
310352
311353
targets:
312354
example:
@@ -316,19 +358,36 @@ YAML format:
316358
drivers:
317359
SerialDriver: {}
318360
319-
To parse this configuration file, use the :any:`Environment` class::
361+
To parse this configuration file, use the :any:`Environment` class:
362+
363+
.. doctest::
320364

321365
>>> from labgrid import Environment
322366
>>> env = Environment('example-env.yaml')
323367

324368
Using :any:`Environment.get_target`, the configured `Targets` can be retrieved
325369
by name.
326-
Without an argument, `get_target` would default to 'main'::
370+
Without an argument, `get_target` would default to 'main':
371+
372+
.. doctest::
327373

328374
>>> t = env.get_target('example')
329375

330376
To access the target's console, the correct driver object can be found by using
331-
:any:`Target.get_driver`::
377+
:any:`Target.get_driver`:
378+
379+
.. testsetup:: get-driver
380+
381+
from labgrid import Environment
382+
383+
env = Environment('example-env.yaml')
384+
t = env.get_target('example')
385+
386+
s = t.get_driver('SerialDriver', activate=False)
387+
s.serial.open = Mock()
388+
s.serial.write = Mock(return_value=4)
389+
390+
.. doctest:: get-driver
332391

333392
>>> cp = t.get_driver('ConsoleProtocol')
334393
>>> cp
@@ -454,7 +513,9 @@ access this board:
454513
login_prompt: ' login: '
455514
username: 'root'
456515
457-
We then add the following test in a file called ``test_example.py``::
516+
We then add the following test in a file called ``test_example.py``:
517+
518+
.. code-block:: python
458519
459520
def test_echo(target):
460521
command = target.get_driver('CommandProtocol')
@@ -482,15 +543,19 @@ Custom Fixture Example
482543
When writing many test cases which use the same driver, we can get rid of some
483544
common code by wrapping the `CommandProtocol` in a fixture.
484545
As pytest always executes the ``conftest.py`` file in the test suite directory,
485-
we can define additional fixtures there::
546+
we can define additional fixtures there:
547+
548+
.. code-block:: python
486549
487550
import pytest
488551
489552
@pytest.fixture(scope='session')
490553
def command(target):
491554
return target.get_driver('CommandProtocol')
492555
493-
With this fixture, we can simplify the ``test_example.py`` file to::
556+
With this fixture, we can simplify the ``test_example.py`` file to:
557+
558+
.. code-block:: python
494559
495560
def test_echo(command):
496561
result = command.run_check('echo OK')
@@ -499,7 +564,9 @@ With this fixture, we can simplify the ``test_example.py`` file to::
499564
Strategy Fixture Example
500565
~~~~~~~~~~~~~~~~~~~~~~~~
501566
When using a :any:`Strategy` to transition the target between states, it is
502-
useful to define a function scope fixture per state in ``conftest.py``::
567+
useful to define a function scope fixture per state in ``conftest.py``:
568+
569+
.. code-block:: python
503570
504571
import pytest
505572
@@ -528,7 +595,9 @@ useful to define a function scope fixture per state in ``conftest.py``::
528595
<http://doc.pytest.org/en/latest/capture.html#accessing-captured-output-from-a-test-function>`_.
529596

530597
With the fixtures defined above, switching between bootloader and Linux shells
531-
is easy::
598+
is easy:
599+
600+
.. code-block:: python
532601
533602
def test_barebox_initial(bootloader_command):
534603
stdout = bootloader_command.run_check('version')
@@ -595,13 +664,15 @@ Feature Flags
595664
~~~~~~~~~~~~~
596665
labgrid includes support for feature flags on a global and target scope.
597666
Adding a ``@pytest.mark.lg_feature`` decorator to a test ensures it is only
598-
executed if the desired feature is available::
667+
executed if the desired feature is available:
668+
669+
.. code-block:: python
599670
600671
import pytest
601672
602673
@pytest.mark.lg_feature("camera")
603674
def test_camera(target):
604-
[...]
675+
pass
605676
606677
Here's an example environment configuration:
607678

@@ -628,13 +699,15 @@ test because of the missing feature:
628699
629700
pytest will record the missing feature as the skip reason.
630701

631-
For tests with multiple required features, pass them as a list to pytest::
702+
For tests with multiple required features, pass them as a list to pytest:
703+
704+
.. code-block:: python
632705
633706
import pytest
634707
635708
@pytest.mark.lg_feature(["camera", "console"])
636709
def test_camera(target):
637-
[...]
710+
pass
638711
639712
Features do not have to be set per target, they can also be set via the global
640713
features key:

0 commit comments

Comments
 (0)