tests: Simplify capture of driver behaviour for regression tests
And update instructions for the simpler method. Co-authored-by: Benjamin Berg <bberg@redhat.com>
This commit is contained in:
parent
e2511095d1
commit
aff063c23c
3 changed files with 184 additions and 47 deletions
|
@ -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.
|
||||
|
|
162
tests/create-driver-test.py.in
Executable file
162
tests/create-driver-test.py.in
Executable file
|
@ -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")
|
|
@ -41,6 +41,15 @@ drivers_tests = [
|
|||
'egis0570',
|
||||
]
|
||||
|
||||
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 = [
|
||||
|
|
Loading…
Reference in a new issue