lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Fri, 11 Mar 2022 17:24:41 +0100
From:   Vincent Whitchurch <vincent.whitchurch@...s.com>
To:     <linux-kernel@...r.kernel.org>
CC:     <kernel@...s.com>,
        Vincent Whitchurch <vincent.whitchurch@...s.com>,
        <devicetree@...r.kernel.org>, <linux-um@...ts.infradead.org>,
        <shuah@...nel.org>, <brendanhiggins@...gle.com>,
        <linux-kselftest@...r.kernel.org>, <jic23@...nel.org>,
        <linux-iio@...r.kernel.org>, <lgirdwood@...il.com>,
        <broonie@...nel.org>, <a.zummo@...ertech.it>,
        <alexandre.belloni@...tlin.com>, <linux-rtc@...r.kernel.org>,
        <corbet@....net>, <linux-doc@...r.kernel.org>
Subject: [RFC v1 06/10] roadtest: add documentation

Add documentation for the roadtest device driver testing framework.
This includes a "how to write your first test" tutorial.

Signed-off-by: Vincent Whitchurch <vincent.whitchurch@...s.com>
---
 Documentation/dev-tools/index.rst    |   1 +
 Documentation/dev-tools/roadtest.rst | 669 +++++++++++++++++++++++++++
 2 files changed, 670 insertions(+)
 create mode 100644 Documentation/dev-tools/roadtest.rst

diff --git a/Documentation/dev-tools/index.rst b/Documentation/dev-tools/index.rst
index 4621eac290f4..44fea7c50dad 100644
--- a/Documentation/dev-tools/index.rst
+++ b/Documentation/dev-tools/index.rst
@@ -33,6 +33,7 @@ Documentation/dev-tools/testing-overview.rst
    kselftest
    kunit/index
    ktap
+   roadtest
 
 
 .. only::  subproject and html
