summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBastien Nocera <hadess@hadess.net>2021-09-03 18:20:03 +0200
committerBenjamin Berg <benjamin@sipsolutions.net>2021-09-15 13:24:08 +0000
commitaff063c23c47b98312b4af41a6a4335b0f598dc6 (patch)
tree54bc254c947e8ba7939b444cbec5e63df9552c8e
parente2511095d1e13b12812690f24809952eca9cbdc4 (diff)
tests: Simplify capture of driver behaviour for regression tests
And update instructions for the simpler method. Co-authored-by: Benjamin Berg <bberg@redhat.com>
-rw-r--r--tests/README.md60
-rwxr-xr-xtests/create-driver-test.py.in162
-rw-r--r--tests/meson.build9
3 files changed, 184 insertions, 47 deletions
diff --git a/tests/README.md b/tests/README.md
index 20fdf9f..d04915a 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -15,57 +15,23 @@ script, capture it and store the capture to `custom.pcapng`.
-----------------------
A new 'capture' test is created by means of `capture.py` script:
-1. Create (if needed) a directory for the driver under `tests`
- directory:
+1. Make sure that libfprint is built with support for the device driver
+ that you want to capture a test case for.
- `mkdir DRIVER`
+2. From the build directory, run tests/create-driver-test.py as root. Note
+ that if you're capturing data for a driver which already has a test case
+ but the hardware is slightly different, you might want to pass a variant
+ name as a command-line options, for example:
+```sh
+$ sudo tests/create-driver-test.py driver [variant]
+```
- Note that the name must be the exact name of the libfprint driver,
- or the exact name of the driver followed by a `-` and a unique identifier
- of your choosing.
+3. If the capture is not successful, run the tool again to start another capture.
-2. Prepare your execution environment.
+4. Add driver test name to `drivers_tests` in the `meson.build`, as instructed,
+ and change the ownership of the just-created test directory in the source.
- In the next step a working and up to date libfprint is needed. This can be
- achieved by installing it into your system. Alternatively, you can set
- the following environment variables to run a local build:
- - `export LD_PRELOAD=<meson-build-dir>/libfprint/libfprint-2.so`
- - `export GI_TYPELIB_PATH=<meson-build-dir>/libfprint`
-
- Also, sometimes the driver must be adapted to the emulated environment
- (mainly if it uses random numbers, see `synaptics.c` for an example).
- Set the following environment variable to enable this adaptation:
- - `export FP_DEVICE_EMULATION=1`
-
- Run the next steps in the same terminal.
-
-3. Find the real USB fingerprint device with `lsusb`, e.g.:
-
- `Bus 001 Device 005: ID 138a:0090 Validity Sensors, Inc. VFS7500 Touch Fingerprint Sensor`
-
- The following USB device is used in the example above:
- `/dev/bus/usb/001/005`.
-
- For the following commands, it is assumed that the user that's
- running the commands has full access to the device node, whether
- by running the commands as `root`, or changing the permissions for
- that device node.
-
-4. Record information about this device:
-
- `umockdev-record /dev/bus/usb/001/005 > DRIVER/device`
-
-5. Record interaction of `capture.py` (or other test) with the device. To do
- so, start wireshark and record `usbmonX` (where X is the bus number). Then
- run the test script:
-
- `python3 ./capture.py DRIVER/capture.png`
-
- Save the wireshark recording as `capture.pcapng`. The command will create
- `capture.png`.
-
-6. Add driver's name to `drivers_tests` in the `meson.build`.
-7. Check whether everything works as expected.
+5. Check whether `meson test` passes with this new test.
**Note.** To avoid submitting a real fingerprint, the side of finger,
arm, or anything else producing an image with the device can be used.
diff --git a/tests/create-driver-test.py.in b/tests/create-driver-test.py.in
new file mode 100755
index 0000000..70b48f5
--- /dev/null
+++ b/tests/create-driver-test.py.in
@@ -0,0 +1,162 @@
+#!/usr/bin/python3
+
+BUILDDIR='@BUILDDIR@'
+SRCDIR='@SRCDIR@'
+
+import os
+import sys
+import signal
+library_path = BUILDDIR + '/libfprint/'
+
+# Relaunch ourselves with a changed environment so
+# that we're loading the development version of libfprint
+if 'LD_LIBRARY_PATH' not in os.environ or not library_path in os.environ['LD_LIBRARY_PATH']:
+ os.environ['LD_LIBRARY_PATH'] = library_path
+ os.environ['GI_TYPELIB_PATH'] = f'{BUILDDIR}/libfprint/'
+ os.environ['FP_DEVICE_EMULATION'] = '1'
+ try:
+ os.execv(sys.argv[0], sys.argv)
+ except Exception as e:
+ print('Could not run script with new library path')
+ sys.exit(1)
+
+import gi
+gi.require_version('FPrint', '2.0')
+from gi.repository import FPrint
+
+gi.require_version('GUsb', '1.0')
+from gi.repository import GUsb
+
+import re
+import shutil
+import subprocess
+import tempfile
+import time
+
+def print_usage():
+ print(f'Usage: {sys.argv[0]} driver [test-variant-name]')
+ print('A test variant name is optional, and must be all lower case letters, or dashes, with no spaces')
+ print(f'The captured data will be stored in {SRCDIR}/tests/[driver name]-[test variant name]')
+
+if len(sys.argv) > 3:
+ print_usage()
+ sys.exit(1)
+
+driver_name = sys.argv[1]
+os.environ['FP_DRIVERS_WHITELIST'] = driver_name
+
+test_variant = None
+if len(sys.argv) == 3:
+ valid_re = re.compile('[a-z-]*')
+ test_variant = sys.argv[2]
+ if (not valid_re.match(test_variant) or
+ test_variant.startswith('-') or
+ test_variant.endswith('-')):
+ print(f'Invalid variant name {test_variant}\n')
+ print_usage()
+ sys.exit(1)
+
+# Check that running as root
+
+if os.geteuid() != 0:
+ print(f'{sys.argv[0]} is expected to be run as root')
+ sys.exit(1)
+
+# Check that tshark is available
+
+tshark = shutil.which('tshark')
+if not tshark:
+ print("The 'tshark' WireShark command-line tool must be installed to capture USB traffic")
+ sys.exit(1)
+
+# Find the fingerprint reader
+ctx = FPrint.Context()
+ctx.enumerate()
+devices = ctx.get_devices()
+if len(devices) == 0:
+ print('Could not find a supported fingerprint reader')
+ sys.exit(1)
+elif len(devices) > 1:
+ print('Capture requires a single supported fingerprint reader to be plugged in')
+ sys.exit(1)
+
+test_name = driver_name
+if test_variant:
+ test_name = driver_name + '-' + test_variant
+usb_device = devices[0].get_property('fpi-usb-device')
+bus_num = usb_device.get_bus()
+device_num = usb_device.get_address()
+
+print(f'### Detected USB device /dev/bus/usb/{bus_num:03d}/{device_num:03d}')
+
+# Make directory
+
+test_dir = SRCDIR + '/tests/' + test_name
+os.makedirs(test_dir, mode=0o775, exist_ok=True)
+
+# Capture device info
+
+args = ['umockdev-record', f'/dev/bus/usb/{bus_num:03d}/{device_num:03d}']
+device_out = open(test_dir + '/device', 'w')
+process = subprocess.Popen(args, stdout=device_out)
+process.wait()
+
+# Run capture
+# https://osqa-ask.wireshark.org/questions/53919/how-can-i-precisely-specify-a-usb-device-to-capture-with-tshark/
+
+print(f'### Starting USB capture on usbmon{bus_num}')
+capture_pid = os.fork()
+assert(capture_pid >= 0)
+
+unfiltered_cap_path = os.path.join(tempfile.gettempdir(), 'capture-unfiltered.pcapng')
+if capture_pid == 0:
+ os.setpgrp()
+ args = ['tshark', '-q', '-i', f'usbmon{bus_num}', '-w', unfiltered_cap_path]
+ os.execv(tshark, args)
+
+# Wait 1 sec to settle (we can assume setpgrp happened)
+time.sleep(1)
+
+print('### Capturing fingerprint, please swipe or press your finger on the reader')
+with subprocess.Popen(['python3', SRCDIR + '/tests/capture.py', test_dir + '/capture.png']) as capture_process:
+ capture_process.wait()
+ if capture_process.returncode != 0:
+ print('Failed to capture fingerprint')
+ os.killpg(capture_pid, signal.SIGKILL)
+ sys.exit(1)
+
+def t_waitpid(pid, timeout):
+ timeout = time.time() + timeout
+ r = os.waitpid(pid, os.WNOHANG)
+ while timeout > time.time() and r[0] == 0:
+ time.sleep(0.1)
+ r = os.waitpid(pid, os.WNOHANG)
+
+ return r
+
+os.kill(capture_pid, signal.SIGTERM)
+try:
+ r = t_waitpid(capture_pid, 2)
+ # Kill if nothing died
+ if r[0] == 0:
+ os.kill(capture_pid, signal.SIGKILL)
+except ChildProcessError:
+ pass
+
+try:
+ while True:
+ r = t_waitpid(-capture_pid, timeout=2)
+ # Kill the process group, if nothing died (and there are children)
+ if r[0] == 0:
+ os.killpg(capture_pid, signal.SIGKILL)
+except ChildProcessError:
+ pass
+
+# Filter the capture
+print(f'\n### Saving USB capture as test case {test_name}')
+args = ['tshark', '-r', unfiltered_cap_path, '-Y', f'usb.bus_id == {bus_num} and usb.device_address == {device_num}',
+ '-w', test_dir + '/capture.pcapng']
+with subprocess.Popen(args, stderr=subprocess.DEVNULL) as filter_process:
+ filter_process.wait()
+
+print(f"\nDone! Don't forget to add {test_name} to tests/meson.build")
diff --git a/tests/meson.build b/tests/meson.build
index 7a64a55..cbd06eb 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -42,6 +42,15 @@ drivers_tests = [
]
if get_option('introspection')
+ conf = configuration_data()
+ conf.set('SRCDIR', meson.project_source_root())
+ conf.set('BUILDDIR', meson.project_build_root())
+ configure_file(configuration: conf,
+ input: 'create-driver-test.py.in',
+ output: 'create-driver-test.py')
+endif
+
+if get_option('introspection')
envs.prepend('GI_TYPELIB_PATH', join_paths(meson.build_root(), 'libfprint'))
virtual_devices_tests = [
'virtual-image',