diff --git a/tests/test-fp-context.c b/tests/test-fp-context.c index 01516b9..1dd1f72 100644 --- a/tests/test-fp-context.c +++ b/tests/test-fp-context.c @@ -20,6 +20,7 @@ #include #include "test-utils.h" +#include "fpi-device.h" static void test_context_new (void) @@ -92,6 +93,296 @@ test_context_enumerates_new_devices (void) fpt_teardown_virtual_device_environment (); } +#define DEV_REMOVED_CB 1 +#define CTX_DEVICE_REMOVED_CB 2 + +static void +device_removed_cb (FpDevice *device, FptContext *tctx) +{ + g_assert_nonnull (device); + g_assert_true (device == tctx->device); + + g_assert_null (tctx->user_data); + tctx->user_data = GINT_TO_POINTER (DEV_REMOVED_CB); +} + +static void +context_device_removed_cb (FpContext *ctx, FpDevice *device, FptContext *tctx) +{ + g_assert_nonnull (device); + g_assert_true (device == tctx->device); + + /* "device-removed" on context is always after "removed" on device */ + g_assert_cmpint (GPOINTER_TO_INT (tctx->user_data), ==, DEV_REMOVED_CB); + tctx->user_data = GINT_TO_POINTER (CTX_DEVICE_REMOVED_CB); +} + +static void +test_context_remove_device_closed (void) +{ + g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev (); + gboolean removed; + + tctx->user_data = NULL; + g_signal_connect (tctx->device, "removed", (GCallback) device_removed_cb, tctx); + g_signal_connect (tctx->fp_context, "device-removed", (GCallback) context_device_removed_cb, tctx); + + /* Triggering remove on closed device. */ + fpi_device_remove (tctx->device); + + g_assert_nonnull (tctx->device); + g_object_get (tctx->device, "removed", &removed, NULL); + g_assert_true (removed); + g_assert_cmpint (GPOINTER_TO_INT (tctx->user_data), ==, DEV_REMOVED_CB); + + /* device-removed is dispatched from idle. */ + while (g_main_context_iteration (NULL, FALSE)) + { + } + + /* The device is now destroyed and device-removed was called. */ + g_assert_null (tctx->device); + g_assert_cmpint (GPOINTER_TO_INT (tctx->user_data), ==, CTX_DEVICE_REMOVED_CB); + + fpt_teardown_virtual_device_environment (); +} + +static void +close_done_cb (GObject *device, GAsyncResult *res, gpointer user_data) +{ + g_autoptr(FpPrint) print = NULL; + GError **error = user_data; + + g_assert_nonnull (error); + g_assert_false (fp_device_close_finish (FP_DEVICE (device), res, error)); + g_assert_null (print); + g_assert_nonnull (*error); +} + +static void +test_context_remove_device_closing (void) +{ + g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev (); + g_autoptr(GError) close_error = NULL; + g_autoptr(GError) error = NULL; + gboolean removed; + + tctx->user_data = NULL; + g_signal_connect (tctx->device, "removed", (GCallback) device_removed_cb, tctx); + g_signal_connect (tctx->fp_context, "device-removed", (GCallback) context_device_removed_cb, tctx); + + fp_device_open_sync (tctx->device, NULL, &error); + g_assert_no_error (error); + + /* Triggering remove on device that is being closed. */ + fp_device_close (tctx->device, NULL, close_done_cb, &close_error); + fpi_device_remove (tctx->device); + + /* Removed but not yet notified*/ + g_assert_nonnull (tctx->device); + g_object_get (tctx->device, "removed", &removed, NULL); + g_assert_true (removed); + g_assert_null (tctx->user_data); + + /* Running the mainloop now will cause the close to fail eventually. */ + while (!close_error) + g_main_context_iteration (NULL, TRUE); + g_assert_error (close_error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_REMOVED); + + /* Now the removed callback has been called already. */ + g_assert_nonnull (tctx->device); + g_assert_cmpint (GPOINTER_TO_INT (tctx->user_data), ==, DEV_REMOVED_CB); + + /* While device-removed needs another idle iteration. */ + while (g_main_context_iteration (NULL, FALSE)) + { + } + + g_assert_null (tctx->device); + g_assert_cmpint (GPOINTER_TO_INT (tctx->user_data), ==, CTX_DEVICE_REMOVED_CB); + + fpt_teardown_virtual_device_environment (); +} + +static void +test_context_remove_device_open (void) +{ + g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev (); + g_autoptr(GError) error = NULL; + gboolean removed = FALSE; + + tctx->user_data = NULL; + g_signal_connect (tctx->fp_context, "device-removed", (GCallback) context_device_removed_cb, tctx); + g_signal_connect (tctx->device, "removed", (GCallback) device_removed_cb, tctx); + + fp_device_open_sync (tctx->device, NULL, &error); + g_assert_no_error (error); + + /* Triggering remove on open device. */ + fpi_device_remove (tctx->device); + + g_assert_nonnull (tctx->device); + g_object_get (tctx->device, "removed", &removed, NULL); + g_assert_true (removed); + g_assert_cmpint (GPOINTER_TO_INT (tctx->user_data), ==, DEV_REMOVED_CB); + + /* At this point, the "removed" cb on the device should have been called! + * Iterating the mainloop will not change anything. + */ + while (g_main_context_iteration (NULL, FALSE)) + { + } + g_assert_cmpint (GPOINTER_TO_INT (tctx->user_data), ==, DEV_REMOVED_CB); + + /* On close, the device will be removed from the context, + * but only a main loop iteration later. */ + fp_device_close_sync (tctx->device, NULL, &error); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_REMOVED); + g_assert_nonnull (tctx->device); + g_assert_cmpint (GPOINTER_TO_INT (tctx->user_data), ==, DEV_REMOVED_CB); + + while (g_main_context_iteration (NULL, FALSE)) + { + } + g_assert_null (tctx->device); + g_assert_cmpint (GPOINTER_TO_INT (tctx->user_data), ==, CTX_DEVICE_REMOVED_CB); + + fpt_teardown_virtual_device_environment (); +} + +static void +open_done_cb (GObject *device, GAsyncResult *res, gpointer user_data) +{ + g_autoptr(GError) error = NULL; + g_autoptr(FpPrint) print = NULL; + gboolean *data = user_data; + + g_assert_true (fp_device_open_finish (FP_DEVICE (device), res, &error)); + g_assert_null (print); + g_assert_null (error); + + *data = TRUE; +} + +static void +test_context_remove_device_opening (void) +{ + g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev (); + g_autoptr(GError) close_error = NULL; + gboolean open_done = FALSE; + gboolean removed; + + tctx->user_data = NULL; + g_signal_connect (tctx->device, "removed", (GCallback) device_removed_cb, tctx); + g_signal_connect (tctx->fp_context, "device-removed", (GCallback) context_device_removed_cb, tctx); + + fp_device_open (tctx->device, NULL, open_done_cb, &open_done); + g_assert_false (open_done); + + fpi_device_remove (tctx->device); + + /* Removed but not yet notified*/ + g_assert_nonnull (tctx->device); + g_object_get (tctx->device, "removed", &removed, NULL); + g_assert_true (removed); + g_assert_null (tctx->user_data); + + /* Running the mainloop now will cause the open to *succeed* dispite removal! */ + while (!open_done) + g_main_context_iteration (NULL, TRUE); + + /* Now the removed callback has been called already. */ + g_assert_nonnull (tctx->device); + g_assert_cmpint (GPOINTER_TO_INT (tctx->user_data), ==, DEV_REMOVED_CB); + + fp_device_close_sync (tctx->device, NULL, &close_error); + g_assert_error (close_error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_REMOVED); + + g_assert_nonnull (tctx->device); + g_assert_cmpint (GPOINTER_TO_INT (tctx->user_data), ==, DEV_REMOVED_CB); + + /* The device-removed signal needs an idle iteration. */ + while (g_main_context_iteration (NULL, FALSE)) + { + } + + g_assert_null (tctx->device); + g_assert_cmpint (GPOINTER_TO_INT (tctx->user_data), ==, CTX_DEVICE_REMOVED_CB); + + fpt_teardown_virtual_device_environment (); +} + +static void +enroll_done_cb (GObject *device, GAsyncResult *res, gpointer user_data) +{ + g_autoptr(FpPrint) print = NULL; + GError **error = user_data; + + g_assert_nonnull (error); + print = fp_device_enroll_finish (FP_DEVICE (device), res, error); + g_assert_null (print); + g_assert_nonnull (*error); +} + +static void +test_context_remove_device_active (void) +{ + g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev (); + g_autoptr(GError) error = NULL; + g_autoptr(GCancellable) cancellable = NULL; + g_autoptr(GError) enroll_error = NULL; + FpPrint *template; + gboolean removed = FALSE; + + tctx->user_data = NULL; + g_signal_connect (tctx->fp_context, "device-removed", (GCallback) context_device_removed_cb, tctx); + g_signal_connect (tctx->device, "removed", (GCallback) device_removed_cb, tctx); + + fp_device_open_sync (tctx->device, NULL, &error); + g_assert_no_error (error); + + /* Start an enroll that we can cancel/fail later. + * NOTE: We need to cancel explicitly as remove() does not trigger a failure! + */ + template = fp_print_new (tctx->device); + cancellable = g_cancellable_new (); + fp_device_enroll (tctx->device, template, cancellable, NULL, NULL, NULL, enroll_done_cb, &enroll_error); + + /* Triggering remove on active device. */ + fpi_device_remove (tctx->device); + + /* The removed property has changed, but the cb has *not* been called yet. */ + g_assert_nonnull (tctx->device); + g_object_get (tctx->device, "removed", &removed, NULL); + g_assert_true (removed); + g_assert_null (tctx->user_data); + + /* Running the mainloop now will cause the operation to fail eventually. */ + while (!enroll_error) + g_main_context_iteration (NULL, TRUE); + + /* The virtual image device throws an PROTO error internally, + * but we should still receive a REMOVED error here. */ + g_assert_error (enroll_error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_REMOVED); + g_assert_cmpint (GPOINTER_TO_INT (tctx->user_data), ==, DEV_REMOVED_CB); + + /* Now we close the device, state remains unchanged mostly. */ + fp_device_close_sync (tctx->device, NULL, &error); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_REMOVED); + g_assert_nonnull (tctx->device); + g_assert_cmpint (GPOINTER_TO_INT (tctx->user_data), ==, DEV_REMOVED_CB); + + /* And "device-removed" is called */ + while (g_main_context_iteration (NULL, FALSE)) + { + } + + g_assert_null (tctx->device); + g_assert_cmpint (GPOINTER_TO_INT (tctx->user_data), ==, CTX_DEVICE_REMOVED_CB); + + fpt_teardown_virtual_device_environment (); +} + int main (int argc, char *argv[]) { @@ -101,6 +392,11 @@ main (int argc, char *argv[]) g_test_add_func ("/context/no-devices", test_context_has_no_devices); g_test_add_func ("/context/has-virtual-device", test_context_has_virtual_device); g_test_add_func ("/context/enumerates-new-devices", test_context_enumerates_new_devices); + g_test_add_func ("/context/remove-device-closed", test_context_remove_device_closed); + g_test_add_func ("/context/remove-device-closing", test_context_remove_device_closing); + g_test_add_func ("/context/remove-device-open", test_context_remove_device_open); + g_test_add_func ("/context/remove-device-opening", test_context_remove_device_opening); + g_test_add_func ("/context/remove-device-active", test_context_remove_device_active); return g_test_run (); }