libfprint/tests/virtual-image.py
Benjamin Berg 4d5c34e11a Introduce an early reporting mechanism for verify and match
It is a good idea to report match results early, to e.g. log in a user
immediately even if more device interaction is needed. Add new _full
variants for the verify/identify functions, with a corresponding
callback. Also move driver result reporting into new
fpi_device_{identify,verify}_report functions and remove the reporting
from the fpi_device_{identify,verify}_complete calls.

Basic updates to code is done in places. Only the upekts driver is
actually modified from a behaviour point of view. The image driver code
should be restructured quite a bit to split the reporting and only
report completion after device deactivation. This should simplifiy the
code quite a bit again.
2020-01-13 14:37:39 +01:00

309 lines
9.4 KiB
Python
Executable file

#!/usr/bin/env python3
import gi
gi.require_version('FPrint', '2.0')
from gi.repository import FPrint, GLib, Gio
import os
import sys
import unittest
import socket
import struct
import subprocess
import shutil
import glob
import cairo
import tempfile
# Re-run the test with the passed wrapper if set
wrapper = os.getenv('LIBFPRINT_TEST_WRAPPER')
if wrapper:
wrap_cmd = wrapper.split(' ') + [sys.executable, os.path.abspath(__file__)] + \
sys.argv[1:]
os.unsetenv('LIBFPRINT_TEST_WRAPPER')
sys.exit(subprocess.check_call(wrap_cmd))
def load_image(img):
png = cairo.ImageSurface.create_from_png(img)
# Cairo wants 4 byte aligned rows, so just add a few pixel if necessary
w = png.get_width()
h = png.get_height()
w = (w + 3) // 4 * 4
h = (h + 3) // 4 * 4
img = cairo.ImageSurface(cairo.Format.A8, w, h)
cr = cairo.Context(img)
cr.set_source_rgba(1, 1, 1, 1)
cr.paint()
cr.set_source_rgba(0, 0, 0, 0)
cr.set_operator(cairo.OPERATOR_SOURCE)
cr.set_source_surface(png)
cr.paint()
return img
if hasattr(os.environ, 'MESON_SOURCE_ROOT'):
root = os.environ['MESON_SOURCE_ROOT']
else:
root = os.path.join(os.path.dirname(__file__), '..')
imgdir = os.path.join(root, 'examples', 'prints')
ctx = GLib.main_context_default()
class VirtualImage(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.tmpdir = tempfile.mkdtemp(prefix='libfprint-')
cls.sockaddr = os.path.join(cls.tmpdir, 'virtual-image.socket')
os.environ['FP_VIRTUAL_IMAGE'] = cls.sockaddr
cls.ctx = FPrint.Context()
cls.dev = None
for dev in cls.ctx.get_devices():
# We might have a USB device in the test system that needs skipping
if dev.get_driver() == 'virtual_image':
cls.dev = dev
break
assert cls.dev is not None, "You need to compile with virtual_image for testing"
cls.prints = {}
for f in glob.glob(os.path.join(imgdir, '*.png')):
n = os.path.basename(f)[:-4]
cls.prints[n] = load_image(f)
@classmethod
def tearDownClass(cls):
shutil.rmtree(cls.tmpdir)
del cls.dev
del cls.ctx
def setUp(self):
self.dev.open_sync()
self.con = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.con.connect(self.sockaddr)
def tearDown(self):
self.con.close()
del self.con
self.dev.close_sync()
def send_retry(self, retry_error=1, iterate=True):
# The default (1) is too-short
self.sendall(struct.pack('ii', -1, retry_error))
while iterate and ctx.pending():
ctx.iteration(False)
def send_error(self, device_error=0, iterate=True):
# The default (0) is a generic error
self.sendall(struct.pack('ii', -1, retry_error))
while iterate and ctx.pending():
ctx.iteration(False)
def send_finger_automatic(self, automatic, iterate=True):
# Set whether finger on/off is reported around images
self.con.sendall(struct.pack('ii', -3, 1 if automatic else 0))
while iterate and ctx.pending():
ctx.iteration(False)
def send_finger_report(self, has_finger, iterate=True):
# Send finger on/off
self.con.sendall(struct.pack('ii', -4, 1 if has_finger else 0))
while iterate and ctx.pending():
ctx.iteration(False)
def send_image(self, image, iterate=True):
img = self.prints[image]
mem = img.get_data()
mem = mem.tobytes()
assert len(mem) == img.get_width() * img.get_height()
encoded_img = struct.pack('ii', img.get_width(), img.get_height())
encoded_img += mem
self.con.sendall(encoded_img)
while iterate and ctx.pending():
ctx.iteration(False)
def test_capture_prevents_close(self):
cancel = Gio.Cancellable()
def cancelled_cb(dev, res, obj):
print("Capture operation finished")
with self.assertRaises(GLib.GError) as cm:
dev.capture_finish(res)
assert cm.exception.matches(Gio.io_error_quark(), Gio.IOErrorEnum.CANCELLED)
print("Capture cancelled as expected")
obj._cancelled = True
self._cancelled = False
self.dev.capture(True, cancel, cancelled_cb, self)
with self.assertRaises(GLib.GError) as cm:
self.dev.close_sync()
assert cm.exception.matches(FPrint.device_error_quark(), FPrint.DeviceError.BUSY)
cancel.cancel()
while not self._cancelled:
ctx.iteration(True)
def enroll_print(self, image):
self._step = 0
self._enrolled = None
def progress_cb(dev, step, fp, user_data):
print('Print was processed, continuing')
self._step = step
def done_cb(dev, res):
print("Enroll done")
fp = dev.enroll_finish(res)
self._enrolled = fp
template = FPrint.Print.new(self.dev)
template.props.finger = FPrint.Finger.LEFT_THUMB
template.props.username = "testuser"
template.props.description = "test print"
datetime = GLib.DateTime.new_now_local()
date = GLib.Date()
date.set_dmy(*datetime.get_ymd()[::-1])
template.props.enroll_date = date
self.dev.enroll(template, None, progress_cb, tuple(), done_cb)
# Note: Assumes 5 enroll steps for this device!
self.send_image(image)
while self._step < 1:
ctx.iteration(True)
# Test the image-device path where the finger is removed after
# the minutiae scan is completed.
self.send_finger_automatic(False)
self.send_finger_report(True)
self.send_image(image)
while self._step < 2:
ctx.iteration(True)
self.send_finger_report(False)
self.send_finger_automatic(True)
self.send_image(image)
while self._step < 3:
ctx.iteration(True)
self.send_image(image)
while self._step < 4:
ctx.iteration(True)
self.send_image(image)
while self._enrolled is None:
ctx.iteration(True)
return self._enrolled
def test_enroll_verify(self):
done = False
def verify_cb(dev, res):
match, fp = dev.verify_finish(res)
self._verify_match = match
self._verify_fp = fp
fp_whorl = self.enroll_print('whorl')
self._verify_match = None
self._verify_fp = None
self.dev.verify(fp_whorl, callback=verify_cb)
self.send_image('whorl')
while self._verify_match is None:
ctx.iteration(True)
assert(self._verify_match)
self._verify_match = None
self._verify_fp = None
self.dev.verify(fp_whorl, callback=verify_cb)
self.send_image('tented_arch')
while self._verify_match is None:
ctx.iteration(True)
assert(not self._verify_match)
def test_identify(self):
done = False
def verify_cb(dev, res):
r, fp = dev.verify_finish(res)
self._verify_match = r
self._verify_fp = fp
fp_whorl = self.enroll_print('whorl')
fp_tented_arch = self.enroll_print('tented_arch')
def identify_cb(dev, res):
print('Identify finished')
self._identify_match, self._identify_fp = self.dev.identify_finish(res)
self._identify_fp = None
self.dev.identify([fp_whorl, fp_tented_arch], callback=identify_cb)
self.send_image('tented_arch')
while self._identify_fp is None:
ctx.iteration(True)
assert(self._identify_match is fp_tented_arch)
self._identify_fp = None
self.dev.identify([fp_whorl, fp_tented_arch], callback=identify_cb)
self.send_image('whorl')
while self._identify_fp is None:
ctx.iteration(True)
assert(self._identify_match is fp_whorl)
def test_verify_serialized(self):
done = False
def verify_cb(dev, res):
r, fp = dev.verify_finish(res)
self._verify_match = r
self._verify_fp = fp
fp_whorl = self.enroll_print('whorl')
fp_data = fp_whorl.serialize()
fp_whorl_new = FPrint.Print.deserialize(fp_data)
# The serialized/deserialized prints need to be equal
assert fp_whorl.equal(fp_whorl_new)
datetime = GLib.DateTime.new_now_local()
date = GLib.Date()
date.set_dmy(*datetime.get_ymd()[::-1])
assert fp_whorl_new.props.username == "testuser"
assert fp_whorl_new.props.description == "test print"
assert fp_whorl_new.props.finger == FPrint.Finger.LEFT_THUMB
assert date.compare(fp_whorl_new.props.enroll_date) == 0
self._verify_match = None
self._verify_fp = None
self.dev.verify(fp_whorl_new, callback=verify_cb)
self.send_image('whorl')
while self._verify_match is None:
ctx.iteration(True)
assert(self._verify_match)
self._verify_match = None
self._verify_fp = None
self.dev.verify(fp_whorl_new, callback=verify_cb)
self.send_image('tented_arch')
while self._verify_match is None:
ctx.iteration(True)
assert(not self._verify_match)
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))