diff --git a/meson_options.txt b/meson_options.txt index f9b801f..73c13d7 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -30,3 +30,8 @@ option('doc', description: 'Whether to build the API documentation', type: 'boolean', value: true) + +option('sdcp_virt_binary', + description: 'Path to virtual SDCP test binary, please refer to CI for more details.', + type: 'string', + value: '') diff --git a/tests/meson.build b/tests/meson.build index f208920..c8e7b37 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -12,6 +12,9 @@ envs.prepend('LD_LIBRARY_PATH', join_paths(meson.build_root(), 'libfprint')) # random numbers rather than proper ones) envs.set('FP_DEVICE_EMULATION', '1') +# Path to SDCP virtual device binary, only used for virtual-sdcp test +envs.set('SDCP_VIRT_BINARY', get_option('sdcp_virt_binary')) + # Set a colon-separated list of native drivers we enable in tests envs.set('FP_DRIVERS_WHITELIST', ':'.join([ 'virtual_image', @@ -82,6 +85,43 @@ if get_option('introspection') endif endforeach + if 'virtual_sdcp' in drivers and get_option('sdcp_virt_binary') != '' + python3 = find_program('python3') + unittest_inspector = find_program('unittest_inspector.py') + base_args = files('virtual-sdcp.py') + suite = [] + + r = run_command(unittest_inspector, files('virtual-sdcp.py')) + unit_tests = r.stdout().strip().split('\n') + + if r.returncode() == 0 and unit_tests.length() > 0 + suite += 'virtual-sdcp' + else + unit_tests = ['virtual-sdcp'] + endif + + foreach ut: unit_tests + ut_suite = suite + ut_args = base_args + if unit_tests.length() > 1 + ut_args += ut + ut_suite += ut.split('.')[0] + endif + test(ut, + python3, + args: ut_args, + suite: ut_suite, + depends: libfprint_typelib, + env: envs, + ) + endforeach + else + test('virtual-sdcp', + find_program('sh'), + args: ['-c', 'exit 77'] + ) + endif + foreach driver_test: drivers_tests if driver_test.contains('-') driver_name = driver_test.split('-')[0] diff --git a/tests/virtual-sdcp.py b/tests/virtual-sdcp.py new file mode 100644 index 0000000..6efb24d --- /dev/null +++ b/tests/virtual-sdcp.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 + +import sys +try: + import gi + import os + + from gi.repository import GLib, Gio + + import unittest + import subprocess + import shutil + import tempfile +except Exception as e: + print("Missing dependencies: %s" % str(e)) + sys.exit(77) + +FPrint = None + +# 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)) + +# Only permit loading virtual_sdcp driver for tests in this file +os.environ['FP_DRIVERS_WHITELIST'] = 'virtual_sdcp' + +if hasattr(os.environ, 'MESON_SOURCE_ROOT'): + root = os.environ['MESON_SOURCE_ROOT'] +else: + root = os.path.join(os.path.dirname(__file__), '..') + +ctx = GLib.main_context_default() + +class VirtualSDCP(unittest.TestCase): + + @classmethod + def setUpClass(cls): + os.environ['FP_VIRTUAL_SDCP'] = os.environ['SDCP_VIRT_BINARY'] + + cls.ctx = FPrint.Context() + + cls.dev = None + for dev in cls.ctx.get_devices(): + cls.dev = dev + break + + assert cls.dev is not None, "You need to compile with virtual_sdcp for testing" + + @classmethod + def tearDownClass(cls): + del cls.dev + del cls.ctx + + def setUp(self): + pass + + def tearDown(self): + pass + + def enroll(self, progress_cb=None): + # Enroll another print + 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 + return self.dev.enroll_sync(template, None, progress_cb, None) + + def test_connect(self): + self.dev.open_sync() + self.dev.close_sync() + + def test_reconnect(self): + # Ensure device was opened once before, this may be a reconnect if + # it is the same process as another test. + self.dev.open_sync() + self.dev.close_sync() + + # Check that a reconnect happens on next open. To know about this, we + # need to parse check log messages for that. + success = [False] + def log_func(domain, level, msg): + print("log: '%s', '%s', '%s'" % (str(domain), str(level), msg)) + if msg == 'Reconnect succeeded': + success[0] = True + + # Call default handler + GLib.log_default_handler(domain, level, msg) + + handler_id = GLib.log_set_handler('libfprint-sdcp_device', GLib.LogLevelFlags.LEVEL_DEBUG, log_func) + self.dev.open_sync() + self.dev.close_sync() + GLib.log_remove_handler('libfprint-sdcp_device', handler_id) + assert success[0] + + def test_enroll(self): + self.dev.open_sync() + + # Must return a print + assert isinstance(self.enroll(), FPrint.Print) + + self.dev.close_sync() + + + def test_verify(self): + self.dev.open_sync() + + # Enroll a new print (will be the last), check that it verifies + p = self.enroll() + match, dev_print = self.dev.verify_sync(p) + assert match + + # The returned "device" print is identical + assert p.equal(dev_print) + + # We can do the same with it + match, dev_print2 = self.dev.verify_sync(dev_print) + assert match + + # Now, enroll a new print, causing the old one to not match anymore + # (the device always claims to see the last enrolled print). + self.enroll() + match, dev_print = self.dev.verify_sync(p) + assert match is False + + self.dev.close_sync() + +if __name__ == '__main__': + try: + gi.require_version('FPrint', '2.0') + from gi.repository import FPrint + except Exception as e: + print("Missing dependencies: %s" % str(e)) + sys.exit(77) + + # avoid writing to stderr + unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))