tests: Add basic unit test based on virtual_image device
This commit is contained in:
parent
0b4f682233
commit
6e25a27870
3 changed files with 302 additions and 0 deletions
|
@ -140,6 +140,11 @@ if get_option('gtk-examples')
|
|||
subdir('demo')
|
||||
endif
|
||||
|
||||
# The tests require introspeciton support to run
|
||||
if get_option('introspection')
|
||||
subdir('tests')
|
||||
endif
|
||||
|
||||
pkgconfig = import('pkgconfig')
|
||||
pkgconfig.generate(
|
||||
name: 'libfprint',
|
||||
|
|
17
tests/meson.build
Normal file
17
tests/meson.build
Normal file
|
@ -0,0 +1,17 @@
|
|||
envs = environment()
|
||||
envs.set('G_DEBUG', 'fatal-warnings')
|
||||
envs.set('G_MESSAGES_DEBUG', 'all')
|
||||
envs.set('MESON_SOURCE_ROOT', meson.build_root())
|
||||
envs.prepend('GI_TYPELIB_PATH', join_paths(meson.build_root(), 'libfprint'))
|
||||
envs.prepend('LD_LIBRARY_PATH', join_paths(meson.build_root(), 'libfprint'))
|
||||
envs.set('NO_AT_BRIDGE', '1')
|
||||
|
||||
if 'virtual_image' in drivers
|
||||
test(
|
||||
'virtual-image',
|
||||
find_program('virtual-image.py'),
|
||||
args: '--verbose',
|
||||
env: envs,
|
||||
)
|
||||
endif
|
||||
|
280
tests/virtual-image.py
Executable file
280
tests/virtual-image.py
Executable file
|
@ -0,0 +1,280 @@
|
|||
#!/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 shutil
|
||||
import glob
|
||||
import cairo
|
||||
import tempfile
|
||||
|
||||
class Connection:
|
||||
|
||||
def __init__(self, addr):
|
||||
self.addr = addr
|
||||
|
||||
def __enter__(self):
|
||||
self.con = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
self.con.connect(self.addr)
|
||||
return self.con
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.con.close()
|
||||
del self.con
|
||||
|
||||
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)
|
||||
|
||||
def setUp(self):
|
||||
self.dev.open_sync()
|
||||
|
||||
def tearDown(self):
|
||||
self.dev.close_sync()
|
||||
|
||||
def report_finger(self, state):
|
||||
with Connection(self.sockaddr) as con:
|
||||
con.write(struct.pack('ii', -1, 1 if state else 0))
|
||||
|
||||
def send_image(self, image):
|
||||
img = self.prints[image]
|
||||
with Connection(self.sockaddr) as con:
|
||||
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
|
||||
|
||||
con.sendall(encoded_img)
|
||||
|
||||
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)
|
||||
|
||||
self.send_image(image)
|
||||
while self._step < 2:
|
||||
ctx.iteration(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, None, 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, None, 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], None, 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], None, 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, None, 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, None, 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))
|
||||
|
Loading…
Reference in a new issue