5df14206d8
Add support for creating more virtual readers. Co-authored-by: Bastien Nocera <hadess@hadess.net>
402 lines
13 KiB
C
402 lines
13 KiB
C
/*
|
|
* FpContext Unit tests
|
|
* Copyright (C) 2019 Marco Trevisan <marco.trevisan@canonical.com>
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
#include <libfprint/fprint.h>
|
|
|
|
#include "test-utils.h"
|
|
#include "fpi-device.h"
|
|
|
|
static void
|
|
test_context_new (void)
|
|
{
|
|
g_autoptr(FpContext) context = fp_context_new ();
|
|
g_assert_true (FP_CONTEXT (context));
|
|
}
|
|
|
|
static void
|
|
test_context_has_no_devices (void)
|
|
{
|
|
g_autoptr(FpContext) context = NULL;
|
|
GPtrArray *devices;
|
|
|
|
context = fp_context_new ();
|
|
devices = fp_context_get_devices (context);
|
|
|
|
g_assert_nonnull (devices);
|
|
g_assert_cmpuint (devices->len, ==, 0);
|
|
}
|
|
|
|
static void
|
|
test_context_has_virtual_device (void)
|
|
{
|
|
g_autoptr(FpContext) context = NULL;
|
|
FpDevice *virtual_device = NULL;
|
|
GPtrArray *devices;
|
|
unsigned int i;
|
|
|
|
fpt_setup_virtual_device_environment (FPT_VIRTUAL_DEVICE_IMAGE);
|
|
|
|
context = fp_context_new ();
|
|
devices = fp_context_get_devices (context);
|
|
|
|
g_assert_nonnull (devices);
|
|
g_assert_cmpuint (devices->len, ==, 1);
|
|
|
|
for (i = 0; i < devices->len; ++i)
|
|
{
|
|
FpDevice *device = devices->pdata[i];
|
|
|
|
if (g_strcmp0 (fp_device_get_driver (device), "virtual_image") == 0)
|
|
{
|
|
virtual_device = device;
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_assert_true (FP_IS_DEVICE (virtual_device));
|
|
|
|
fpt_teardown_virtual_device_environment ();
|
|
}
|
|
|
|
static void
|
|
test_context_enumerates_new_devices (void)
|
|
{
|
|
g_autoptr(FpContext) context = NULL;
|
|
GPtrArray *devices;
|
|
|
|
context = fp_context_new ();
|
|
|
|
fpt_setup_virtual_device_environment (FPT_VIRTUAL_DEVICE_IMAGE);
|
|
|
|
fp_context_enumerate (context);
|
|
devices = fp_context_get_devices (context);
|
|
|
|
g_assert_nonnull (devices);
|
|
g_assert_cmpuint (devices->len, ==, 1);
|
|
|
|
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_device (FPT_VIRTUAL_DEVICE_IMAGE);
|
|
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_device (FPT_VIRTUAL_DEVICE_IMAGE);
|
|
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_device (FPT_VIRTUAL_DEVICE_IMAGE);
|
|
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_device (FPT_VIRTUAL_DEVICE_IMAGE);
|
|
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* despite 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_device (FPT_VIRTUAL_DEVICE_IMAGE);
|
|
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[])
|
|
{
|
|
g_test_init (&argc, &argv, NULL);
|
|
|
|
g_test_add_func ("/context/new", test_context_new);
|
|
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 ();
|
|
}
|