diff --git a/.gitignore b/.gitignore index 20dcfdfc..07d73995 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,3 @@ -ltmain.sh -missing -stamp-h1 -libtool -*.la -*.lo *.o *.swp -Makefile -Makefile.in -config.h* -aclocal.m4 -autom4te.cache -config.guess -config.log -config.status -config.sub -configure -depcomp -install-sh -.deps -.libs -compile -ChangeLog +_build diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 270aec2c..d7a4a246 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,15 +1,17 @@ +include: + - local: '.gitlab-ci/libfprint-templates.yaml' + - project: 'wayland/ci-templates' + ref: master + file: '/templates/fedora.yml' + variables: + extends: .libfprint_common_variables FEDORA_TAG: rawhide FEDORA_VERSION: rawhide FEDORA_IMAGE: "$CI_REGISTRY/libfprint/$CI_PROJECT_NAME/fedora/$FEDORA_VERSION:$FEDORA_TAG" BUNDLE: "org.freedesktop.libfprint.Demo.flatpak" LAST_ABI_BREAK: "056ea541ddc97f5806cffbd99a12dc87e4da3546" -include: - - project: 'wayland/ci-templates' - ref: master - file: '/templates/fedora.yml' - stages: - check-source - build @@ -106,9 +108,6 @@ test_indent: .flatpak_master_template: &flatpak_master image: registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:3.32 stage: flatpack - except: - variables: - - $CI_PIPELINE_SOURCE == "schedule" variables: MANIFEST_PATH: "demo/org.freedesktop.libfprint.Demo.json" # From demo/org.freedesktop.libfprint.Demo.json @@ -128,8 +127,11 @@ flatpak-manual master: <<: *flatpak_master when: manual except: - - tags - - master + refs: + - tags + - master + variables: + - $CI_PIPELINE_SOURCE == "schedule" # CONTAINERS creation stage container_fedora_build: @@ -140,28 +142,4 @@ container_fedora_build: variables: GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image # a list of packages to install - FEDORA_RPMS: - doxygen - flatpak-builder - gcc - gcc-c++ - gcovr - git - glib2-devel - glibc-devel - gobject-introspection-devel - gtk-doc - gtk3-devel - libabigail - libgusb-devel - libX11-devel - libXv-devel - meson - nss-devel - pixman-devel - python3-cairo - python3-gobject - systemd - umockdev - uncrustify - valgrind + FEDORA_RPMS: $LIBFPRINT_DEPENDENCIES diff --git a/.gitlab-ci/libfprint-templates.yaml b/.gitlab-ci/libfprint-templates.yaml new file mode 100644 index 00000000..e8a510f8 --- /dev/null +++ b/.gitlab-ci/libfprint-templates.yaml @@ -0,0 +1,26 @@ +.libfprint_common_variables: + LIBFPRINT_DEPENDENCIES: + doxygen + flatpak-builder + gcc + gcc-c++ + gcovr + git + glib2-devel + glibc-devel + gobject-introspection-devel + gtk-doc + gtk3-devel + libabigail + libgusb-devel + libX11-devel + libXv-devel + meson + nss-devel + pixman-devel + python3-cairo + python3-gobject + systemd + umockdev + uncrustify + valgrind diff --git a/NEWS b/NEWS index f3fa3547..e7a2b668 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,41 @@ This file lists notable changes in each release. For the full history of all changes, see ChangeLog. +2019-11-20: v1.90.1 release + +This release fixes a lot of the regressions introduced in 1.90.0. Please note +that both the driver and external APIs have changed, as both the verify and +the identify functions now have early reporting mechanisms. +The soname for the library, as well as a number of file locations have also +changed. While this allows installation in parallel with the 1.0 version of +libfprint, we recommend installing only one, and migrating from version 1.0 to +version 2.0 alongside its main consumer (fprintd). + +Only major changes are listed below. A lot of other cleanup work and small +fixes have also been merged. + +* Library: + - Add support to run tests in gdb/valgrind + - Allow testing on all architectures + - Avoid image device AWAIT_FINGER_ON to deactivate state transitions + - Fix verify/identify error propagation to library user + - Correctly read image device information from class data + - Continue enroll after an image driver reported a retry error + - Change external API to allow reporting match results early + - A lot of new unit tests and integration tests have been added + +* Drivers API + - Support variadic arguments in error functions + - Various re-definitions of ownership handling + - Add convenience API to change state after a timeout + - Add unit tests for all the drivers API + +* Drivers: + - elan: Ensure correct deactivation of device + - uru4000: Fix IRQ handler registration and internal state handling + - uru4000: Fix control transfer request type + - synaptics: Ensure errors are only reported after finger removal + 2019-11-20: v1.90.0 release This release updates the core of the library to use GLib routines and Gio diff --git a/doc/libfprint-sections.txt b/doc/libfprint-sections.txt index 30a4e9bf..ca92190c 100644 --- a/doc/libfprint-sections.txt +++ b/doc/libfprint-sections.txt @@ -26,6 +26,7 @@ FpDeviceError fp_device_retry_quark fp_device_error_quark FpEnrollProgress +FpMatchCb fp_device_get_driver fp_device_get_device_id fp_device_get_name @@ -159,6 +160,8 @@ fpi_device_identify_complete fpi_device_capture_complete fpi_device_delete_complete fpi_device_enroll_progress +fpi_device_verify_report +fpi_device_identify_report
diff --git a/doc/meson.build b/doc/meson.build index e138ea29..1da63fbf 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -23,16 +23,13 @@ glib_prefix = dependency('glib-2.0').get_pkgconfig_variable('prefix') glib_docpath = join_paths(glib_prefix, 'share', 'gtk-doc', 'html') docpath = join_paths(get_option('datadir'), 'gtk-doc', 'html') -gnome.gtkdoc(meson.project_name(), +gnome.gtkdoc(versioned_libname, main_xml: 'libfprint-docs.xml', src_dir: join_paths(meson.source_root(), 'libfprint'), dependencies: libfprint_dep, content_files: content_files, expand_content_files: expand_content_files, - scan_args: [ - #'--rebuild-sections', - '--ignore-headers=' + ' '.join(private_headers), - ], + ignore_headers: private_headers, fixxref_args: [ '--html-dir=@0@'.format(docpath), '--extra-dir=@0@'.format(join_paths(glib_docpath, 'glib')), diff --git a/doc/xml/meson.build b/doc/xml/meson.build index 5e56bb40..44c6e4aa 100644 --- a/doc/xml/meson.build +++ b/doc/xml/meson.build @@ -1,8 +1,8 @@ ent_conf = configuration_data() -ent_conf.set('PACKAGE', meson.project_name()) +ent_conf.set('PACKAGE', versioned_libname) ent_conf.set('PACKAGE_BUGREPORT', 'https://gitlab.freedesktop.org/libfprint/libfprint/issues') -ent_conf.set('PACKAGE_NAME', meson.project_name()) -ent_conf.set('PACKAGE_STRING', meson.project_name()) +ent_conf.set('PACKAGE_NAME', versioned_libname) +ent_conf.set('PACKAGE_STRING', versioned_libname) ent_conf.set('PACKAGE_TARNAME', 'libfprint-' + meson.project_version()) ent_conf.set('PACKAGE_URL', 'https://fprint.freedesktop.org/') ent_conf.set('PACKAGE_VERSION', meson.project_version()) diff --git a/examples/enroll.c b/examples/enroll.c index 159ffbc0..c6572860 100644 --- a/examples/enroll.c +++ b/examples/enroll.c @@ -19,6 +19,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#define FP_COMPONENT "example-enroll" + #include #include diff --git a/examples/manage-prints.c b/examples/manage-prints.c index d64b5fa6..36e4046d 100644 --- a/examples/manage-prints.c +++ b/examples/manage-prints.c @@ -18,6 +18,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#define FP_COMPONENT "example-mange-prints" + #include #include diff --git a/examples/storage.c b/examples/storage.c index 0ab49468..ebffb33f 100644 --- a/examples/storage.c +++ b/examples/storage.c @@ -19,7 +19,10 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#define FP_COMPONENT "example-storage" + #include +#include #include "storage.h" #include @@ -161,8 +164,8 @@ FpPrint * print_create_template (FpDevice *dev, FpFinger finger) { g_autoptr(GDateTime) datetime = NULL; + g_autoptr(GDate) date = NULL; FpPrint *template = NULL; - GDate *date = NULL; gint year, month, day; template = fp_print_new (dev); @@ -172,7 +175,6 @@ print_create_template (FpDevice *dev, FpFinger finger) g_date_time_get_ymd (datetime, &year, &month, &day); date = g_date_new_dmy (day, month, year); fp_print_set_enroll_date (template, date); - g_date_free (date); return template; } diff --git a/examples/utilities.c b/examples/utilities.c index eb18600d..d68d8c18 100644 --- a/examples/utilities.c +++ b/examples/utilities.c @@ -18,6 +18,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#define FP_COMPONENT "example-utilities" + #include #include diff --git a/examples/verify.c b/examples/verify.c index 7fcc64cf..acfc9eff 100644 --- a/examples/verify.c +++ b/examples/verify.c @@ -19,6 +19,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#define FP_COMPONENT "example-verify" + #include #include @@ -55,6 +57,19 @@ on_device_closed (FpDevice *dev, GAsyncResult *res, void *user_data) g_main_loop_quit (verify_data->loop); } +static void +verify_quit (FpDevice *dev, + VerifyData *verify_data) +{ + if (!fp_device_is_open (dev)) + { + g_main_loop_quit (verify_data->loop); + return; + } + + fp_device_close (dev, NULL, (GAsyncReadyCallback) on_device_closed, verify_data); +} + static void start_verification (FpDevice *dev, VerifyData *verify_data); @@ -71,23 +86,13 @@ on_verify_completed (FpDevice *dev, GAsyncResult *res, void *user_data) if (!fp_device_verify_finish (dev, res, &match, &print, &error)) { g_warning ("Failed to verify print: %s", error->message); - g_main_loop_quit (verify_data->loop); - return; - } - - if (print && fp_device_supports_capture (dev) && - print_image_save (print, "verify.pgm")) - g_print ("Print image saved as verify.pgm\n"); - - if (match) - { - g_print ("MATCH!\n"); - verify_data->ret_value = EXIT_SUCCESS; - } - else - { - g_print ("NO MATCH!\n"); verify_data->ret_value = EXIT_FAILURE; + + if (error->domain != FP_DEVICE_RETRY) + { + verify_quit (dev, verify_data); + return; + } } g_print ("Verify again? [Y/n]? "); @@ -98,8 +103,48 @@ on_verify_completed (FpDevice *dev, GAsyncResult *res, void *user_data) return; } - fp_device_close (dev, NULL, (GAsyncReadyCallback) on_device_closed, - verify_data); + verify_quit (dev, verify_data); +} + +static void +on_match_cb (FpDevice *dev, FpPrint *match, FpPrint *print, + gpointer user_data, GError *error) +{ + VerifyData *verify_data = user_data; + + if (error) + { + g_warning ("Match report: Finger not matched, retry error reported: %s", + error->message); + return; + } + + if (print && fp_device_supports_capture (dev) && + print_image_save (print, "verify.pgm")) + g_print ("Print image saved as verify.pgm\n"); + + if (match) + { + char date_str[128]; + + verify_data->ret_value = EXIT_SUCCESS; + + g_date_strftime (date_str, G_N_ELEMENTS (date_str), "%Y-%m-%d\0", + fp_print_get_enroll_date (match)); + g_debug ("Match report: device %s matched finger %s successifully " + "with print %s, enrolled on date %s by user %s", + fp_device_get_name (dev), + finger_to_string (fp_print_get_finger (match)), + fp_print_get_description (match), date_str, + fp_print_get_username (match)); + + g_print ("MATCH!\n"); + } + else + { + g_debug ("Match report: Finger not matched"); + g_print ("NO MATCH!\n"); + } } static void @@ -143,7 +188,7 @@ on_list_completed (FpDevice *dev, GAsyncResult *res, gpointer user_data) { g_warning ("Did you remember to enroll your %s finger first?", finger_to_string (verify_data->finger)); - g_main_loop_quit (verify_data->loop); + verify_quit (dev, verify_data); return; } @@ -152,13 +197,14 @@ on_list_completed (FpDevice *dev, GAsyncResult *res, gpointer user_data) g_print ("Print loaded. Time to verify!\n"); fp_device_verify (dev, verify_print, NULL, + on_match_cb, verify_data, NULL, (GAsyncReadyCallback) on_verify_completed, verify_data); } else { g_warning ("Loading prints failed with error %s", error->message); - g_main_loop_quit (verify_data->loop); + verify_quit (dev, verify_data); } } @@ -175,7 +221,7 @@ start_verification (FpDevice *dev, VerifyData *verify_data) { g_warning ("Unknown finger selected"); verify_data->ret_value = EXIT_FAILURE; - g_main_loop_quit (verify_data->loop); + verify_quit (dev, verify_data); return; } @@ -199,12 +245,13 @@ start_verification (FpDevice *dev, VerifyData *verify_data) g_warning ("Failed to load fingerprint data"); g_warning ("Did you remember to enroll your %s finger first?", finger_to_string (verify_data->finger)); - g_main_loop_quit (verify_data->loop); + verify_quit (dev, verify_data); return; } g_print ("Print loaded. Time to verify!\n"); fp_device_verify (dev, verify_print, NULL, + NULL, NULL, NULL, (GAsyncReadyCallback) on_verify_completed, verify_data); } @@ -220,7 +267,7 @@ on_device_opened (FpDevice *dev, GAsyncResult *res, void *user_data) if (!fp_device_open_finish (dev, res, &error)) { g_warning ("Failed to open device: %s", error->message); - g_main_loop_quit (verify_data->loop); + verify_quit (dev, verify_data); return; } diff --git a/libfprint/drivers/elan.c b/libfprint/drivers/elan.c index 1c2a7a37..084e4bd1 100644 --- a/libfprint/drivers/elan.c +++ b/libfprint/drivers/elan.c @@ -585,8 +585,6 @@ capture_complete (FpiSsm *ssm, FpDevice *_dev, GError *error) G_DEBUG_HERE (); - /* XXX: cancellation was specially handled by doing nothing! */ - /* either max frames captured or timed out waiting for the next frame */ if (!error || (g_error_matches (error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT) && diff --git a/libfprint/drivers/synaptics/synaptics.c b/libfprint/drivers/synaptics/synaptics.c index 2aac75e8..8333fdae 100644 --- a/libfprint/drivers/synaptics/synaptics.c +++ b/libfprint/drivers/synaptics/synaptics.c @@ -279,17 +279,10 @@ cmd_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) self->cmd_ssm = NULL; /* Notify about the SSM failure from here instead. */ - if (error) - { - callback (self, NULL, error); - } - else if (self->cmd_complete_on_removal) - { - callback (self, NULL, self->cmd_complete_error); - self->cmd_complete_error = NULL; - } + if (error || self->cmd_complete_on_removal) + callback (self, NULL, error); + self->cmd_complete_on_removal = FALSE; - g_clear_pointer (&self->cmd_complete_error, g_error_free); } static void @@ -527,8 +520,8 @@ list_msg_cb (FpiDeviceSynaptics *self, userid[12] == '-' && userid[14] == '-' && userid[23] == '-') { g_autofree gchar *copy = g_strdup (userid); + g_autoptr(GDate) date = NULL; gint32 date_ymd; - GDate *date = NULL; gint32 finger; gchar *username; /* Try to parse information from the string. */ @@ -543,7 +536,6 @@ list_msg_cb (FpiDeviceSynaptics *self, date = g_date_new (); fp_print_set_enroll_date (print, date); - g_date_free (date); copy[14] = '\0'; finger = g_ascii_strtoll (copy + 13, NULL, 16); @@ -582,6 +574,22 @@ list (FpDevice *device) synaptics_sensor_cmd (self, 0, BMKT_CMD_GET_TEMPLATE_RECORDS, NULL, 0, list_msg_cb); } +static void +verify_complete_after_finger_removal (FpiDeviceSynaptics *self) +{ + FpDevice *device = FP_DEVICE (self); + + if (self->finger_on_sensor) + { + fp_dbg ("delaying verify report until after finger removal!"); + self->cmd_complete_on_removal = TRUE; + } + else + { + fpi_device_verify_complete (device, NULL); + } +} + static void verify_msg_cb (FpiDeviceSynaptics *self, bmkt_response_t *resp, @@ -592,16 +600,13 @@ verify_msg_cb (FpiDeviceSynaptics *self, if (error) { - fpi_device_verify_complete (device, FPI_MATCH_ERROR, NULL, error); + fpi_device_verify_complete (device, error); return; } if (resp == NULL && self->cmd_complete_on_removal) { - fpi_device_verify_complete (device, - GPOINTER_TO_INT (self->cmd_complete_data), - NULL, - error); + fpi_device_verify_complete (device, NULL); return; } @@ -620,39 +625,40 @@ verify_msg_cb (FpiDeviceSynaptics *self, break; case BMKT_RSP_VERIFY_FAIL: - if(resp->result == BMKT_SENSOR_STIMULUS_ERROR) + if (resp->result == BMKT_SENSOR_STIMULUS_ERROR) { - fp_dbg ("delaying retry error until after finger removal!"); - self->cmd_complete_on_removal = TRUE; - self->cmd_complete_data = GINT_TO_POINTER (FPI_MATCH_ERROR); - self->cmd_complete_error = fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL); + fp_info ("Match error occurred"); + fpi_device_verify_report (device, FPI_MATCH_ERROR, NULL, + fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL)); + verify_complete_after_finger_removal (self); } else if (resp->result == BMKT_FP_NO_MATCH) { - fp_dbg ("delaying match failure until after finger removal!"); - self->cmd_complete_on_removal = TRUE; - self->cmd_complete_data = GINT_TO_POINTER (FPI_MATCH_FAIL); - self->cmd_complete_error = NULL; + fp_info ("Print didn't match"); + fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, error); + verify_complete_after_finger_removal (self); } - else if (BMKT_FP_DATABASE_NO_RECORD_EXISTS) + else if (resp->result == BMKT_FP_DATABASE_NO_RECORD_EXISTS) { fp_info ("Print is not in database"); fpi_device_verify_complete (device, - FPI_MATCH_ERROR, - NULL, fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND)); } else { fp_warn ("Verify has failed: %d", resp->result); - fpi_device_verify_complete (device, FPI_MATCH_FAIL, NULL, NULL); + fpi_device_verify_complete (device, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Unexpected result from device %d", + resp->result)); } break; case BMKT_RSP_VERIFY_OK: fp_info ("Verify was successful! for user: %s finger: %d score: %f", verify_resp->user_id, verify_resp->finger_id, verify_resp->match_result); - fpi_device_verify_complete (device, FPI_MATCH_SUCCESS, NULL, NULL); + fpi_device_verify_report (device, FPI_MATCH_SUCCESS, NULL, NULL); + fpi_device_verify_complete (device, NULL); break; } } @@ -675,8 +681,6 @@ verify (FpDevice *device) if (!parse_print_data (data, &finger, &user_id, &user_id_len)) { fpi_device_verify_complete (device, - FPI_MATCH_ERROR, - NULL, fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); return; } diff --git a/libfprint/drivers/synaptics/synaptics.h b/libfprint/drivers/synaptics/synaptics.h index 37eb362e..ac50171b 100644 --- a/libfprint/drivers/synaptics/synaptics.h +++ b/libfprint/drivers/synaptics/synaptics.h @@ -110,8 +110,6 @@ struct _FpiDeviceSynaptics FpiSsm *cmd_ssm; FpiUsbTransfer *cmd_pending_transfer; gboolean cmd_complete_on_removal; - GError *cmd_complete_error; - void *cmd_complete_data; bmkt_sensor_version_t mis_version; diff --git a/libfprint/drivers/upekts.c b/libfprint/drivers/upekts.c index 16534d39..08e98c68 100644 --- a/libfprint/drivers/upekts.c +++ b/libfprint/drivers/upekts.c @@ -902,8 +902,10 @@ enroll_start_sm_cb_msg28 (FpDevice *dev, FpiSsm *ssm = user_data; if (error) - fpi_ssm_mark_failed (ssm, error); - if (type != READ_MSG_RESPONSE) + { + fpi_ssm_mark_failed (ssm, error); + } + else if (type != READ_MSG_RESPONSE) { fp_err ("expected response, got %d seq=%x", type, seq); fpi_ssm_mark_failed (ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, @@ -1225,8 +1227,7 @@ enroll (FpDevice *dev) typedef struct { - FpiMatchResult res; - GError *error; + GError *error; } VerifyStopData; static void @@ -1244,7 +1245,12 @@ verify_stop_deinit_cb (FpiSsm *ssm, FpDevice *dev, GError *error) if (error) fp_warn ("Error deinitializing: %s", error->message); - fpi_device_verify_complete (dev, data->res, NULL, data->error); + if (data->error) + fpi_device_verify_complete (dev, data->error); + else + fpi_device_verify_complete (dev, g_steal_pointer (&error)); + + g_error_free (error); } static void @@ -1253,8 +1259,11 @@ do_verify_stop (FpDevice *dev, FpiMatchResult res, GError *error) VerifyStopData *data = g_new0 (VerifyStopData, 1); FpiSsm *ssm = deinitsm_new (dev, data); - data->res = res; - data->error = error; + /* Report the error immediately if possible, otherwise delay it. */ + if (!error && error->domain != FP_DEVICE_RETRY) + fpi_device_verify_report (dev, res, NULL, error); + else + data->error = error; fpi_ssm_start (ssm, verify_stop_deinit_cb); fpi_ssm_set_data (ssm, data, (GDestroyNotify) verify_stop_data_free); diff --git a/libfprint/drivers_api.h b/libfprint/drivers_api.h index 7476ba73..aef8c9dc 100644 --- a/libfprint/drivers_api.h +++ b/libfprint/drivers_api.h @@ -21,6 +21,7 @@ #pragma once +#include "fpi-compat.h" #include "fpi-assembling.h" #include "fpi-device.h" #include "fpi-image-device.h" diff --git a/libfprint/fp-context.c b/libfprint/fp-context.c index 5dc37000..f0dd6858 100644 --- a/libfprint/fp-context.c +++ b/libfprint/fp-context.c @@ -136,8 +136,7 @@ usb_device_added_cb (FpContext *self, GUsbDevice *device, GUsbContext *usb_ctx) for (i = 0; i < priv->drivers->len; i++) { GType driver = g_array_index (priv->drivers, GType, i); - g_autoptr(GTypeClass) type_class = g_type_class_ref (driver); - FpDeviceClass *cls = FP_DEVICE_CLASS (type_class); + g_autoptr(FpDeviceClass) cls = g_type_class_ref (driver); const FpIdEntry *entry; if (cls->type != FP_DEVICE_TYPE_USB) @@ -288,8 +287,7 @@ fp_context_init (FpContext *self) for (i = 0; i < priv->drivers->len;) { GType driver = g_array_index (priv->drivers, GType, i); - g_autoptr(GTypeClass) type_class = g_type_class_ref (driver); - FpDeviceClass *cls = FP_DEVICE_CLASS (type_class); + g_autoptr(FpDeviceClass) cls = g_type_class_ref (driver); if (!is_driver_allowed (cls->id)) g_array_remove_index (priv->drivers, i); @@ -363,8 +361,7 @@ fp_context_enumerate (FpContext *context) for (i = 0; i < priv->drivers->len; i++) { GType driver = g_array_index (priv->drivers, GType, i); - g_autoptr(GTypeClass) type_class = g_type_class_ref (driver); - FpDeviceClass *cls = FP_DEVICE_CLASS (type_class); + g_autoptr(FpDeviceClass) cls = g_type_class_ref (driver); const FpIdEntry *entry; if (cls->type != FP_DEVICE_TYPE_VIRTUAL) diff --git a/libfprint/fp-device-private.h b/libfprint/fp-device-private.h index 1a350fe5..5bf5954a 100644 --- a/libfprint/fp-device-private.h +++ b/libfprint/fp-device-private.h @@ -63,3 +63,20 @@ typedef struct } FpEnrollData; void enroll_data_free (FpEnrollData *enroll_data); + +typedef struct +{ + FpPrint *enrolled_print; /* verify */ + GPtrArray *gallery; /* identify */ + + gboolean result_reported; + FpPrint *match; + FpPrint *print; + GError *error; + + FpMatchCb match_cb; + gpointer match_data; + GDestroyNotify match_destroy; +} FpMatchData; + +void match_data_free (FpMatchData *match_data); diff --git a/libfprint/fp-device.c b/libfprint/fp-device.c index 116f9f87..fe36eae3 100644 --- a/libfprint/fp-device.c +++ b/libfprint/fp-device.c @@ -775,6 +775,7 @@ fp_device_enroll (FpDevice *device, data->print = g_object_ref_sink (template_print); data->enroll_progress_cb = progress_cb; data->enroll_progress_data = progress_data; + data->enroll_progress_destroy = progress_destroy; // Attach the progress data as task data so that it is destroyed g_task_set_task_data (priv->current_task, data, (GDestroyNotify) enroll_data_free); @@ -808,10 +809,13 @@ fp_device_enroll_finish (FpDevice *device, * @device: a #FpDevice * @enrolled_print: a #FpPrint to verify * @cancellable: (nullable): a #GCancellable, or %NULL + * @match_cb: (nullable) (scope notified): match reporting callback + * @match_data: (closure match_cb): user data for @match_cb + * @match_destroy: (destroy match_data): Destroy notify for @match_data * @callback: the function to call on completion * @user_data: the data to pass to @callback * - * Start an asynchronous operation to close the device. The callback will + * Start an asynchronous operation to verify a print. The callback will * be called once the operation has finished. Retrieve the result with * fp_device_verify_finish(). */ @@ -819,11 +823,15 @@ void fp_device_verify (FpDevice *device, FpPrint *enrolled_print, GCancellable *cancellable, + FpMatchCb match_cb, + gpointer match_data, + GDestroyNotify match_destroy, GAsyncReadyCallback callback, gpointer user_data) { g_autoptr(GTask) task = NULL; FpDevicePrivate *priv = fp_device_get_instance_private (device); + FpMatchData *data; task = g_task_new (device, cancellable, callback, user_data); if (g_task_return_error_if_cancelled (task)) @@ -847,9 +855,14 @@ fp_device_verify (FpDevice *device, priv->current_task = g_steal_pointer (&task); maybe_cancel_on_cancelled (device, cancellable); - g_task_set_task_data (priv->current_task, - g_object_ref (enrolled_print), - g_object_unref); + data = g_new0 (FpMatchData, 1); + data->enrolled_print = g_object_ref (enrolled_print); + data->match_cb = match_cb; + data->match_data = match_data; + data->match_destroy = match_destroy; + + // Attach the match data as task data so that it is destroyed + g_task_set_task_data (priv->current_task, data, (GDestroyNotify) match_data_free); FP_DEVICE_GET_CLASS (device)->verify (device); } @@ -885,7 +898,11 @@ fp_device_verify_finish (FpDevice *device, if (print) { - *print = g_object_get_data (G_OBJECT (result), "print"); + FpMatchData *data; + + data = g_task_get_task_data (G_TASK (result)); + + *print = data->print; if (*print) g_object_ref (*print); } @@ -901,6 +918,9 @@ fp_device_verify_finish (FpDevice *device, * @device: a #FpDevice * @prints: (element-type FpPrint) (transfer none): #GPtrArray of #FpPrint * @cancellable: (nullable): a #GCancellable, or %NULL + * @match_cb: (nullable) (scope notified): match reporting callback + * @match_data: (closure match_cb): user data for @match_cb + * @match_destroy: (destroy match_data): Destroy notify for @match_data * @callback: the function to call on completion * @user_data: the data to pass to @callback * @@ -912,11 +932,15 @@ void fp_device_identify (FpDevice *device, GPtrArray *prints, GCancellable *cancellable, + FpMatchCb match_cb, + gpointer match_data, + GDestroyNotify match_destroy, GAsyncReadyCallback callback, gpointer user_data) { g_autoptr(GTask) task = NULL; FpDevicePrivate *priv = fp_device_get_instance_private (device); + FpMatchData *data; task = g_task_new (device, cancellable, callback, user_data); if (g_task_return_error_if_cancelled (task)) @@ -940,9 +964,14 @@ fp_device_identify (FpDevice *device, priv->current_task = g_steal_pointer (&task); maybe_cancel_on_cancelled (device, cancellable); - g_task_set_task_data (priv->current_task, - g_ptr_array_ref (prints), - (GDestroyNotify) g_ptr_array_unref); + data = g_new0 (FpMatchData, 1); + data->gallery = g_ptr_array_ref (prints); + data->match_cb = match_cb; + data->match_data = match_data; + data->match_destroy = match_destroy; + + // Attach the match data as task data so that it is destroyed + g_task_set_task_data (priv->current_task, data, (GDestroyNotify) match_data_free); FP_DEVICE_GET_CLASS (device)->identify (device); } @@ -973,15 +1002,19 @@ fp_device_identify_finish (FpDevice *device, FpPrint **print, GError **error) { + FpMatchData *data; + + data = g_task_get_task_data (G_TASK (result)); + if (print) { - *print = g_object_get_data (G_OBJECT (result), "print"); + *print = data->print; if (*print) g_object_ref (*print); } if (match) { - *match = g_object_get_data (G_OBJECT (result), "match"); + *match = data->match; if (*match) g_object_ref (*match); } @@ -1180,6 +1213,14 @@ fp_device_list_prints (FpDevice *device, return; } + if (!fp_device_has_storage (device)) + { + g_task_return_error (task, + fpi_device_error_new_msg (FP_DEVICE_ERROR_NOT_SUPPORTED, + "Device has no storage")); + return; + } + priv->current_action = FPI_DEVICE_ACTION_LIST; priv->current_task = g_steal_pointer (&task); maybe_cancel_on_cancelled (device, cancellable); @@ -1308,6 +1349,8 @@ fp_device_enroll_sync (FpDevice *device, * @device: a #FpDevice * @enrolled_print: a #FpPrint to verify * @cancellable: (nullable): a #GCancellable, or %NULL + * @match_cb: (nullable) (scope call): match reporting callback + * @match_data: (closure match_cb): user data for @match_cb * @match: (out): Whether the user presented the correct finger * @print: (out) (transfer full) (nullable): Location to store the scanned print, or %NULL to ignore * @error: Return location for errors, or %NULL to ignore @@ -1320,6 +1363,8 @@ gboolean fp_device_verify_sync (FpDevice *device, FpPrint *enrolled_print, GCancellable *cancellable, + FpMatchCb match_cb, + gpointer match_data, gboolean *match, FpPrint **print, GError **error) @@ -1331,6 +1376,7 @@ fp_device_verify_sync (FpDevice *device, fp_device_verify (device, enrolled_print, cancellable, + match_cb, match_data, NULL, async_result_ready, &task); while (!task) g_main_context_iteration (NULL, TRUE); @@ -1343,6 +1389,8 @@ fp_device_verify_sync (FpDevice *device, * @device: a #FpDevice * @prints: (element-type FpPrint) (transfer none): #GPtrArray of #FpPrint * @cancellable: (nullable): a #GCancellable, or %NULL + * @match_cb: (nullable) (scope call): match reporting callback + * @match_data: (closure match_cb): user data for @match_cb * @match: (out) (transfer full) (nullable): Location for the matched #FpPrint, or %NULL * @print: (out) (transfer full) (nullable): Location for the new #FpPrint, or %NULL * @error: Return location for errors, or %NULL to ignore @@ -1355,6 +1403,8 @@ gboolean fp_device_identify_sync (FpDevice *device, GPtrArray *prints, GCancellable *cancellable, + FpMatchCb match_cb, + gpointer match_data, FpPrint **match, FpPrint **print, GError **error) @@ -1366,6 +1416,7 @@ fp_device_identify_sync (FpDevice *device, fp_device_identify (device, prints, cancellable, + match_cb, match_data, NULL, async_result_ready, &task); while (!task) g_main_context_iteration (NULL, TRUE); diff --git a/libfprint/fp-device.h b/libfprint/fp-device.h index 4f7acaca..b0b690cc 100644 --- a/libfprint/fp-device.h +++ b/libfprint/fp-device.h @@ -125,6 +125,43 @@ typedef void (*FpEnrollProgress) (FpDevice *device, gpointer user_data, GError *error); +/** + * FpMatchCb: + * @device: a #FpDevice + * @match: (nullable) (transfer none): The matching print if any matched @print + * @print: (nullable) (transfer none): The newly scanned print + * @user_data: (nullable) (transfer none): User provided data + * @error: (nullable) (transfer none): #GError or %NULL + * + * Report the result of a match (identify or verify) operation. + * + * If @match is non-%NULL, then it is set to the matching #FpPrint as passed + * to the match operation. In this case @error will always be %NULL. + * + * If @error is not %NULL then its domain is guaranteed to be + * %FP_DEVICE_RETRY. All other error conditions will not be reported using + * this callback. If such an error occurs before a match/no-match decision + * can be made, then this callback will not be called. Should an error + * happen afterwards, then you will get a match report through this callback + * and an error when the operation finishes. + * + * If @match and @error are %NULL, then a finger was presented but it did not + * match any known print. + * + * @print represents the newly scanned print. The driver may or may not + * provide this information. Image based devices will provide it and it + * allows access to the raw data. + * + * This callback exists because it makes sense for drivers to wait e.g. on + * finger removal before completing the match operation. However, the + * success/failure can often be reported at an earlier time, and there is + * no need to make the user wait. + */ +typedef void (*FpMatchCb) (FpDevice *device, + FpPrint *match, + FpPrint *print, + gpointer user_data, + GError *error); const gchar *fp_device_get_driver (FpDevice *device); const gchar *fp_device_get_device_id (FpDevice *device); @@ -160,12 +197,18 @@ void fp_device_enroll (FpDevice *device, void fp_device_verify (FpDevice *device, FpPrint *enrolled_print, GCancellable *cancellable, + FpMatchCb match_cb, + gpointer match_data, + GDestroyNotify match_destroy, GAsyncReadyCallback callback, gpointer user_data); void fp_device_identify (FpDevice *device, GPtrArray *prints, GCancellable *cancellable, + FpMatchCb match_cb, + gpointer match_data, + GDestroyNotify match_destroy, GAsyncReadyCallback callback, gpointer user_data); @@ -231,12 +274,16 @@ FpPrint * fp_device_enroll_sync (FpDevice *device, gboolean fp_device_verify_sync (FpDevice *device, FpPrint *enrolled_print, GCancellable *cancellable, + FpMatchCb match_cb, + gpointer match_data, gboolean *match, FpPrint **print, GError **error); gboolean fp_device_identify_sync (FpDevice *device, GPtrArray *prints, GCancellable *cancellable, + FpMatchCb match_cb, + gpointer match_data, FpPrint **match, FpPrint **print, GError **error); diff --git a/libfprint/fp-print.c b/libfprint/fp-print.c index 34139ce8..d02bf0a3 100644 --- a/libfprint/fp-print.c +++ b/libfprint/fp-print.c @@ -21,6 +21,7 @@ #define FP_COMPONENT "print" #include "fp-print-private.h" +#include "fpi-compat.h" #include "fpi-log.h" /** @@ -752,8 +753,8 @@ fp_print_deserialize (const guchar *data, g_autoptr(GVariant) raw_value = NULL; g_autoptr(GVariant) value = NULL; g_autoptr(GVariant) print_data = NULL; + g_autoptr(GDate) date = NULL; guchar *aligned_data = NULL; - GDate *date = NULL; guint8 finger_int8; FpFinger finger; g_autofree gchar *username = NULL; @@ -881,8 +882,6 @@ fp_print_deserialize (const guchar *data, "enroll_date", date, NULL); - g_date_free (date); - return g_steal_pointer (&result); invalid_format: diff --git a/libfprint/fpi-compat.h b/libfprint/fpi-compat.h new file mode 100644 index 00000000..59025e14 --- /dev/null +++ b/libfprint/fpi-compat.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 Benjamin Berg + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include + +#if !GLIB_CHECK_VERSION (2, 57, 0) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GTypeClass, g_type_class_unref); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GEnumClass, g_type_class_unref); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GParamSpec, g_param_spec_unref); +#else +/* Re-define G_SOURCE_FUNC as we are technically not allowed to use it with + * the version we depend on currently. */ +#undef G_SOURCE_FUNC +#endif + +#define G_SOURCE_FUNC(f) ((GSourceFunc) (void (*)(void))(f)) + +#if !GLIB_CHECK_VERSION (2, 63, 3) +typedef struct _FpDeviceClass FpDeviceClass; +G_DEFINE_AUTOPTR_CLEANUP_FUNC (FpDeviceClass, g_type_class_unref); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GDate, g_date_free); +#endif diff --git a/libfprint/fpi-context.h b/libfprint/fpi-context.h index 48fecb48..ec98675b 100644 --- a/libfprint/fpi-context.h +++ b/libfprint/fpi-context.h @@ -20,6 +20,7 @@ #include #include "fp-context.h" +#include "fpi-compat.h" /** * fpi_get_driver_types: diff --git a/libfprint/fpi-device.c b/libfprint/fpi-device.c index 51dbee17..88d752a9 100644 --- a/libfprint/fpi-device.c +++ b/libfprint/fpi-device.c @@ -420,6 +420,23 @@ enroll_data_free (FpEnrollData *data) g_free (data); } +void +match_data_free (FpMatchData *data) +{ + g_clear_object (&data->print); + g_clear_object (&data->match); + g_clear_error (&data->error); + + if (data->match_destroy) + data->match_destroy (data->match_data); + data->match_data = NULL; + + g_clear_object (&data->enrolled_print); + g_clear_pointer (&data->gallery, g_ptr_array_unref); + + g_free (data); +} + /** * fpi_device_get_enroll_data: * @device: The #FpDevice @@ -476,12 +493,16 @@ fpi_device_get_verify_data (FpDevice *device, FpPrint **print) { FpDevicePrivate *priv = fp_device_get_instance_private (device); + FpMatchData *data; g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_VERIFY); + data = g_task_get_task_data (priv->current_task); + g_assert (data); + if (print) - *print = g_task_get_task_data (priv->current_task); + *print = data->enrolled_print; } /** @@ -496,12 +517,16 @@ fpi_device_get_identify_data (FpDevice *device, GPtrArray **prints) { FpDevicePrivate *priv = fp_device_get_instance_private (device); + FpMatchData *data; g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_IDENTIFY); + data = g_task_get_task_data (priv->current_task); + g_assert (data); + if (prints) - *prints = g_task_get_task_data (priv->current_task); + *prints = data->gallery; } /** @@ -596,11 +621,11 @@ fpi_device_action_error (FpDevice *device, break; case FPI_DEVICE_ACTION_VERIFY: - fpi_device_verify_complete (device, FPI_MATCH_ERROR, NULL, error); + fpi_device_verify_complete (device, error); break; case FPI_DEVICE_ACTION_IDENTIFY: - fpi_device_identify_complete (device, NULL, NULL, error); + fpi_device_identify_complete (device, error); break; case FPI_DEVICE_ACTION_CAPTURE: @@ -826,8 +851,6 @@ fpi_device_close_complete (FpDevice *device, GError *error) g_debug ("Device reported close completion"); clear_device_cancel_action (device); - priv->is_open = FALSE; - g_object_notify (G_OBJECT (device), "open"); switch (priv->type) { @@ -852,10 +875,16 @@ fpi_device_close_complete (FpDevice *device, GError *error) } if (!error) - fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL, - GUINT_TO_POINTER (TRUE)); + { + priv->is_open = FALSE; + g_object_notify (G_OBJECT (device), "open"); + fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL, + GUINT_TO_POINTER (TRUE)); + } else - fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); + { + fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); + } } /** @@ -907,64 +936,68 @@ fpi_device_enroll_complete (FpDevice *device, FpPrint *print, GError *error) /** * fpi_device_verify_complete: * @device: The #FpDevice - * @result: The #FpiMatchResult of the operation - * @print: The scanned #FpPrint * @error: A #GError if result is %FPI_MATCH_ERROR * * Finish an ongoing verify operation. The returned print should be * representing the new scan and not the one passed for verification. + * + * Note that @error should only be set for actual errors. In the case + * of retry errors, report these using fpi_device_verify_report() + * and then call this function without any error argument. */ void -fpi_device_verify_complete (FpDevice *device, - FpiMatchResult result, - FpPrint *print, - GError *error) +fpi_device_verify_complete (FpDevice *device, + GError *error) { FpDevicePrivate *priv = fp_device_get_instance_private (device); + FpMatchData *data; g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_VERIFY); g_debug ("Device reported verify completion"); - clear_device_cancel_action (device); + data = g_task_get_task_data (priv->current_task); - g_object_set_data_full (G_OBJECT (priv->current_task), - "print", - print, - g_object_unref); + clear_device_cancel_action (device); if (!error) { - if (result != FPI_MATCH_ERROR) + if (!data->result_reported) { - fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_INT, - GINT_TO_POINTER (result)); + g_warning ("Driver reported successful verify complete but did not report the result earlier. Reporting error instead"); + fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + } + else if (data->error) + { + fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, g_steal_pointer (&data->error)); } else { - g_warning ("Driver did not provide an error for a failed verify operation!"); - error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, - "Driver failed to provide an error!"); - fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); + fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_INT, + GINT_TO_POINTER (data->match != NULL ? FPI_MATCH_SUCCESS : FPI_MATCH_FAIL)); } } else { - fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); - if (result != FPI_MATCH_ERROR) + /* Replace a retry error with a general error, this is a driver bug. */ + if (error->domain == FP_DEVICE_RETRY) { - g_warning ("Driver passed an error but also provided a match result, returning error!"); - g_object_unref (print); + g_warning ("Driver reported a retry error to fpi_device_verify_complete. " + "This is not permissible and needs to be reported using " + "fpi_device_verify_report, reporting general verification " + "failure instead."); + g_clear_error (&error); + error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL); } + fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); } } /** * fpi_device_identify_complete: * @device: The #FpDevice - * @match: The matching #FpPrint from the passed gallery, or %NULL if none matched - * @print: The scanned #FpPrint, may be %NULL * @error: The #GError or %NULL on success * * Finish an ongoing identify operation. The match that was identified is @@ -973,40 +1006,50 @@ fpi_device_verify_complete (FpDevice *device, */ void fpi_device_identify_complete (FpDevice *device, - FpPrint *match, - FpPrint *print, GError *error) { FpDevicePrivate *priv = fp_device_get_instance_private (device); + FpMatchData *data; g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_IDENTIFY); g_debug ("Device reported identify completion"); + data = g_task_get_task_data (priv->current_task); + clear_device_cancel_action (device); - g_object_set_data_full (G_OBJECT (priv->current_task), - "print", - print, - g_object_unref); - g_object_set_data_full (G_OBJECT (priv->current_task), - "match", - match, - g_object_unref); if (!error) { - fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL, - GUINT_TO_POINTER (TRUE)); + if (!data->result_reported) + { + g_warning ("Driver reported successful identify complete but did not report the result earlier. Reporting error instead"); + fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + } + else if (data->error) + { + fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, g_steal_pointer (&data->error)); + } + else + { + fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_INT, GINT_TO_POINTER (TRUE)); + } } else { - fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); - if (match) + /* Replace a retry error with a general error, this is a driver bug. */ + if (error->domain == FP_DEVICE_RETRY) { - g_warning ("Driver passed an error but also provided a match result, returning error!"); - g_clear_object (&match); + g_warning ("Driver reported a retry error to fpi_device_identify_complete. " + "This is not permissible and needs to be reported using " + "fpi_device_identify_report, reporting general identification " + "failure instead."); + g_clear_error (&error); + error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL); } + fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); } } @@ -1134,7 +1177,7 @@ fpi_device_list_complete (FpDevice *device, * fpi_device_enroll_progress: * @device: The #FpDevice * @completed_stages: The number of stages that are completed at this point - * @print: (transfer full): The #FpPrint for the newly completed stage or %NULL on failure + * @print: (transfer floating): The #FpPrint for the newly completed stage or %NULL on failure * @error: (transfer full): The #GError or %NULL on success * * Notify about the progress of the enroll operation. This is important for UI interaction. @@ -1155,6 +1198,9 @@ fpi_device_enroll_progress (FpDevice *device, g_debug ("Device reported enroll progress, reported %i of %i have been completed", completed_stages, priv->nr_enroll_stages); + if (print) + g_object_ref_sink (print); + if (error && print) { g_warning ("Driver passed an error and also provided a print, returning error!"); @@ -1175,3 +1221,151 @@ fpi_device_enroll_progress (FpDevice *device, g_clear_error (&error); g_clear_object (&print); } + +/** + * fpi_device_verify_report: + * @device: The #FpDevice + * @result: The #FpiMatchResult of the operation + * @print: (transfer floating) The scanned #FpPrint + * @error: A #GError if result is %FPI_MATCH_ERROR + * + * Report the result of a verify operation. Note that the passed @error must be + * a retry error with the %FP_DEVICE_RETRY domain. For all other error cases, + * the error should passed to fpi_device_verify_complete(). + */ +void +fpi_device_verify_report (FpDevice *device, + FpiMatchResult result, + FpPrint *print, + GError *error) +{ + FpDevicePrivate *priv = fp_device_get_instance_private (device); + FpMatchData *data = g_task_get_task_data (priv->current_task); + gboolean call_cb = TRUE; + + g_return_if_fail (FP_IS_DEVICE (device)); + g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_VERIFY); + g_return_if_fail (data->result_reported == FALSE); + + data->result_reported = TRUE; + + g_debug ("Device reported verify result"); + + if (print) + print = g_object_ref_sink (print); + + if (error || result == FPI_MATCH_ERROR) + { + if (result != FPI_MATCH_ERROR) + g_warning ("Driver reported an error code without setting match result to error!"); + + if (error == NULL) + { + g_warning ("Driver reported an error without specifying a retry code, assuming general retry error!"); + error = fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL); + } + + if (print) + { + g_warning ("Driver reported a print together with an error!"); + g_clear_object (&print); + } + + data->error = error; + + if (error->domain != FP_DEVICE_RETRY) + { + g_warning ("Driver reported a verify error that was not in the retry domain, delaying report!"); + call_cb = FALSE; + } + } + else + { + if (result == FPI_MATCH_SUCCESS) + { + fpi_device_get_verify_data (device, &data->match); + g_object_ref (data->match); + } + + data->print = g_steal_pointer (&print); + } + + if (call_cb && data->match_cb) + data->match_cb (device, data->match, data->print, data->match_data, data->error); +} + +/** + * fpi_device_identify_report: + * @device: The #FpDevice + * @match: (transfer none): The #FpPrint from the gallery that matched + * @print: (transfer floating): The scanned #FpPrint + * @error: A #GError if result is %FPI_MATCH_ERROR + * + * Report the result of a identify operation. Note that the passed @error must be + * a retry error with the %FP_DEVICE_RETRY domain. For all other error cases, + * the error should passed to fpi_device_identify_complete(). + */ +void +fpi_device_identify_report (FpDevice *device, + FpPrint *match, + FpPrint *print, + GError *error) +{ + FpDevicePrivate *priv = fp_device_get_instance_private (device); + FpMatchData *data = g_task_get_task_data (priv->current_task); + gboolean call_cb = TRUE; + + g_return_if_fail (FP_IS_DEVICE (device)); + g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_IDENTIFY); + g_return_if_fail (data->result_reported == FALSE); + + data->result_reported = TRUE; + + if (match) + g_object_ref (match); + + if (print) + print = g_object_ref_sink (print); + + if (match && !g_ptr_array_find (data->gallery, match, NULL)) + { + g_warning ("Driver reported a match to a print that was not in the gallery, ignoring match."); + g_clear_object (&match); + } + + g_debug ("Device reported identify result"); + + if (error) + { + if (match != NULL) + { + g_warning ("Driver reported an error code but also provided a match!"); + g_clear_object (&match); + } + + if (print) + { + g_warning ("Driver reported a print together with an error!"); + g_clear_object (&print); + } + + data->error = error; + + if (error->domain != FP_DEVICE_RETRY) + { + g_warning ("Driver reported a verify error that was not in the retry domain, delaying report!"); + call_cb = FALSE; + } + } + else + { + if (match) + data->match = g_steal_pointer (&match); + + if (print) + data->print = g_steal_pointer (&print); + } + + if (call_cb && data->match_cb) + data->match_cb (device, data->match, data->print, data->match_data, data->error); +} diff --git a/libfprint/fpi-device.h b/libfprint/fpi-device.h index fa8ddf15..23656ee1 100644 --- a/libfprint/fpi-device.h +++ b/libfprint/fpi-device.h @@ -237,13 +237,9 @@ void fpi_device_close_complete (FpDevice *device, void fpi_device_enroll_complete (FpDevice *device, FpPrint *print, GError *error); -void fpi_device_verify_complete (FpDevice *device, - FpiMatchResult result, - FpPrint *print, - GError *error); +void fpi_device_verify_complete (FpDevice *device, + GError *error); void fpi_device_identify_complete (FpDevice *device, - FpPrint *match, - FpPrint *print, GError *error); void fpi_device_capture_complete (FpDevice *device, FpImage *image, @@ -258,5 +254,13 @@ void fpi_device_enroll_progress (FpDevice *device, gint completed_stages, FpPrint *print, GError *error); +void fpi_device_verify_report (FpDevice *device, + FpiMatchResult result, + FpPrint *print, + GError *error); +void fpi_device_identify_report (FpDevice *device, + FpPrint *match, + FpPrint *print, + GError *error); G_END_DECLS diff --git a/libfprint/fpi-image-device.c b/libfprint/fpi-image-device.c index efdbb532..55b64886 100644 --- a/libfprint/fpi-image-device.c +++ b/libfprint/fpi-image-device.c @@ -210,7 +210,9 @@ fpi_image_device_minutiae_detected (GObject *source_object, GAsyncResult *res, g else result = FPI_MATCH_ERROR; - fpi_device_verify_complete (device, result, g_steal_pointer (&print), error); + if (!error || error->domain == FP_DEVICE_RETRY) + fpi_device_verify_report (device, result, g_steal_pointer (&print), g_steal_pointer (&error)); + fpi_device_verify_complete (device, error); fpi_image_device_deactivate (self); } else if (action == FPI_DEVICE_ACTION_IDENTIFY) @@ -226,12 +228,14 @@ fpi_image_device_minutiae_detected (GObject *source_object, GAsyncResult *res, g if (fpi_print_bz3_match (template, print, priv->bz3_threshold, &error) == FPI_MATCH_SUCCESS) { - result = g_object_ref (template); + result = template; break; } } - fpi_device_identify_complete (device, result, g_steal_pointer (&print), error); + if (!error || error->domain == FP_DEVICE_RETRY) + fpi_device_identify_report (device, result, g_steal_pointer (&print), g_steal_pointer (&error)); + fpi_device_identify_complete (device, error); fpi_image_device_deactivate (self); } else @@ -410,12 +414,30 @@ fpi_image_device_retry_scan (FpImageDevice *self, FpDeviceRetry retry) priv->enroll_await_on_pending = TRUE; fp_image_device_change_state (self, FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF); } + else if (action == FPI_DEVICE_ACTION_VERIFY) + { + fpi_device_verify_report (FP_DEVICE (self), FPI_MATCH_ERROR, NULL, error); + priv->cancelling = TRUE; + fpi_image_device_deactivate (self); + priv->cancelling = FALSE; + fpi_device_verify_complete (FP_DEVICE (self), NULL); + } + else if (action == FPI_DEVICE_ACTION_IDENTIFY) + { + fpi_device_identify_report (FP_DEVICE (self), NULL, NULL, error); + priv->cancelling = TRUE; + fpi_image_device_deactivate (self); + priv->cancelling = FALSE; + fpi_device_identify_complete (FP_DEVICE (self), NULL); + } else { /* We abort the operation and let the surrounding code retry in the * non-enroll case (this is identical to a session error). */ g_debug ("Abort current operation due to retry (non-enroll case)"); + priv->cancelling = TRUE; fpi_image_device_deactivate (self); + priv->cancelling = FALSE; fpi_device_action_error (FP_DEVICE (self), error); } } @@ -426,7 +448,9 @@ fpi_image_device_retry_scan (FpImageDevice *self, FpDeviceRetry retry) * @error: The #GError to report * * Report an error while interacting with the device. This effectively - * aborts the current ongoing action. + * aborts the current ongoing action. Note that doing so will result in + * the deactivation handler to be called and this function must not be + * used to report an error during deactivation. */ void fpi_image_device_session_error (FpImageDevice *self, GError *error) @@ -453,17 +477,29 @@ fpi_image_device_session_error (FpImageDevice *self, GError *error) return; } } + else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && + fpi_device_action_is_cancelled (FP_DEVICE (self))) + { + /* Ignore cancellation errors here, as we will explicitly deactivate + * anyway (or, may already have done so at this point). + */ + g_debug ("Driver reported a cancellation error, this is expected but not required. Ignoring."); + g_clear_error (&error); + return; + } else if (priv->state == FPI_IMAGE_DEVICE_STATE_INACTIVE) { - g_warning ("Driver reported session error; translating to deactivation failure."); - fpi_image_device_deactivate_complete (self, error); + g_warning ("Driver reported session error while deactivating already, ignoring. This indicates a driver bug."); + g_clear_error (&error); return; } if (error->domain == FP_DEVICE_RETRY) g_warning ("Driver should report retries using fpi_image_device_retry_scan!"); + priv->cancelling = TRUE; fpi_image_device_deactivate (self); + priv->cancelling = FALSE; fpi_device_action_error (FP_DEVICE (self), error); } diff --git a/libfprint/fprint-list-supported-devices.c b/libfprint/fprint-list-supported-devices.c index cb2803f1..ae8cc113 100644 --- a/libfprint/fprint-list-supported-devices.c +++ b/libfprint/fprint-list-supported-devices.c @@ -38,8 +38,7 @@ insert_drivers (GList *list) for (i = 0; i < drivers->len; i++) { GType driver = g_array_index (drivers, GType, i); - g_autoptr(GTypeClass) type_class = g_type_class_ref (driver); - FpDeviceClass *cls = FP_DEVICE_CLASS (type_class); + g_autoptr(FpDeviceClass) cls = g_type_class_ref (driver); const FpIdEntry *entry; if (cls->type != FP_DEVICE_TYPE_USB) diff --git a/libfprint/fprint-list-udev-rules.c b/libfprint/fprint-list-udev-rules.c index ac507971..bd7c8a89 100644 --- a/libfprint/fprint-list-udev-rules.c +++ b/libfprint/fprint-list-udev-rules.c @@ -104,8 +104,7 @@ main (int argc, char **argv) for (i = 0; i < drivers->len; i++) { GType driver = g_array_index (drivers, GType, i); - g_autoptr(GTypeClass) type_class = g_type_class_ref (driver); - FpDeviceClass *cls = FP_DEVICE_CLASS (type_class); + g_autoptr(FpDeviceClass) cls = g_type_class_ref (driver); if (cls->type != FP_DEVICE_TYPE_USB) continue; diff --git a/libfprint/meson.build b/libfprint/meson.build index 8cfef8c6..de37fdac 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -177,7 +177,7 @@ other_sources = [] fp_enums = gnome.mkenums_simple('fp-enums', sources: libfprint_public_headers, install_header: true, - install_dir: get_option('includedir') / meson.project_name(), + install_dir: get_option('includedir') / versioned_libname, ) fp_enums_h = fp_enums[1] @@ -209,6 +209,10 @@ deps = [ nss_dep, ] +# These are empty and only exist so that the include directories are created +# in the build tree. This silences a build time warning. +subdir('nbis/include') +subdir('nbis/libfprint-include') deps += declare_dependency(include_directories: [ root_inc, include_directories('nbis/include'), @@ -248,7 +252,7 @@ libfprint_drivers = static_library('fprint-drivers', mapfile = files('libfprint.ver') vflag = '-Wl,--version-script,@0@/@1@'.format(meson.source_root(), mapfile[0]) -libfprint = library('fprint', +libfprint = library(versioned_libname.split('lib')[1], sources: [ fp_enums, libfprint_sources, @@ -272,7 +276,7 @@ libfprint_dep = declare_dependency(link_with: libfprint, ]) install_headers(['fprint.h'] + libfprint_public_headers, - subdir: meson.project_name() + subdir: versioned_libname ) libfprint_private_dep = declare_dependency( @@ -292,7 +296,7 @@ udev_rules = executable('fprint-list-udev-rules', if get_option('udev_rules') custom_target('udev-rules', - output: '60-fprint-autosuspend.rules', + output: '60-@0@-autosuspend.rules'.format(versioned_libname), capture: true, command: [ udev_rules ], install: true, diff --git a/libfprint/nbis/include/meson.build b/libfprint/nbis/include/meson.build new file mode 100644 index 00000000..e69de29b diff --git a/libfprint/nbis/libfprint-include/meson.build b/libfprint/nbis/libfprint-include/meson.build new file mode 100644 index 00000000..e69de29b diff --git a/meson.build b/meson.build index 06392e6e..8d35d925 100644 --- a/meson.build +++ b/meson.build @@ -1,10 +1,10 @@ project('libfprint', [ 'c', 'cpp' ], - version: '1.90.0', + version: '1.90.1', license: 'LGPLv2.1+', default_options: [ 'buildtype=debugoptimized', 'warning_level=1', - 'c_std=c99', + 'c_std=gnu99', ], meson_version: '>= 0.49.0') @@ -53,10 +53,9 @@ common_cflags = cc.get_supported_arguments([ '-DGLIB_VERSION_MIN_REQUIRED=' + glib_version_def, '-DGLIB_VERSION_MAX_ALLOWED=' + glib_version_def, '-D_GNU_SOURCE', - '-DG_LOG_DOMAIN="libfprint"', + '-DG_LOG_DOMAIN="@0@"'.format(meson.project_name()), ]) c_cflags = cc.get_supported_arguments([ - '-std=gnu99', '-Wimplicit-function-declaration', '-Wmissing-prototypes', '-Wnested-externs', @@ -75,11 +74,12 @@ soversion = 2 current = 0 revision = 0 libversion = '@0@.@1@.@2@'.format(soversion, current, revision) +versioned_libname = meson.project_name() + '-' + soversion.to_string() # Dependencies glib_dep = dependency('glib-2.0', version: '>=' + glib_min_version) gio_dep = dependency('gio-unix-2.0', version: '>=' + glib_min_version) -gusb_dep = dependency('gusb', version: '>= 0.3.0') +gusb_dep = dependency('gusb', version: '>= 0.2.0') mathlib_dep = cc.find_library('m', required: false) # The following dependencies are only used for tests @@ -208,10 +208,10 @@ subdir('tests') pkgconfig = import('pkgconfig') pkgconfig.generate( - name: meson.project_name(), + name: versioned_libname, description: 'Generic C API for fingerprint reader access', version: meson.project_version(), libraries: libfprint, - subdirs: meson.project_name(), - filebase: meson.project_name() + '@0@'.format(soversion), + subdirs: versioned_libname, + filebase: versioned_libname, ) diff --git a/tests/meson.build b/tests/meson.build index 4536dbf3..0a556a99 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -26,12 +26,35 @@ if get_option('introspection') envs.prepend('GI_TYPELIB_PATH', join_paths(meson.build_root(), 'libfprint')) if 'virtual_image' in drivers - test('virtual-image', - find_program('virtual-image.py'), - args: '--verbose', - env: envs, - depends: libfprint_typelib, - ) + python3 = find_program('python3') + unittest_inspector = find_program('unittest_inspector.py') + base_args = files('virtual-image.py') + suite = [] + + r = run_command(unittest_inspector, files('virtual-image.py')) + unit_tests = r.stdout().strip().split('\n') + + if r.returncode() == 0 and unit_tests.length() > 0 + suite += 'virtual-image' + else + unit_tests = ['virtual-image'] + 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-image', find_program('sh'), @@ -43,7 +66,7 @@ if get_option('introspection') driver_envs = envs driver_envs.set('FP_DRIVERS_WHITELIST', driver_test) - if driver_test in drivers + if driver_test in drivers and gusb_dep.version().version_compare('>= 0.3.0') test(driver_test, find_program('umockdev-test.py'), args: join_paths(meson.current_source_dir(), driver_test), diff --git a/tests/test-device-fake.c b/tests/test-device-fake.c index eaa1fa63..cd085374 100644 --- a/tests/test-device-fake.c +++ b/tests/test-device-fake.c @@ -35,9 +35,15 @@ fpi_device_fake_probe (FpDevice *device) FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device); FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + fake_dev->last_called_function = fpi_device_fake_probe; g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_PROBE); - fake_dev->last_called_function = fpi_device_fake_probe; + if (fake_dev->return_action_error) + { + fpi_device_action_error (device, fake_dev->ret_error); + return; + } + fpi_device_probe_complete (device, dev_class->id, dev_class->full_name, fake_dev->ret_error); } @@ -47,9 +53,15 @@ fpi_device_fake_open (FpDevice *device) { FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + fake_dev->last_called_function = fpi_device_fake_open; g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_OPEN); - fake_dev->last_called_function = fpi_device_fake_open; + if (fake_dev->return_action_error) + { + fpi_device_action_error (device, fake_dev->ret_error); + return; + } + fpi_device_open_complete (device, fake_dev->ret_error); } @@ -58,9 +70,15 @@ fpi_device_fake_close (FpDevice *device) { FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + fake_dev->last_called_function = fpi_device_fake_close; g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_CLOSE); - fake_dev->last_called_function = fpi_device_fake_close; + if (fake_dev->return_action_error) + { + fpi_device_action_error (device, fake_dev->ret_error); + return; + } + fpi_device_close_complete (device, fake_dev->ret_error); } @@ -70,13 +88,20 @@ fpi_device_fake_enroll (FpDevice *device) FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); FpPrint *print = fake_dev->ret_print; + fake_dev->last_called_function = fpi_device_fake_enroll; g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_ENROLL); + + if (fake_dev->return_action_error) + { + fpi_device_action_error (device, fake_dev->ret_error); + return; + } + fpi_device_get_enroll_data (device, (FpPrint **) &fake_dev->action_data); if (!print && !fake_dev->ret_error) fpi_device_get_enroll_data (device, &print); - fake_dev->last_called_function = fpi_device_fake_enroll; fpi_device_enroll_complete (device, print ? g_object_ref (print) : NULL, fake_dev->ret_error); @@ -88,15 +113,29 @@ fpi_device_fake_verify (FpDevice *device) FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); FpPrint *print = fake_dev->ret_print; + fake_dev->last_called_function = fpi_device_fake_verify; g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_VERIFY); + + if (fake_dev->return_action_error) + { + fpi_device_action_error (device, fake_dev->ret_error); + return; + } + fpi_device_get_verify_data (device, (FpPrint **) &fake_dev->action_data); if (!print && !fake_dev->ret_error) fpi_device_get_verify_data (device, &print); - fake_dev->last_called_function = fpi_device_fake_verify; - fpi_device_verify_complete (device, fake_dev->ret_result, print, - fake_dev->ret_error); + if (!fake_dev->ret_error || fake_dev->ret_error->domain == FP_DEVICE_RETRY) + { + fpi_device_verify_report (device, fake_dev->ret_result, print, fake_dev->ret_error); + fpi_device_verify_complete (device, NULL); + } + else + { + fpi_device_verify_complete (device, fake_dev->ret_error); + } } static void @@ -105,7 +144,15 @@ fpi_device_fake_identify (FpDevice *device) FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); FpPrint *match = fake_dev->ret_match; + fake_dev->last_called_function = fpi_device_fake_identify; g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_IDENTIFY); + + if (fake_dev->return_action_error) + { + fpi_device_action_error (device, fake_dev->ret_error); + return; + } + fpi_device_get_identify_data (device, (GPtrArray **) &fake_dev->action_data); if (!match && !fake_dev->ret_error) @@ -127,9 +174,15 @@ fpi_device_fake_identify (FpDevice *device) } } - fake_dev->last_called_function = fpi_device_fake_identify; - fpi_device_identify_complete (device, match, fake_dev->ret_print, - fake_dev->ret_error); + if (!fake_dev->ret_error || fake_dev->ret_error->domain == FP_DEVICE_RETRY) + { + fpi_device_identify_report (device, match, fake_dev->ret_print, fake_dev->ret_error); + fpi_device_identify_complete (device, NULL); + } + else + { + fpi_device_identify_complete (device, fake_dev->ret_error); + } } static void @@ -137,10 +190,16 @@ fpi_device_fake_capture (FpDevice *device) { FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); - g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_CAPTURE); - fpi_device_get_capture_data (device, (gboolean *) &fake_dev->action_data); - fake_dev->last_called_function = fpi_device_fake_capture; + g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_CAPTURE); + + if (fake_dev->return_action_error) + { + fpi_device_action_error (device, fake_dev->ret_error); + return; + } + + fpi_device_get_capture_data (device, (gboolean *) &fake_dev->action_data); fpi_device_capture_complete (device, fake_dev->ret_image, fake_dev->ret_error); } @@ -149,9 +208,15 @@ fpi_device_fake_list (FpDevice *device) { FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + fake_dev->last_called_function = fpi_device_fake_list; g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_LIST); - fake_dev->last_called_function = fpi_device_fake_list; + if (fake_dev->return_action_error) + { + fpi_device_action_error (device, fake_dev->ret_error); + return; + } + fpi_device_list_complete (device, fake_dev->ret_list, fake_dev->ret_error); } @@ -160,10 +225,16 @@ fpi_device_fake_delete (FpDevice *device) { FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); - g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_DELETE); - fpi_device_get_delete_data (device, (gpointer) & fake_dev->action_data); - fake_dev->last_called_function = fpi_device_fake_delete; + g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_DELETE); + + if (fake_dev->return_action_error) + { + fpi_device_action_error (device, fake_dev->ret_error); + return; + } + + fpi_device_get_delete_data (device, (gpointer) (&fake_dev->action_data)); fpi_device_delete_complete (device, fake_dev->ret_error); } @@ -172,9 +243,8 @@ fpi_device_fake_cancel (FpDevice *device) { FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); - g_assert_cmpuint (fpi_device_get_current_action (device), !=, FPI_DEVICE_ACTION_NONE); - fake_dev->last_called_function = fpi_device_fake_cancel; + g_assert_cmpuint (fpi_device_get_current_action (device), !=, FPI_DEVICE_ACTION_NONE); } static void diff --git a/tests/test-device-fake.h b/tests/test-device-fake.h index e8a09193..e828b55c 100644 --- a/tests/test-device-fake.h +++ b/tests/test-device-fake.h @@ -30,6 +30,7 @@ struct _FpiDeviceFake FpDevice parent; gpointer last_called_function; + gboolean return_action_error; GError *ret_error; FpPrint *ret_print; diff --git a/tests/test-fpi-device.c b/tests/test-fpi-device.c index 3fa800c9..0e66c2ef 100644 --- a/tests/test-fpi-device.c +++ b/tests/test-fpi-device.c @@ -1,6 +1,5 @@ /* - * Example fingerprint device prints listing and deletion - * Enrolls your right index finger and saves the print to disk + * Unit tests for the internal fingerprint drivers API * Copyright (C) 2019 Marco Trevisan * * This library is free software; you can redistribute it and/or @@ -23,6 +22,7 @@ #define FP_COMPONENT "device" #include "fpi-device.h" +#include "fpi-compat.h" #include "fpi-log.h" #include "test-device-fake.h" @@ -43,6 +43,14 @@ auto_close_fake_device_new (void) static void auto_close_fake_device_free (FpAutoCloseDevice *device) { + FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + + if (fake_dev->return_action_error) + { + fake_dev->return_action_error = FALSE; + fake_dev->ret_error = NULL; + } + if (fp_device_is_open (device)) g_assert_true (fp_device_close_sync (device, NULL, NULL)); @@ -56,13 +64,13 @@ static FpAutoResetClass default_fake_dev_class = {0}; static FpAutoResetClass * auto_reset_device_class (void) { - g_autoptr(GTypeClass) type_class = NULL; + g_autoptr(FpDeviceClass) type_class = NULL; FpDeviceClass *dev_class = g_type_class_peek_static (FPI_TYPE_DEVICE_FAKE); if (!dev_class) { type_class = g_type_class_ref (FPI_TYPE_DEVICE_FAKE); - dev_class = (FpDeviceClass *) type_class; + dev_class = type_class; g_assert_nonnull (dev_class); } @@ -90,16 +98,6 @@ on_device_notify (FpDevice *device, GParamSpec *spec, gpointer user_data) fake_dev->user_data = g_param_spec_ref (spec); } -static void -test_driver_action_error_vfunc (FpDevice *device) -{ - FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); - - fake_dev->last_called_function = test_driver_action_error_vfunc; - - fpi_device_action_error (device, fake_dev->user_data); -} - /* Tests */ static void @@ -240,7 +238,7 @@ test_driver_get_usb_device (void) g_autoptr(FpDevice) device = NULL; dev_class->type = FP_DEVICE_TYPE_USB; - device = g_object_new (FPI_TYPE_DEVICE_FAKE, "fpi-usb-device", NULL); + device = g_object_new (FPI_TYPE_DEVICE_FAKE, "fpi-usb-device", NULL, NULL); g_assert_null (fpi_device_get_usb_device (device)); g_clear_object (&device); @@ -322,6 +320,69 @@ test_driver_probe (void) g_assert_cmpstr (fp_device_get_name (device), ==, "Probed device name"); } +static void +fake_device_probe_error (FpDevice *device) +{ + FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device); + + g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_PROBE); + + fpi_device_probe_complete (device, dev_class->id, dev_class->full_name, + fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); +} + +static void +fake_device_probe_action_error (FpDevice *device) +{ + g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_PROBE); + + fpi_device_action_error (device, fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); +} + +static void +on_driver_probe_error_async (GObject *initable, GAsyncResult *res, gpointer user_data) +{ + g_autoptr(GError) error = NULL; + gboolean *out_done = user_data; + FpDevice *device; + + device = FP_DEVICE (g_async_initable_new_finish (G_ASYNC_INITABLE (initable), res, &error)); + g_assert_null (device); + + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_NOT_SUPPORTED); + *out_done = TRUE; +} + +static void +test_driver_probe_error (void) +{ + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + gboolean done = FALSE; + + dev_class->id = "Error device ID"; + dev_class->probe = fake_device_probe_error; + g_async_initable_new_async (FPI_TYPE_DEVICE_FAKE, G_PRIORITY_DEFAULT, NULL, + on_driver_probe_error_async, &done, NULL); + + while (!done) + g_main_context_iteration (NULL, TRUE); +} + +static void +test_driver_probe_action_error (void) +{ + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + gboolean done = FALSE; + + dev_class->id = "Error device ID"; + dev_class->probe = fake_device_probe_action_error; + g_async_initable_new_async (FPI_TYPE_DEVICE_FAKE, G_PRIORITY_DEFAULT, NULL, + on_driver_probe_error_async, &done, NULL); + + while (!done) + g_main_context_iteration (NULL, TRUE); +} + static void test_driver_open (void) { @@ -386,6 +447,7 @@ test_driver_close_error (void) g_assert (fake_dev->last_called_function == dev_class->close); g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); g_assert (error == g_steal_pointer (&fake_dev->ret_error)); + g_assert_true (fp_device_is_open (device)); } static void @@ -428,6 +490,59 @@ test_driver_enroll_error (void) g_assert_null (out_print); } +static void +test_driver_enroll_complete_simple (FpDevice *device) +{ + FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + + fake_dev->last_called_function = test_driver_enroll_complete_simple; + g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_ENROLL); + + fpi_device_enroll_complete (device, fake_dev->ret_print, fake_dev->ret_error); +} + +static void +test_driver_enroll_error_no_print (void) +{ + g_autoptr(GError) error = NULL; + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(FpPrint) out_print = NULL; + FpiDeviceFake *fake_dev; + + dev_class->enroll = test_driver_enroll_complete_simple; + device = auto_close_fake_device_new (); + fake_dev = FPI_DEVICE_FAKE (device); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Driver did not provide a valid print and failed to provide an error*"); + out_print = + fp_device_enroll_sync (device, fp_print_new (device), NULL, NULL, NULL, &error); + + g_test_assert_expected_messages (); + g_assert (fake_dev->last_called_function == dev_class->enroll); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); + g_assert_null (out_print); + g_clear_error (&error); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Driver passed an error but also provided a print, returning error*"); + + fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL); + fake_dev->ret_print = fp_print_new (device); + g_object_add_weak_pointer (G_OBJECT (fake_dev->ret_print), + (gpointer) (&fake_dev->ret_print)); + out_print = + fp_device_enroll_sync (device, fp_print_new (device), NULL, NULL, NULL, &error); + + g_test_assert_expected_messages (); + g_assert (fake_dev->last_called_function == dev_class->enroll); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); + g_assert_true (error == g_steal_pointer (&fake_dev->ret_error)); + g_assert_null (out_print); + g_assert_null (fake_dev->ret_print); +} + typedef struct { gint completed_stages; @@ -543,24 +658,82 @@ test_driver_enroll_progress (void) g_assert (fake_dev->last_called_function == test_driver_enroll_progress_vfunc); } +typedef struct +{ + gboolean called; + FpPrint *match; + FpPrint *print; + GError *error; +} MatchCbData; + +static void +test_driver_match_data_clear (MatchCbData *data) +{ + data->called = FALSE; + g_clear_object (&data->match); + g_clear_object (&data->print); + g_clear_error (&data->error); +} + +static void +test_driver_match_data_free (MatchCbData *data) +{ + test_driver_match_data_clear (data); + g_free (data); +} +G_DEFINE_AUTOPTR_CLEANUP_FUNC (MatchCbData, test_driver_match_data_free); + +static void +test_driver_match_cb (FpDevice *device, + FpPrint *match, + FpPrint *print, + gpointer user_data, + GError *error) +{ + MatchCbData *data = user_data; + + g_assert (data->called == FALSE); + data->called = TRUE; + if (match) + data->match = g_object_ref (match); + if (print) + data->print = g_object_ref (print); + if (error) + { + data->error = g_error_copy (error); + g_assert_null (match); + } + + if (match) + g_assert_no_error (error); +} + static void test_driver_verify (void) { g_autoptr(GError) error = NULL; g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new (); - g_autoptr(FpPrint) enrolled_print = fp_print_new (device); + g_autoptr(FpPrint) enrolled_print = g_object_ref_sink (fp_print_new (device)); + g_autoptr(FpPrint) out_print = NULL; + g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1); FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device); FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); - FpPrint *out_print = NULL; gboolean match; fake_dev->ret_result = FPI_MATCH_SUCCESS; - fp_device_verify_sync (device, enrolled_print, NULL, &match, &out_print, &error); + fp_device_verify_sync (device, enrolled_print, NULL, + test_driver_match_cb, match_data, + &match, &out_print, &error); g_assert (fake_dev->last_called_function == dev_class->verify); g_assert (fake_dev->action_data == enrolled_print); g_assert_no_error (error); + g_assert_true (match_data->called); + g_assert_nonnull (match_data->match); + g_assert_true (match_data->print == out_print); + g_assert_true (match_data->match == enrolled_print); + g_assert (out_print == enrolled_print); g_assert_true (match); } @@ -570,36 +743,79 @@ test_driver_verify_fail (void) { g_autoptr(GError) error = NULL; g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new (); - g_autoptr(FpPrint) enrolled_print = fp_print_new (device); + g_autoptr(FpPrint) enrolled_print = g_object_ref_sink (fp_print_new (device)); + g_autoptr(FpPrint) out_print = NULL; + g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1); FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device); FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); - FpPrint *out_print = NULL; gboolean match; fake_dev->ret_result = FPI_MATCH_FAIL; - fp_device_verify_sync (device, enrolled_print, NULL, &match, &out_print, &error); + fp_device_verify_sync (device, enrolled_print, NULL, + test_driver_match_cb, match_data, + &match, &out_print, &error); g_assert (fake_dev->last_called_function == dev_class->verify); g_assert_no_error (error); + g_assert_true (match_data->called); + g_assert_no_error (match_data->error); + g_assert_true (match_data->print == out_print); + g_assert_null (match_data->match); + g_assert (out_print == enrolled_print); g_assert_false (match); } +static void +test_driver_verify_retry (void) +{ + g_autoptr(GError) error = NULL; + g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new (); + g_autoptr(FpPrint) enrolled_print = g_object_ref_sink (fp_print_new (device)); + g_autoptr(FpPrint) out_print = NULL; + g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1); + FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device); + FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + gboolean match; + + fake_dev->ret_result = FPI_MATCH_ERROR; + fake_dev->ret_error = fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL); + fp_device_verify_sync (device, enrolled_print, NULL, + test_driver_match_cb, match_data, + &match, &out_print, &error); + + g_assert_true (match_data->called); + g_assert_null (match_data->match); + g_assert_error (match_data->error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_GENERAL); + + g_assert (fake_dev->last_called_function == dev_class->verify); + g_assert_error (error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_GENERAL); + g_assert (error == g_steal_pointer (&fake_dev->ret_error)); + g_assert_false (match); +} + static void test_driver_verify_error (void) { g_autoptr(GError) error = NULL; g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new (); - g_autoptr(FpPrint) enrolled_print = fp_print_new (device); + g_autoptr(FpPrint) enrolled_print = g_object_ref_sink (fp_print_new (device)); + g_autoptr(FpPrint) out_print = NULL; + g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1); FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device); FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); - FpPrint *out_print = NULL; gboolean match; fake_dev->ret_result = FPI_MATCH_ERROR; fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL); - fp_device_verify_sync (device, enrolled_print, NULL, &match, &out_print, &error); + fp_device_verify_sync (device, enrolled_print, NULL, + test_driver_match_cb, match_data, + &match, &out_print, &error); + + g_assert_false (match_data->called); + g_assert_null (match_data->match); + g_assert_no_error (match_data->error); g_assert (fake_dev->last_called_function == dev_class->verify); g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); @@ -607,6 +823,216 @@ test_driver_verify_error (void) g_assert_false (match); } +static void +fake_device_verify_immediate_complete (FpDevice *device) +{ + fpi_device_verify_complete (device, NULL); +} + +static void +test_driver_verify_not_reported (void) +{ + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(FpPrint) enrolled_print = NULL; + g_autoptr(GError) error = NULL; + + dev_class->verify = fake_device_verify_immediate_complete; + device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); + enrolled_print = g_object_ref_sink (fp_print_new (device)); + + g_assert_true (fp_device_open_sync (device, NULL, NULL)); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*reported successful verify complete*not report*result*"); + + fp_device_verify_sync (device, enrolled_print, NULL, + NULL, NULL, + NULL, NULL, &error); + + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); + + g_test_assert_expected_messages (); +} + +static void +fake_device_verify_complete_error (FpDevice *device) +{ + FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + GError *complete_error = fake_dev->user_data; + + fake_dev->last_called_function = fake_device_verify_complete_error; + + fpi_device_verify_report (device, fake_dev->ret_result, fake_dev->ret_print, fake_dev->ret_error); + fpi_device_verify_complete (device, complete_error); +} + +static void +test_driver_verify_report_no_callback (void) +{ + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1); + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(FpPrint) enrolled_print = NULL; + g_autoptr(FpPrint) print = NULL; + g_autoptr(GError) error = NULL; + FpiDeviceFake *fake_dev; + gboolean match; + + dev_class->verify = fake_device_verify_complete_error; + device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); + fake_dev = FPI_DEVICE_FAKE (device); + enrolled_print = g_object_ref_sink (fp_print_new (device)); + + g_assert_true (fp_device_open_sync (device, NULL, NULL)); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Driver reported a verify error that was not in the retry domain*"); + + fake_dev->ret_result = FPI_MATCH_ERROR; + fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED); + fp_device_verify_sync (device, enrolled_print, NULL, + test_driver_match_cb, match_data, + &match, &print, &error); + + g_test_assert_expected_messages (); + + g_assert_false (match_data->called); + g_assert_null (match_data->match); + g_assert_no_error (match_data->error); + + g_assert (fake_dev->last_called_function == dev_class->verify); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_NOT_SUPPORTED); + g_assert (error == g_steal_pointer (&fake_dev->ret_error)); + g_assert_false (match); +} + +static void +test_driver_verify_complete_retry (void) +{ + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1); + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(FpPrint) enrolled_print = NULL; + g_autoptr(FpPrint) print = NULL; + g_autoptr(GError) error = NULL; + FpiDeviceFake *fake_dev; + gboolean match; + + dev_class->verify = fake_device_verify_complete_error; + device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); + fake_dev = FPI_DEVICE_FAKE (device); + enrolled_print = g_object_ref_sink (fp_print_new (device)); + + g_assert_true (fp_device_open_sync (device, NULL, NULL)); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Driver reported an error code without setting match result to error*"); + + test_driver_match_data_clear (match_data); + fake_dev->ret_result = FPI_MATCH_FAIL; + fake_dev->ret_error = fpi_device_retry_new (FP_DEVICE_RETRY_TOO_SHORT); + fp_device_verify_sync (device, enrolled_print, NULL, test_driver_match_cb, + match_data, &match, &print, &error); + g_test_assert_expected_messages (); + + g_assert_true (error == g_steal_pointer (&fake_dev->ret_error)); + g_assert_error (error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_TOO_SHORT); + g_assert_false (match); + g_assert_true (match_data->called); + g_assert_error (match_data->error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_TOO_SHORT); + g_assert_null (print); + g_clear_error (&error); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Driver reported an error code without setting match result to error*"); + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Driver reported a retry error to fpi_device_verify_complete" + "*reporting general verification failure*"); + + test_driver_match_data_clear (match_data); + fake_dev->ret_result = FPI_MATCH_FAIL; + fake_dev->ret_error = fpi_device_retry_new (FP_DEVICE_RETRY_TOO_SHORT); + fake_dev->user_data = g_error_copy (fake_dev->ret_error); + fp_device_verify_sync (device, enrolled_print, NULL, test_driver_match_cb, + match_data, &match, &print, &error); + + g_test_assert_expected_messages (); + g_assert_true (error != g_steal_pointer (&fake_dev->ret_error)); + g_assert_true (error != g_steal_pointer (&fake_dev->user_data)); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); + g_assert_true (match_data->called); + g_assert_error (match_data->error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_TOO_SHORT); + g_assert_false (match); + g_assert_null (print); + g_clear_error (&error); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Driver reported a retry error to fpi_device_verify_complete" + "*reporting general verification failure*"); + + test_driver_match_data_clear (match_data); + fake_dev->ret_result = FPI_MATCH_ERROR; + fake_dev->ret_error = fpi_device_retry_new (FP_DEVICE_RETRY_TOO_SHORT); + fake_dev->user_data = g_error_copy (fake_dev->ret_error); + + fp_device_verify_sync (device, enrolled_print, NULL, test_driver_match_cb, + match_data, &match, &print, &error); + g_test_assert_expected_messages (); + + g_assert_true (error != g_steal_pointer (&fake_dev->ret_error)); + g_assert_true (error != g_steal_pointer (&fake_dev->user_data)); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); + g_assert_true (match_data->called); + g_assert_error (match_data->error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_TOO_SHORT); + g_assert_false (match); + g_assert_null (print); + g_clear_error (&error); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Driver reported an error without specifying a retry " + "code, assuming general retry error*"); + + test_driver_match_data_clear (match_data); + fake_dev->ret_result = FPI_MATCH_ERROR; + + fp_device_verify_sync (device, enrolled_print, NULL, test_driver_match_cb, + match_data, &match, &print, &error); + g_test_assert_expected_messages (); + + g_assert_true (error != g_steal_pointer (&fake_dev->ret_error)); + g_assert_true (error != g_steal_pointer (&fake_dev->user_data)); + g_assert_error (error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_GENERAL); + g_assert_true (match_data->called); + g_assert_error (match_data->error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_GENERAL); + g_assert_false (match); + g_assert_null (print); + g_clear_error (&error); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Driver reported a print together with an error*"); + + test_driver_match_data_clear (match_data); + fake_dev->ret_result = FPI_MATCH_ERROR; + fake_dev->ret_error = fpi_device_retry_new (FP_DEVICE_RETRY_TOO_SHORT); + fake_dev->ret_print = fp_print_new (device); + g_object_add_weak_pointer (G_OBJECT (fake_dev->ret_print), + (gpointer) (&fake_dev->ret_print)); + + fp_device_verify_sync (device, enrolled_print, NULL, test_driver_match_cb, + match_data, &match, &print, &error); + g_test_assert_expected_messages (); + + g_assert_error (error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_TOO_SHORT); + g_assert_true (error == g_steal_pointer (&fake_dev->ret_error)); + g_assert_true (match_data->called); + g_assert_error (match_data->error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_TOO_SHORT); + g_assert_null (fake_dev->ret_print); + g_assert_false (match); + g_assert_null (print); + g_clear_error (&error); +} + static void fake_device_stub_identify (FpDevice *device) { @@ -641,16 +1067,17 @@ test_driver_identify (void) { g_autoptr(GError) error = NULL; g_autoptr(FpPrint) print = NULL; + g_autoptr(FpPrint) matched_print = NULL; g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new (); g_autoptr(GPtrArray) prints = g_ptr_array_new_with_free_func (g_object_unref); + g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1); FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device); FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); - FpPrint *matched_print; FpPrint *expected_matched; unsigned int i; for (i = 0; i < 500; ++i) - g_ptr_array_add (prints, fp_print_new (device)); + g_ptr_array_add (prints, g_object_ref_sink (fp_print_new (device))); expected_matched = g_ptr_array_index (prints, g_random_int_range (0, 499)); fp_print_set_description (expected_matched, "fake-verified"); @@ -658,7 +1085,14 @@ test_driver_identify (void) g_assert_true (fp_device_supports_identify (device)); fake_dev->ret_print = fp_print_new (device); - fp_device_identify_sync (device, prints, NULL, &matched_print, &print, &error); + fp_device_identify_sync (device, prints, NULL, + test_driver_match_cb, match_data, + &matched_print, &print, &error); + + g_assert_true (match_data->called); + g_assert_nonnull (match_data->match); + g_assert_true (match_data->match == matched_print); + g_assert_true (match_data->print == print); g_assert (fake_dev->last_called_function == dev_class->identify); g_assert (fake_dev->action_data == prints); @@ -673,20 +1107,29 @@ test_driver_identify_fail (void) { g_autoptr(GError) error = NULL; g_autoptr(FpPrint) print = NULL; + g_autoptr(FpPrint) matched_print = NULL; g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new (); g_autoptr(GPtrArray) prints = g_ptr_array_new_with_free_func (g_object_unref); + g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1); FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device); FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); - FpPrint *matched_print; unsigned int i; for (i = 0; i < 500; ++i) - g_ptr_array_add (prints, fp_print_new (device)); + g_ptr_array_add (prints, g_object_ref_sink (fp_print_new (device))); g_assert_true (fp_device_supports_identify (device)); fake_dev->ret_print = fp_print_new (device); - fp_device_identify_sync (device, prints, NULL, &matched_print, &print, &error); + fp_device_identify_sync (device, prints, NULL, + test_driver_match_cb, match_data, + &matched_print, &print, &error); + + g_assert_true (match_data->called); + g_assert_null (match_data->match); + g_assert_no_error (match_data->error); + g_assert_true (match_data->match == matched_print); + g_assert_true (match_data->print == print); g_assert (fake_dev->last_called_function == dev_class->identify); g_assert_no_error (error); @@ -696,20 +1139,59 @@ test_driver_identify_fail (void) } static void -test_driver_identify_error (void) +test_driver_identify_retry (void) { g_autoptr(GError) error = NULL; g_autoptr(FpPrint) print = NULL; + g_autoptr(FpPrint) matched_print = NULL; g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new (); g_autoptr(GPtrArray) prints = g_ptr_array_new_with_free_func (g_object_unref); + g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1); FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device); FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); - FpPrint *matched_print; FpPrint *expected_matched; unsigned int i; for (i = 0; i < 500; ++i) - g_ptr_array_add (prints, fp_print_new (device)); + g_ptr_array_add (prints, g_object_ref_sink (fp_print_new (device))); + + expected_matched = g_ptr_array_index (prints, g_random_int_range (0, 499)); + fp_print_set_description (expected_matched, "fake-verified"); + + g_assert_true (fp_device_supports_identify (device)); + + fake_dev->ret_error = fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL); + fp_device_identify_sync (device, prints, NULL, + test_driver_match_cb, match_data, + &matched_print, &print, &error); + + g_assert_true (match_data->called); + g_assert_null (match_data->match); + g_assert_error (match_data->error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_GENERAL); + + g_assert (fake_dev->last_called_function == dev_class->identify); + g_assert_error (error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_GENERAL); + g_assert (error == g_steal_pointer (&fake_dev->ret_error)); + g_assert_null (matched_print); + g_assert_null (print); +} + +static void +test_driver_identify_error (void) +{ + g_autoptr(GError) error = NULL; + g_autoptr(FpPrint) print = NULL; + g_autoptr(FpPrint) matched_print = NULL; + g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new (); + g_autoptr(GPtrArray) prints = g_ptr_array_new_with_free_func (g_object_unref); + g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1); + FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device); + FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + FpPrint *expected_matched; + unsigned int i; + + for (i = 0; i < 500; ++i) + g_ptr_array_add (prints, g_object_ref_sink (fp_print_new (device))); expected_matched = g_ptr_array_index (prints, g_random_int_range (0, 499)); fp_print_set_description (expected_matched, "fake-verified"); @@ -717,7 +1199,13 @@ test_driver_identify_error (void) g_assert_true (fp_device_supports_identify (device)); fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL); - fp_device_identify_sync (device, prints, NULL, &matched_print, &print, &error); + fp_device_identify_sync (device, prints, NULL, + test_driver_match_cb, match_data, + &matched_print, &print, &error); + + g_assert_false (match_data->called); + g_assert_null (match_data->match); + g_assert_no_error (match_data->error); g_assert (fake_dev->last_called_function == dev_class->identify); g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); @@ -726,6 +1214,182 @@ test_driver_identify_error (void) g_assert_null (print); } +static void +fake_device_identify_immediate_complete (FpDevice *device) +{ + fpi_device_identify_complete (device, NULL); +} + +static void +test_driver_identify_not_reported (void) +{ + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(GPtrArray) prints = g_ptr_array_new_with_free_func (g_object_unref); + g_autoptr(GError) error = NULL; + unsigned int i; + + dev_class->identify = fake_device_identify_immediate_complete; + device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); + + for (i = 0; i < 500; ++i) + g_ptr_array_add (prints, g_object_ref_sink (fp_print_new (device))); + + g_assert_true (fp_device_open_sync (device, NULL, NULL)); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*reported successful identify complete*not report*result*"); + + fp_device_identify_sync (device, prints, NULL, + NULL, NULL, + NULL, NULL, &error); + + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); + + g_test_assert_expected_messages (); +} + +static void +fake_device_identify_complete_error (FpDevice *device) +{ + FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + GError *complete_error = fake_dev->user_data; + + fake_dev->last_called_function = fake_device_identify_complete_error; + + fpi_device_identify_report (device, fake_dev->ret_match, fake_dev->ret_print, fake_dev->ret_error); + fpi_device_identify_complete (device, complete_error); +} + +static void +test_driver_identify_complete_retry (void) +{ + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(GPtrArray) prints = g_ptr_array_new_with_free_func (g_object_unref); + g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1); + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(FpPrint) print = NULL; + g_autoptr(FpPrint) match = NULL; + g_autoptr(GError) error = NULL; + FpiDeviceFake *fake_dev; + int i; + + dev_class->identify = fake_device_identify_complete_error; + device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); + fake_dev = FPI_DEVICE_FAKE (device); + + for (i = 0; i < 500; ++i) + g_ptr_array_add (prints, g_object_ref_sink (fp_print_new (device))); + + g_assert_true (fp_device_open_sync (device, NULL, NULL)); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Driver reported a retry error to fpi_device_identify_complete" + "*reporting general identification failure*"); + + test_driver_match_data_clear (match_data); + fake_dev->ret_error = fpi_device_retry_new (FP_DEVICE_RETRY_TOO_SHORT); + fake_dev->user_data = g_error_copy (fake_dev->ret_error); + + fp_device_identify_sync (device, prints, NULL, test_driver_match_cb, match_data, + &match, &print, &error); + g_test_assert_expected_messages (); + + g_assert_true (error != g_steal_pointer (&fake_dev->ret_error)); + g_assert_true (error != g_steal_pointer (&fake_dev->user_data)); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); + g_assert_true (match_data->called); + g_assert_error (match_data->error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_TOO_SHORT); + g_assert_null (match); + g_assert_null (print); + g_clear_error (&error); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Driver reported a match to a print that was not in the gallery*"); + + test_driver_match_data_clear (match_data); + fake_dev->ret_match = fp_print_new (device); + g_object_add_weak_pointer (G_OBJECT (fake_dev->ret_match), + (gpointer) (&fake_dev->ret_match)); + fp_device_identify_sync (device, prints, NULL, test_driver_match_cb, match_data, + &match, &print, &error); + g_test_assert_expected_messages (); + + g_object_unref (fake_dev->ret_match); + g_assert_null (fake_dev->ret_match); + g_assert_true (match_data->called); + g_assert_no_error (match_data->error); + g_assert_no_error (error); + g_assert_false (match); + g_assert_null (print); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Driver reported an error code but also provided a match*"); + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Driver reported a print together with an error*"); + + test_driver_match_data_clear (match_data); + fake_dev->ret_error = fpi_device_retry_new (FP_DEVICE_RETRY_REMOVE_FINGER); + fake_dev->ret_match = prints->pdata[0]; + fake_dev->ret_print = fp_print_new (device); + g_object_add_weak_pointer (G_OBJECT (fake_dev->ret_print), + (gpointer) (&fake_dev->ret_print)); + fp_device_identify_sync (device, prints, NULL, test_driver_match_cb, match_data, + &match, &print, &error); + g_test_assert_expected_messages (); + + g_assert_error (error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_REMOVE_FINGER); + g_assert_true (error == g_steal_pointer (&fake_dev->ret_error)); + g_assert_null (fake_dev->ret_print); + g_assert_true (match_data->called); + g_assert_error (match_data->error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_REMOVE_FINGER); + g_assert_false (match); + g_assert_null (print); + g_clear_error (&error); +} + +static void +test_driver_identify_report_no_callback (void) +{ + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1); + g_autoptr(GPtrArray) prints = g_ptr_array_new_with_free_func (g_object_unref); + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(FpPrint) enrolled_print = NULL; + g_autoptr(FpPrint) print = NULL; + g_autoptr(FpPrint) match = NULL; + g_autoptr(GError) error = NULL; + FpiDeviceFake *fake_dev; + + dev_class->identify = fake_device_identify_complete_error; + device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); + fake_dev = FPI_DEVICE_FAKE (device); + enrolled_print = g_object_ref_sink (fp_print_new (device)); + + g_assert_true (fp_device_open_sync (device, NULL, NULL)); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Driver reported a verify error that was not in the retry domain*"); + + fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED); + fp_device_identify_sync (device, prints, NULL, + test_driver_match_cb, match_data, + &match, &print, &error); + + g_test_assert_expected_messages (); + + g_assert_null (match); + g_assert_null (print); + g_assert_false (match_data->called); + g_assert_null (match_data->match); + g_assert_no_error (match_data->error); + + g_assert (fake_dev->last_called_function == dev_class->identify); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_NOT_SUPPORTED); + g_assert (error == g_steal_pointer (&fake_dev->ret_error)); + g_assert_false (match); +} + static void fake_device_stub_capture (FpDevice *device) { @@ -863,6 +1527,24 @@ test_driver_list_error (void) g_assert_null (prints); } +static void +test_driver_list_no_storage (void) +{ + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(GPtrArray) prints = NULL; + g_autoptr(GError) error = NULL; + + dev_class->list = NULL; + + device = auto_close_fake_device_new (); + g_assert_false (fp_device_has_storage (device)); + + prints = fp_device_list_prints_sync (device, NULL, &error); + g_assert_null (prints); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_NOT_SUPPORTED); +} + static void test_driver_delete (void) { @@ -980,7 +1662,7 @@ test_driver_cancel_fail (void) g_cancellable_cancel (cancellable); while (g_main_context_iteration (NULL, FALSE)) - ; + continue; g_assert (fake_dev->last_called_function == dev_class->delete); g_assert_no_error (error); @@ -1166,12 +1848,12 @@ test_driver_complete_actions_errors (void) g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*assertion*current_action*failed"); - fpi_device_verify_complete (device, FPI_MATCH_FAIL, NULL, NULL); + fpi_device_verify_complete (device, NULL); g_test_assert_expected_messages (); g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*assertion*current_action*failed"); - fpi_device_identify_complete (device, NULL, NULL, NULL); + fpi_device_identify_complete (device, NULL); g_test_assert_expected_messages (); g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, @@ -1202,45 +1884,176 @@ test_driver_action_error_error (void) } static void -test_driver_action_error_open (void) +test_driver_action_error_all (void) { - g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); - g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(FpAutoCloseDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); + g_autoptr(FpPrint) enrolled_print = g_object_ref_sink (fp_print_new (device)); + g_autoptr(GPtrArray) prints = g_ptr_array_new_with_free_func (g_object_unref); g_autoptr(GError) error = NULL; - FpiDeviceFake *fake_dev; + FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device); + FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); - dev_class->open = test_driver_action_error_vfunc; - device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); fake_dev = FPI_DEVICE_FAKE (device); - fake_dev->user_data = fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID); + fake_dev->return_action_error = TRUE; + fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID); g_assert_false (fp_device_open_sync (device, NULL, &error)); + g_assert_true (fake_dev->last_called_function == dev_class->open); g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID); + g_clear_error (&error); - g_assert (fake_dev->last_called_function == test_driver_action_error_vfunc); + fake_dev->return_action_error = FALSE; + fake_dev->ret_error = NULL; + g_assert_true (fp_device_open_sync (device, NULL, NULL)); + + fake_dev->return_action_error = TRUE; + + fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID); + g_assert_false (fp_device_close_sync (device, NULL, &error)); + g_assert_true (fake_dev->last_called_function == dev_class->close); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID); + g_clear_error (&error); + + fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID); + g_assert_null (fp_device_enroll_sync (device, fp_print_new (device), NULL, + NULL, NULL, &error)); + g_assert_true (fake_dev->last_called_function == dev_class->enroll); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID); + g_clear_error (&error); + + fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID); + g_assert_false (fp_device_verify_sync (device, enrolled_print, NULL, + NULL, NULL, NULL, NULL, &error)); + g_assert_true (fake_dev->last_called_function == dev_class->verify); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID); + g_clear_error (&error); + + fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID); + g_assert_false (fp_device_identify_sync (device, prints, NULL, + NULL, NULL, NULL, NULL, &error)); + g_assert_true (fake_dev->last_called_function == dev_class->identify); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID); + g_clear_error (&error); + + fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID); + g_assert_null (fp_device_capture_sync (device, TRUE, NULL, &error)); + g_assert_true (fake_dev->last_called_function == dev_class->capture); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID); + g_clear_error (&error); + + fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID); + g_assert_null (fp_device_list_prints_sync (device, NULL, &error)); + g_assert_true (fake_dev->last_called_function == dev_class->list); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID); + g_clear_error (&error); + + fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID); + g_assert_false (fp_device_delete_print_sync (device, enrolled_print, NULL, &error)); + g_assert_true (fake_dev->last_called_function == dev_class->delete); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID); + g_clear_error (&error); } static void -test_driver_action_error_fallback_open (void) +test_driver_action_error_fallback_all (void) { - g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); - g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(FpAutoCloseDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); + g_autoptr(FpPrint) enrolled_print = g_object_ref_sink (fp_print_new (device)); + g_autoptr(GPtrArray) prints = g_ptr_array_new_with_free_func (g_object_unref); g_autoptr(GError) error = NULL; - FpiDeviceFake *fake_dev; + FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device); + FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); - dev_class->open = test_driver_action_error_vfunc; - device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); fake_dev = FPI_DEVICE_FAKE (device); + fake_dev->return_action_error = TRUE; g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*Device failed to pass an error to generic action " "error function*"); g_assert_false (fp_device_open_sync (device, NULL, &error)); - g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); - - g_assert (fake_dev->last_called_function == test_driver_action_error_vfunc); g_test_assert_expected_messages (); + g_assert_true (fake_dev->last_called_function == dev_class->open); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); + g_clear_error (&error); + + fake_dev->return_action_error = FALSE; + g_assert_true (fp_device_open_sync (device, NULL, NULL)); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Device failed to pass an error to generic action " + "error function*"); + + fake_dev->return_action_error = TRUE; + g_assert_false (fp_device_close_sync (device, NULL, &error)); + g_test_assert_expected_messages (); + g_assert_true (fake_dev->last_called_function == dev_class->close); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); + g_clear_error (&error); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Device failed to pass an error to generic action " + "error function*"); + + g_assert_null (fp_device_enroll_sync (device, fp_print_new (device), NULL, + NULL, NULL, &error)); + g_test_assert_expected_messages (); + g_test_assert_expected_messages (); + g_assert_true (fake_dev->last_called_function == dev_class->enroll); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); + g_clear_error (&error); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Device failed to pass an error to generic action " + "error function*"); + + g_assert_false (fp_device_verify_sync (device, enrolled_print, NULL, + NULL, NULL, NULL, NULL, &error)); + g_test_assert_expected_messages (); + g_assert_true (fake_dev->last_called_function == dev_class->verify); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); + g_clear_error (&error); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Device failed to pass an error to generic action " + "error function*"); + + g_assert_false (fp_device_identify_sync (device, prints, NULL, + NULL, NULL, NULL, NULL, &error)); + g_test_assert_expected_messages (); + g_assert_true (fake_dev->last_called_function == dev_class->identify); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); + g_clear_error (&error); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Device failed to pass an error to generic action " + "error function*"); + + g_assert_null (fp_device_capture_sync (device, TRUE, NULL, &error)); + g_test_assert_expected_messages (); + g_assert_true (fake_dev->last_called_function == dev_class->capture); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); + g_clear_error (&error); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Device failed to pass an error to generic action " + "error function*"); + + g_assert_null (fp_device_list_prints_sync (device, NULL, &error)); + g_test_assert_expected_messages (); + g_assert_true (fake_dev->last_called_function == dev_class->list); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); + g_clear_error (&error); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, + "*Device failed to pass an error to generic action " + "error function*"); + + g_assert_false (fp_device_delete_print_sync (device, enrolled_print, NULL, &error)); + g_test_assert_expected_messages (); + g_assert_true (fake_dev->last_called_function == dev_class->delete); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); + g_clear_error (&error); } static void @@ -1312,9 +2125,18 @@ test_driver_error_types (void) for (i = 0; g_enum_get_value (errors_enum, i); ++i) { - error = fpi_device_error_new (i); - g_assert_error (error, FP_DEVICE_ERROR, i); - g_clear_error (&error); + g_autoptr(GError) e = NULL; + g_autoptr(GError) msg_e = NULL; + g_autofree char *expected_msg = NULL; + g_autofree char *enum_string = g_enum_to_string (FP_TYPE_DEVICE_ERROR, i); + + e = fpi_device_error_new (i); + g_assert_error (e, FP_DEVICE_ERROR, i); + + expected_msg = g_strdup_printf ("Error message %s", enum_string); + msg_e = fpi_device_error_new_msg (i, "Error message %s", enum_string); + g_assert_error (msg_e, FP_DEVICE_ERROR, i); + g_assert_cmpstr (msg_e->message, ==, expected_msg); } g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*Unsupported error*"); @@ -1332,9 +2154,18 @@ test_driver_retry_error_types (void) for (i = 0; g_enum_get_value (errors_enum, i); ++i) { - error = fpi_device_retry_new (i); - g_assert_error (error, FP_DEVICE_RETRY, i); - g_clear_error (&error); + g_autoptr(GError) e = NULL; + g_autoptr(GError) msg_e = NULL; + g_autofree char *expected_msg = NULL; + g_autofree char *enum_string = g_enum_to_string (FP_TYPE_DEVICE_RETRY, i); + + e = fpi_device_retry_new (i); + g_assert_error (e, FP_DEVICE_RETRY, i); + + expected_msg = g_strdup_printf ("Retry error message %s", enum_string); + msg_e = fpi_device_retry_new_msg (i, "Retry error message %s", enum_string); + g_assert_error (msg_e, FP_DEVICE_RETRY, i); + g_assert_cmpstr (msg_e->message, ==, expected_msg); } g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*Unsupported error*"); @@ -1369,23 +2200,35 @@ main (int argc, char *argv[]) g_test_add_func ("/driver/get_driver_data", test_driver_get_driver_data); g_test_add_func ("/driver/probe", test_driver_probe); + g_test_add_func ("/driver/probe/error", test_driver_probe_error); + g_test_add_func ("/driver/probe/action_error", test_driver_probe_action_error); g_test_add_func ("/driver/open", test_driver_open); g_test_add_func ("/driver/open/error", test_driver_open_error); g_test_add_func ("/driver/close", test_driver_close); g_test_add_func ("/driver/close/error", test_driver_close_error); g_test_add_func ("/driver/enroll", test_driver_enroll); g_test_add_func ("/driver/enroll/error", test_driver_enroll_error); + g_test_add_func ("/driver/enroll/error/no_print", test_driver_enroll_error_no_print); g_test_add_func ("/driver/enroll/progress", test_driver_enroll_progress); g_test_add_func ("/driver/verify", test_driver_verify); g_test_add_func ("/driver/verify/fail", test_driver_verify_fail); + g_test_add_func ("/driver/verify/retry", test_driver_verify_retry); g_test_add_func ("/driver/verify/error", test_driver_verify_error); + g_test_add_func ("/driver/verify/report_no_cb", test_driver_verify_report_no_callback); + g_test_add_func ("/driver/verify/not_reported", test_driver_verify_not_reported); + g_test_add_func ("/driver/verify/complete_retry", test_driver_verify_complete_retry); g_test_add_func ("/driver/identify", test_driver_identify); g_test_add_func ("/driver/identify/fail", test_driver_identify_fail); + g_test_add_func ("/driver/identify/retry", test_driver_identify_retry); g_test_add_func ("/driver/identify/error", test_driver_identify_error); + g_test_add_func ("/driver/identify/not_reported", test_driver_identify_not_reported); + g_test_add_func ("/driver/identify/complete_retry", test_driver_identify_complete_retry); + g_test_add_func ("/driver/identify/report_no_cb", test_driver_identify_report_no_callback); g_test_add_func ("/driver/capture", test_driver_capture); g_test_add_func ("/driver/capture/error", test_driver_capture_error); g_test_add_func ("/driver/list", test_driver_list); g_test_add_func ("/driver/list/error", test_driver_list_error); + g_test_add_func ("/driver/list/no_storage", test_driver_list_no_storage); g_test_add_func ("/driver/delete", test_driver_delete); g_test_add_func ("/driver/delete/error", test_driver_delete_error); g_test_add_func ("/driver/cancel", test_driver_cancel); @@ -1400,8 +2243,8 @@ main (int argc, char *argv[]) g_test_add_func ("/driver/action_is_cancelled/error", test_driver_action_is_cancelled_error); g_test_add_func ("/driver/complete_action/all/error", test_driver_complete_actions_errors); g_test_add_func ("/driver/action_error/error", test_driver_action_error_error); - g_test_add_func ("/driver/action_error/open", test_driver_action_error_open); - g_test_add_func ("/driver/action_error/fail/open", test_driver_action_error_fallback_open); + g_test_add_func ("/driver/action_error/all", test_driver_action_error_all); + g_test_add_func ("/driver/action_error/fail", test_driver_action_error_fallback_all); g_test_add_func ("/driver/timeout", test_driver_add_timeout); g_test_add_func ("/driver/timeout/cancelled", test_driver_add_timeout_cancelled); diff --git a/tests/unittest_inspector.py b/tests/unittest_inspector.py new file mode 100755 index 00000000..0d5d3a6f --- /dev/null +++ b/tests/unittest_inspector.py @@ -0,0 +1,46 @@ +#! /usr/bin/env python3 +# Copyright © 2020, Canonical Ltd +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . +# Authors: +# Marco Trevisan + +import argparse +import importlib.util +import inspect +import os +import unittest + +def list_tests(module): + tests = [] + for name, obj in inspect.getmembers(module): + if inspect.isclass(obj) and issubclass(obj, unittest.TestCase): + cases = unittest.defaultTestLoader.getTestCaseNames(obj) + tests += [ (obj, '{}.{}'.format(name, t)) for t in cases ] + return tests + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('unittest_source', type=argparse.FileType('r')) + + args = parser.parse_args() + source_path = args.unittest_source.name + spec = importlib.util.spec_from_file_location( + os.path.basename(source_path), source_path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + for machine, human in list_tests(module): + print(human) diff --git a/tests/virtual-image.py b/tests/virtual-image.py index 11ec8ae8..b6cad95a 100755 --- a/tests/virtual-image.py +++ b/tests/virtual-image.py @@ -1,20 +1,24 @@ #!/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 +try: + 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 +except Exception as e: + print("Missing dependencies: %s" % str(e)) + sys.exit(77) # Re-run the test with the passed wrapper if set wrapper = os.getenv('LIBFPRINT_TEST_WRAPPER') @@ -97,15 +101,13 @@ class VirtualImage(unittest.TestCase): 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)) + def send_retry(self, retry_error=FPrint.DeviceRetry.TOO_SHORT, iterate=True): + self.con.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)) + def send_error(self, device_error=FPrint.DeviceError.GENERAL, iterate=True): + self.con.sendall(struct.pack('ii', -2, device_error)) while iterate and ctx.pending(): ctx.iteration(False) @@ -212,15 +214,16 @@ class VirtualImage(unittest.TestCase): done = False def verify_cb(dev, res): - match, fp = dev.verify_finish(res) - self._verify_match = match - self._verify_fp = fp + try: + self._verify_match, self._verify_fp = dev.verify_finish(res) + except gi.repository.GLib.Error as e: + self._verify_error = e fp_whorl = self.enroll_print('whorl') self._verify_match = None self._verify_fp = None - self.dev.verify(fp_whorl, None, verify_cb) + self.dev.verify(fp_whorl, callback=verify_cb) self.send_image('whorl') while self._verify_match is None: ctx.iteration(True) @@ -228,41 +231,79 @@ class VirtualImage(unittest.TestCase): self._verify_match = None self._verify_fp = None - self.dev.verify(fp_whorl, None, verify_cb) + 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) + # Test verify error cases + self._verify_fp = None + self._verify_error = None + self.dev.verify(fp_whorl, callback=verify_cb) + self.send_retry() + while self._verify_fp is None and self._verify_error is None: + ctx.iteration(True) + assert(self._verify_error is not None) + assert(self._verify_error.matches(FPrint.device_retry_quark(), FPrint.DeviceRetry.TOO_SHORT)) + + self._verify_fp = None + self._verify_error = None + self.dev.verify(fp_whorl, callback=verify_cb) + self.send_error() + while self._verify_fp is None and self._verify_error is None: + ctx.iteration(True) + assert(self._verify_error is not None) + print(self._verify_error) + assert(self._verify_error.matches(FPrint.device_error_quark(), FPrint.DeviceError.GENERAL)) + 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) + try: + self._identify_match, self._identify_fp = self.dev.identify_finish(res) + except gi.repository.GLib.Error as e: + print(e) + self._identify_error = e self._identify_fp = None - self.dev.identify([fp_whorl, fp_tented_arch], None, identify_cb) + 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], None, identify_cb) + 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) + # Test error cases + self._identify_fp = None + self._identify_error = None + self.dev.identify([fp_whorl, fp_tented_arch], callback=identify_cb) + self.send_retry() + while self._identify_fp is None and self._identify_error is None: + ctx.iteration(True) + assert(self._identify_error is not None) + assert(self._identify_error.matches(FPrint.device_retry_quark(), FPrint.DeviceRetry.TOO_SHORT)) + + self._identify_fp = None + self._identify_error = None + self.dev.identify([fp_whorl, fp_tented_arch], callback=identify_cb) + self.send_error() + while self._identify_fp is None and self._identify_error is None: + ctx.iteration(True) + assert(self._identify_error is not None) + assert(self._identify_error.matches(FPrint.device_error_quark(), FPrint.DeviceError.GENERAL)) + def test_verify_serialized(self): done = False @@ -290,7 +331,7 @@ class VirtualImage(unittest.TestCase): self._verify_match = None self._verify_fp = None - self.dev.verify(fp_whorl_new, None, verify_cb) + self.dev.verify(fp_whorl_new, callback=verify_cb) self.send_image('whorl') while self._verify_match is None: ctx.iteration(True) @@ -298,12 +339,12 @@ class VirtualImage(unittest.TestCase): self._verify_match = None self._verify_fp = None - self.dev.verify(fp_whorl_new, None, verify_cb) + 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)) - +if __name__ == '__main__': + # avoid writing to stderr + unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))