diff --git a/Documentation/dev-tools/roadtest.rst b/Documentation/dev-tools/roadtest.rst
new file mode 100644
index 000000000000..114bf822e376
--- /dev/null
+++ b/Documentation/dev-tools/roadtest.rst
@@ -0,0 +1,669 @@
+========
+Roadtest
+========
+
+Roadtest is a device-driver testing framework.  It tests drivers under User
+Mode Linux using models of the hardware.  The tests cases and hardware models
+are written in Python, the former using the built-in unittest framework.
+
+Roadtest is meant to be used for relatively simple drivers, such as the ones
+part of the IIO, regulator or RTC subsystems.
+
+Drivers are tested via their userspace interfaces and interact with hardware
+models which allow tests to inject values into registers and assert that
+drivers control the hardware in the right way and react as expected to stimuli.
+
+Installing the requirements
+===========================
+
+Addition to the normal requirements for building kernels, *running* roadtest
+requires Python 3.9 or later, including the development libraries:
+
+.. code-block:: shell
+
+  apt-get -y install python3.9 libpython3.9-dev device-tree-compiler
+
+There is also support for running the tests in a Docker container without
+having to install any packages.
+
+Running roadtest
+================
+
+To run the tests, run the following command from the base of a kernel source
+tree:
+
+.. code-block:: shell
+
+  $ make -C tools/testing/roadtest
+
+Or, if you prefer to use the Docker container:
+
+.. code-block:: shell
+
+  $ make -C tools/testing/roadtest DOCKER=1
+
+Either of these commands will build a kernel and run all roadtests.
+
+.. note::
+
+  Roadtest builds the kernel out-of-tree.  The kernel build system may instruct
+  you to clean your tree if you have previously performed an in-tree build.  You
+  can pass the usual ``-jNN`` options to parallelize the build.  The tests
+  themselves are currently always run sequentially.
+
+Writing roadtests
+=================
+
+Tutorial: Writing your first roadtest
+-------------------------------------
+
+You may find it simplest to have a look at the existing tests and base your new
+tests on them, but if you prefer, this section provides a tutorial which will
+guide you to write a new basic test from scratch.
+
+Even if you're not too keen on following the tutorial hands-on, you're
+encouraged to skim through it since there are useful debugging tips and notes
+on roadtest's internals which could be useful to know before diving in and
+writing tests.
+
+A quick note on the terminology before we begin: we'll refer to the framework
+itself as "roadtest" or just "the framework", and we'll call a driver test
+which uses this framework a "roadtest" or just a "test".
+
+Goal for the test
+~~~~~~~~~~~~~~~~~
+
+In this tutorial, we'll add a basic test for one of the features of the
+VCNL4000 light sensor driver which is a part of the IIO subsystem
+(``drivers/iio/light/vcnl4000.c``).
+
+This driver supports a bunch of related proximity and ambient light sensor
+chips which communicate using the I2C protocol; we'll be testing the VCNL4000
+variant.  The datasheet for the chip is, at the time of writing, available
+`here <https://cdn-shop.adafruit.com/datasheets/vcnl4000.pdf>`_.
+
+The test will check that the driver correctly reads and reports the illuminance
+values from the hardware to userspace via the IIO framework.
+
+Test file placement
+~~~~~~~~~~~~~~~~~~~
+
+Roadtests are placed under ``tools/testing/roadtest/roadtest/tests``.  (In case
+you're wondering, the second ``roadtest`` is to create a Python package, so
+that imports of ``roadtest`` work without having to mess with module search
+paths.)
+
+Tests are organized by subsystem.  Normally we'd put our IIO light sensor tests
+under ``iio/light/`` (below the ``tests`` directory), but since there is
+already a VCNL4000 test there, we'll create a new subsystem directory called
+``tutorial`` and put our test there in a new file called ``test_tutorial.py``.
+
+We'll also need to create an empty ``__init__.py`` in that directory to allow
+Python to recognize it as a package.
+
+All the commands in this tutorial should be executed from the
+``tools/testing/roadtest`` directory inside the kernel source tree.  (To reduce
+noise, we won't show the current working directory before the ``$`` in future
+command line examples.)
+
+.. code-block:: shell
+
+  tools/testing/roadtest$ mkdir -p roadtest/tests/tutorial/
+  tools/testing/roadtest$ touch roadtest/tests/tutorial/__init__.py
+
+Building the module
+~~~~~~~~~~~~~~~~~~~
+
+First, we'll need to ensure that our driver is built.  To do that, we'll add
+the appropriate config option to built our driver as a module.  The lines
+should be written to a new file called ``config`` in the ``tutorial``
+directory.  Roadtest will gather all ``config`` files placed anywhere under
+``tests`` and build a kernel with the combined config.
+
+.. code-block:: shell
+
+   $ echo CONFIG_VCNL4000=m >> roadtest/tests/tutorial/config
+
+.. note::
+
+  This driver will actually be built even if you don't add this config, since
+  it's already present in the ``roadtest/tests/iio/light/config`` used by the
+  existing VCNL4000 test.  Roadtest uses a single build for all tests.
+
+Loading the module from the test
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We've set up our module to be built, so we can now start working on the test
+case iself.  We'll start with the following few lines of code.  Tests are
+written Python's built-in `unittest
+<https://docs.python.org/3/library/unittest.html>`_ module.  This tutorial will
+assume familiariy with that framework; see the Python documentation for more
+information.
+
+Test classes should subclass ``roadtest.core.suite.UMLTestCase`` instead of
+``unittest.TestCase``.  This informs the roadtest core code that the test
+should be run inside UML.
+
+.. note::
+
+  There are several "real" unit tests for the framework itself; these subclass
+  ``unittest.TestCase`` directly and are run on the host system.  You'll see
+  these run in the beginning when you run roadtest.
+
+All this test currently does is insert our driver's module, do nothing, and
+then remove our driver's kernel module.  (The ``roadtest.core.modules.Module``
+class implements a ``ContextManager`` which automatically cleans up using the
+``with`` statement.)
+
+.. code-block:: python
+
+  from roadtest.core.suite import UMLTestCase
+  from roadtest.core.modules import Module
+
+  class TestTutorial(UMLTestCase):
+    def test_illuminance(self) -> None:
+      with Module("vcnl4000"):
+        pass
+
+You can now build the kernel and run roadtest with:
+
+.. code-block:: shell
+
+  $ make
+
+.. note::
+
+  Make sure you have all the dependencies described at the beginning of the
+  document installed.  You can also use a Docker container, append ``DOCKER=1``
+  to all the ``make`` commands in this tutorial if you want to do that.
+
+You should see your new test run and pass in the output of the above command:
+
+.. code-block::
+
+  ...
+  test_illuminance (tests.tutorial.test_tutorial.TestTutorial) ... ok
+  ...
+
+Shortening feedback loops
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+While just running ``make`` runs your new test, it also runs all the *other*
+tests too, and what's more, it calls in to the kernel build system every time,
+and that can be relatively slow even if there's nothing to be rebuilt.
+
+When you're only working on writing tests, and not modifying the driver or the
+kernel source, you can avoid calling into Kbuild by passing ``KBUILD=0`` to the
+``make`` invocation.  For example:
+
+.. code-block:: shell
+
+  $ make KBUILD=0
+
+To only run specific tests, you can use the ``--filter`` option to roadtest's
+main script (implemented in ``roadtest.cmd.main``) which takes a wildcard
+pattern.  Only tests whoses names match the pattern are run.
+
+Options to the main script are passed via the ``OPTS`` variable.  So the
+following would both skip the kernel build and only run your test:
+
+.. code-block:: shell
+
+  $ make KBUILD=0 OPTS="--filter tutorial"
+
+.. tip::
+
+  Roadtest builds the kernel inside a directory called ``.roadtest`` in your
+  kernel source tree.  Logs from UML are saved as
+  ``.roadtest/roadtest-work/uml.txt`` and logs from roadtest's backend (more on
+  that later) are at ``.roadtest/roadtest-work/backend.txt``.  It's sometimes
+  useful to keep a terminal open running ``tail -f`` on these files while
+  developing roadtests.
+
+Adding a device
+~~~~~~~~~~~~~~~
+
+Our basic test only loads and unloads the module, so the next step is to
+actually get our driver to probe and bind to a device.  On many systems,
+devices are instantiated based on the hardware descriptions in devicetree, and
+this is the case on roadtest's UML-based system too.  See
+:ref:`Documentation/driver-api/driver-model/binding.rst <binding>` and
+:ref:`Documentation/devicetree/usage-model.rst <usage-model>` for more
+information.
+
+When working on real harwdare, the hardware design specifies at what address
+and on which I2C bus the hardware sensor chip is connected.  Roadtest provides
+a virtual I2C bus and the test can chose to place devices at any valid address
+on this bus.
+
+In this tutorial, we'll use a hard coded device address of ``0x42`` and set the
+``run_separately`` flag on the test, asking roadtest to run our test in a
+separate UML instance so that we know that no other test has tried to put a
+device at that I2C address.
+
+.. note::
+
+  Normally, roadtests use what the framework refers to as *relocatable
+  devicetree fragments* (unrelated to the fragments used in devicetree
+  overlays).  These do not use fixed addreses for specific devices, but instead
+  allow the framework to freely assign addresses.  This allows several
+  different, independent tests can be run using one devicetree and one UML
+  instance (to save on startup time costs), without having to coordinate
+  selection of device addesses.
+
+  When writing "real" roadtests (after you're done with this tutorial), you too
+  should use relocatable fragments.  See the existing tests for examples.
+
+The framework's devicetree module (``roadtest.core.devicetree``) includes a
+base tree that provides an I2C controller node (appropriately named ``i2c``)
+for the virtual I2C, so we will add our new device under that node.
+
+Unlike on a default Linux system, just adding the node to the devicetree won't
+get our I2C driver to automatically bind to the driver when we load the module.
+This is because roadtest's ``init.sh`` (a script which runs inside UML after
+the kernel boots up) turns off automatic probing on the I2C bus, in order to
+give the test cases full control of when things get probed.
+
+So we'll have ask the ``test_illuminance()`` method to get the ``vcnl4000``
+driver (that's the name of the I2C driver which the module registers, and
+that's not necessarily the same as the name of the module) to explicitly bind
+to our chosen ``0x42`` I2C device using some of the helper classes in the
+framework:
+
+.. code-block:: python
+
+  from roadtest.core.devicetree import DtFragment
+  from roadtest.core.devices import I2CDriver
+
+  class TestTutorial(UMLTestCase):
+    run_separately = True
+    dts = DtFragment(
+      src="""
+  &i2c {
+      light-sensor@42 {
+          compatible = "vishay,vcnl4000";
+          reg = <0x42>;
+      };
+  };
+      """,
+    )
+
+    def test_illuminance(self) -> None:
+      with (
+        Module("vcnl4000"),
+        I2CDriver("vcnl4000").bind(0x42) as dev,
+      ):
+        pass
+
+You can run this test using the same ``make`` command you used previously.
+This time, rather than an "ok", you should see roadtest complain about an error
+during your test:
+
+.. code-block::
+
+  ======================================================================
+  ERROR: test_illuminance (tests.tutorial.test_tutorial.TestTutorial)
+  ----------------------------------------------------------------------
+  Backend log:
+    Traceback (most recent call last):
+      File ".../roadtest/backend/i2c.py", line 35, in write
+        raise Exception("No I2C model loaded")
+    Exception: No I2C model loaded
+    Traceback (most recent call last):
+      File ".../roadtest/backend/i2c.py", line 29, in read
+        raise Exception("No I2C model loaded")
+    Exception: No I2C model loaded
+
+  UML log:
+    [ 1220.410000][   T19] vcnl4000: probe of 0-0042 failed with error -5
+
+  Traceback (most recent call last):
+    File ".../roadtest/tests/tutorial/test_tutorial.py", line 21, in test_illuminance
+      with (
+    File "/usr/lib/python3.9/contextlib.py", line 119, in __enter__
+      return next(self.gen)
+    File ".../roadtest/core/devices.py", line 32, in bind
+      f.write(dev.id.encode())
+  OSError: [Errno 5] Input/output error
+
+To understand and fix this error, we'll have to learn a bit about how roadtest
+works under the hood.
+
+Adding a hardware model
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Roadtest's *backend* is what allows the hardware to modelled for the sake of
+driver testing.  The backend runs outside of UML and communication between the
+drivers and the models goes via ``virtio-uml``, a shared-memory based
+communication protocol.  At its lowest level, the backend is written in C and
+implements virtio devices for ``virtio-i2c`` and ``virtio-gpio``, both of which
+have respective virtio drivers which run inside UML and provide the virtual I2C
+bus (and GPIO controller) whose nodes are available in the devicetree.
+
+The C backend embeds a Python interpreter which runs a Python module which
+implements the I2C bus model.  It's that Python module which is complaining now
+that it does not have any I2C device model to handle the I2C transactions that
+it received from UML.  This is quite understandable since we haven't
+implemented one yet!
+
+.. note::
+
+  In the error message above, you'll also notice an error ``printk()`` from the
+  driver (as part of the *UML log*, which includes kernel console messages), as
+  well as the exception stacktrace from the test case itself.  The ``-EIO``
+  seen inside UML is a result of the roadtest backend failing the I2C
+  transaction due to the exception.
+
+Models are placed in the same source file as the test cases.  The model and
+the test cases will however run in two different Python interpreters on two
+different systems (the test case inside UML, and the model inside the backend
+on your host).
+
+For I2C, the interface our model needs to implement is specified by the
+Abstract Base Class ``roadtest.backend.i2c.I2CModel`` (which can be found,
+following Python's standard naming conventions, in the file
+``roadtest/backend/i2c.py``).  You can see that it expects the model to
+implement ``read()`` and ``write()`` functions which transmit and receive the
+raw bytes of the I2C transaction.
+
+Our VCNL4000 device uses the SMBus protocol which is a subset of the I2C
+protocol, so we can use a higher-level class to base our implementation off,
+``roadtest.backend.i2c.SMBusModel``.  This one takes care of doing segmentation
+of the I2C requests, and expects subclasses to implement ``reg_read()`` and
+``reg_write()`` methods which will handle the register access for the device.
+
+For our initial model, we'll just going to just make our ``reg_read()`` and
+``reg_write()`` methods read and store the register values in a dictionary.
+We'll need some initial values for the registers, and for these we use the
+values which are specified in the VCNL4000's datasheet.  We won't bother with
+creating constants for the register addresses and we'll just specify them in
+hex:
+
+.. code-block:: python
+
+  from typing import Any
+  from roadtest.backend.i2c import SMBusModel
+
+  class VCNL4000(SMBusModel):
+      def __init__(self, **kwargs: Any) -> None:
+          super().__init__(regbytes=1, **kwargs)
+          self.regs = {
+              0x80: 0b_1000_0000,
+              0x81: 0x11,
+              0x82: 0x00,
+              0x83: 0x00,
+              0x84: 0x00,
+              0x85: 0x00,
+              0x86: 0x00,
+              0x87: 0x00,
+              0x88: 0x00,
+              0x89: 0x00,
+          }
+
+      def reg_read(self, addr: int) -> int:
+          val = self.regs[addr]
+          return val
+
+      def reg_write(self, addr: int, val: int) -> None:
+          assert addr in self.regs
+          self.regs[addr] = val
+
+Then we need to modify the test function to ask the backend to load this model:
+
+.. code-block:: python
+  :emphasize-lines: 1,6
+
+  from roadtest.core.hardware import Hardware
+
+  def test_illuminance(self) -> None:
+    with (
+      Module("vcnl4000"),
+      Hardware("i2c").load_model(VCNL4000),
+      I2CDriver("vcnl4000").bind(0x42),
+    ):
+      pass
+
+Now run the test again.  You should see the test pass, meaning that the driver
+successfully talked to and recognized your hardware model.  (You can look at
+the UML and backend logs mentioned earlier to confirm this.)
+
+.. tip::
+
+  You can add arbitrary command line arguments to UML using the
+  ``--uml-append`` option.  For example, while developing tests for I2C
+  drivers, it could be helpful to turn on the appropriate trace events and
+  arrange for them to be printed to the console (which you can then access via
+  the previously mentioned ``uml.txt``.):
+
+  .. code-block::
+
+    OPTS="--filter tutorial --uml-append tp_printk trace_event=i2c:*"
+
+Exploring the target
+~~~~~~~~~~~~~~~~~~~~
+
+Now that we've gotten the driver to probe to our new device, we want to get the
+test to read the illuminance value from the driver.  However, which file should
+the test read the value from?  IIO exposes the illuminance value in a sysfs
+file, but where do we find this file?
+
+If you have real hardware with a VCNL4000 chip and already running the vcnl4000
+driver, or are already very familiar with the IIO framework, you likely already
+know what sysfs files to read, but in our case, we can open up a shell on UML
+to manually explore the system and find the relevant sysfs files before
+implementing the rest of the test case.
+
+Roadtest's ``--shell`` option makes UML start a shell instead of exiting after
+the tests are run.  However, since our test case cleans up after itself (as
+it should) using context managers, neither the module nor the model would
+remain loaded after the test exists, which would make manual exploration
+difficult.
+
+To remedy this, we can combine ``--shell`` with temporary code in our test to
+_exit(2) after setting up everything:
+
+.. code-block:: python
+  :emphasize-lines: 5,7
+
+  def test_illuminance(self) -> None:
+    with (
+      Module("vcnl4000"),
+      Hardware("i2c").load_model(VCNL4000),
+      I2CDriver("vcnl4000").bind(0x42) as dev,
+    ):
+      print(dev.path)
+      import os; os._exit(1)
+
+.. note::
+
+  The communication between the test cases and the models uses a simple text
+  based protocol where the test cases write Python expressions to a file which
+  the backend reads and evaluates, so it is possible to load a model using only
+  shell commands, but this is undocumented.  See the source code if you need to
+  do this.
+
+We'll also need to ask UML to open up a terminal emulator (``con=xterm``) or start a telnet server
+and wait for a connection (``con=port:9000``).  See
+:ref:`Documentation/virt/uml/user_mode_linux_hotwo_v2.rst
+<user_mode_linux_hotwo_v2>` for more information about the required packages.
+These options can be passed to UML using ``--uml-append``.  So the final
+``OPTS`` argument is something like the following (you can combine this with
+the tracing options):
+
+.. code-block::
+
+  OPTS="--shell --uml-append con=xterm"
+
+.. tip::
+
+  ``con=xterm doesn``'t work in the Docker container, so use the telnet option
+  if you're running roadtest inside Docker.  ``screen -L //telnet localhost
+  9000`` or similar can be used to connect to UML.
+
+  When running *without* using Docker, the telnet option tends to leave UML's
+  ``port-helper`` running in the background, so you may have to ``kill(1)`` it
+  yourself after each run.
+
+Using the shell, you should be able to find the illuminance file under the
+device's sysfs path:
+
+.. code-block::
+
+  root@(none):/sys/bus/i2c/devices/0-0042# ls -1 iio\:device0/in*
+  iio:device0/in_illuminance_raw
+  iio:device0/in_illuminance_scale
+  iio:device0/in_proximity_nearlevel
+  iio:device0/in_proximity_raw
+
+You can also attempt to read the ``in_illuminance_raw`` file; you should see
+that it fails with something like this (with the trace events enabled):
+
+.. code-block::
+
+  root@(none):/sys/bus/i2c/devices/0-0042# cat iio:device0/in_illuminance_raw
+  [  151.270000][   T34] i2c_write: i2c-0 #0 a=042 f=0000 l=2 [80-10]
+  [  151.270000][   T34] i2c_result: i2c-0 n=1 ret=1
+  ...
+  [  152.030000][   T34] i2c_write: i2c-0 #0 a=042 f=0000 l=1 [80]
+  [  152.030000][   T34] i2c_read: i2c-0 #1 a=042 f=0001 l=1
+  [  152.030000][   T34] i2c_reply: i2c-0 #1 a=042 f=0001 l=1 [10]
+  [  152.030000][   T34] i2c_result: i2c-0 n=2 ret=2
+  [  152.070000][   T34] vcnl4000 0-0042: vcnl4000_measure() failed, data not ready
+
+Controlling register values
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Our next challenge is to get the ``in_illuminance_raw`` file to be read
+successfully.  From the I2C trace events above, or from looking at the
+``backend.txt`` (below), we can see that the driver repeatedly reads a
+particular register.
+
+.. code-block::
+
+  INFO - roadtest.core.control: START<roadtest.tests.tutorial.test_tutorial.TestTutorial.test_illuminance>
+  DEBUG - roadtest.core.control: backend.i2c.load_model(*('roadtest.tests.tutorial.test_tutorial', 'VCNL4000'), **{})
+  DEBUG - roadtest.backend.i2c: SMBus read addr=0x81 val=0x11
+  DEBUG - roadtest.backend.i2c: SMBus write addr=0x80 val=0x10
+  DEBUG - roadtest.backend.i2c: SMBus read addr=0x80 val=0x10
+  DEBUG - roadtest.backend.i2c: SMBus read addr=0x80 val=0x10
+  ...
+
+To understand this register, we need to take a look at the chip's datasheet and
+compare it with the driver code.  By doing so, we can see the driver is waiting
+for the hardware to signal that the data is ready by polling for a particular
+bit to be set.
+
+One simple way to set the data ready bit, which we'll use for the purpose of
+this tutorial, is to simply ensure that the model always returns reads to the
+0x80 register with that bit set.
+
+.. note::
+
+  This method wouldn't allow a test to be written to test the timeout handling,
+  but we won't bother with that in this tutorial.  You can explore the exising
+  roadtests for alternative solutions, such as setting the data ready bit
+  whenever the test injects new data and clearing it when the driver reads the
+  data.
+
+.. code-block:: python
+  :emphasize-lines: 4,5
+
+  def reg_read(self, addr: int) -> int:
+    val = self.regs[addr]
+
+    if addr == 0x80:
+      val |= 1 << 6
+
+    return val
+
+This should get the bit set and make the read succeed (you can check this using
+the shell), but we'd also like to return different values from the data
+registers rather the reset values we hardcoded in ``__init__``.  One way to do
+this is to have the test inject the values into the ALS result registers by
+having it call the ``reg_write()`` method of the model.  It can do this via the
+``Hardware`` object.
+
+.. note::
+
+  The test can call methods on the model but it can't receive return values
+  from these methods, nor can it set attributes on the model.  The model and
+  the test run on different systems and communication between them is
+  asynchronous.
+
+We'll combine this with a read of the sysfs file we identified and throw in an
+assertion to check that the value which the driver reports to userspace via
+that file matches the value which we inject into the hardware's result
+registers:
+
+.. code-block:: python
+   :emphasize-lines: 6,8,9-13
+
+    from roadtest.core.sysfs import read_int
+
+    def test_illuminance(self) -> None:
+        with (
+            Module("vcnl4000"),
+            Hardware("i2c").load_model(VCNL4000) as hw,
+            I2CDriver("vcnl4000").bind(0x42) as dev,
+        ):
+            hw.reg_write(0x85, 0x12)
+            hw.reg_write(0x86, 0x34)
+            self.assertEqual(
+                read_int(dev.path / "iio:device0/in_illuminance_raw", 0x1234)
+            )
+
+And that's it for this tutorial.  We've written a simple end-to-end test for
+one aspect of this driver with the help of a minimal model of the hardware.
+
+Verifying drivers' interactions with the hardware
+-------------------------------------------------
+
+The tutorial covered injection of values into hardware registers and how to
+check that the driver interprets the value exposed by the hardware correctly,
+but another important aspect of testing device drivers is to verify that the
+driver actually *controls* the hardware in the expected way.
+
+For example, if you are testing a regulator driver, you want to test that
+driver actually writes the correct voltage register in the hardware with the
+correct value when the driver is asked to set a voltage using the kernel's
+regulator API.
+
+To support this, roadtest integrates with Python's built-in `unittest.mock
+<https://docs.python.org/3/library/unittest.mock.html>`_ library.  The
+``update_mock()`` method on the ``Hardware`` objects results in a ``HwMock`` (a
+subclass of ``unittest.mock``'s ``MagicMock``) object which, in the case of
+``SMBusModel``, provides access to a log of all register writes and their
+values.
+
+The object can be then used to check which registers the hardware has written
+with which values, and to assert that the expect actions have been taken.
+
+See ``roadtest/tests/regulator/test_tps62864.py`` for an example of this.
+
+GPIOs
+-----
+
+The framework includes support for hardware models to trigger interrupts by
+controlling GPIOs.  See ``roadtest/tests/rtc/test_pcf8563.py`` for an example.
+
+Support has not been implemented yet for asserting that drivers control GPIOs
+correctly.  See the comment in ``gpio_handle_cmdq()`` in ``src/backend.c``.
+
+Coding guidelines
+-----------------
+
+Run ``make fmt`` to automatically format your Python code to follow the coding
+style.  Run ``make check`` and ensure that your code passes static checkers and
+style checks.  Typing hints are mandatory.
+
+These two commands require that you have installed the packages listed in
+``requirements.txt``, for example with something like the following patch and
+then ensuring that ``~/.local/bin`` is in your ``$PATH``.
+
+.. code-block:: shell
+
+  $ pip3 install --user -r requirements.txt
+
+Alternatively, you can also run these commands in the Docker container (by
+appending ``DOCKER=1`` to the ``make`` commands) which has all the correct
+tools installed.
-- 
2.34.1

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