tests: Add device removal test
This tests all relevant scenarios of device removal, i.e.: * device is not open * device is currently closing * device is open and idle * device is currently opening * device is open and active The test ensures that in all scenarios the following holds true: * device "removed" signal is only emitted after the action completes * context "device-removed" signal is only emitted after the device has been closed Note that the "opening" case is special. Here we confirm that a success from "open" will not be overriden by a FP_DEVICE_ERROR_REMOVED error, in order to correctly signal that the internal device state is open and it needs to be closed.
This commit is contained in:
parent
8a6f1932f8
commit
1b5dd0057f
1 changed files with 296 additions and 0 deletions
|
@ -20,6 +20,7 @@
|
||||||
#include <libfprint/fprint.h>
|
#include <libfprint/fprint.h>
|
||||||
|
|
||||||
#include "test-utils.h"
|
#include "test-utils.h"
|
||||||
|
#include "fpi-device.h"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
test_context_new (void)
|
test_context_new (void)
|
||||||
|
@ -92,6 +93,296 @@ test_context_enumerates_new_devices (void)
|
||||||
fpt_teardown_virtual_device_environment ();
|
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
|
int
|
||||||
main (int argc, char *argv[])
|
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/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/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/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 ();
|
return g_test_run ();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue