/*
 * Unit tests for the internal fingerprint drivers API
 * 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 "fp-device.h"
#include "fp-enums.h"
#include <libfprint/fprint.h>

#define FP_COMPONENT "device"

#include "fpi-device.h"
#include "fpi-compat.h"
#include "fpi-log.h"
#include "test-device-fake.h"
#include "fp-print-private.h"

#ifdef TEST_TOD_DRIVER

#include "test-utils-tod.h"

#undef FPI_TYPE_DEVICE_FAKE
#define FPI_TYPE_DEVICE_FAKE (fpt_context_device_driver_get_type ())

#undef FPI_DEVICE_FAKE
#define FPI_DEVICE_FAKE(dev) (G_TYPE_CHECK_INSTANCE_CAST ((dev), FPI_TYPE_DEVICE_FAKE, FpiDeviceFake))

static GType
fpt_context_device_driver_get_type (void)
{
  FptContext *tctx = fpt_context_fake_dev_default ();

  return G_TYPE_FROM_CLASS (FP_DEVICE_GET_CLASS (tctx->device));
}

static int
tod_get_version (FpDeviceClass *device_class,
                 const char   **sub_version)
{
  g_autofree char *tod_version = NULL;
  const char *tod_version_info;
  const char *tod_subversion_info;

  g_debug ("Getting TOD version for driver '%s'", device_class->id);
  g_assert_true (g_str_has_prefix (device_class->id, "fake_test_dev_tod"));

  tod_version_info = device_class->id + sizeof ("fake_test_dev_tod");
  g_debug ("Tod version info is '%s'", tod_version_info);
  g_assert (*tod_version_info != '\0');

  if (sub_version)
    *sub_version = NULL;

  if (g_str_equal (tod_version_info, "current"))
    {
      *sub_version = TOD_CURRENT_SUBVERSION;
      return TOD_CURRENT_VERSION;
    }

  g_assert_true (g_str_has_prefix (device_class->id, "fake_test_dev_tod_v"));

  tod_version_info = device_class->id + sizeof ("fake_test_dev_tod_v") - 1;
  tod_subversion_info = strchr (tod_version_info, '+');
  g_assert_nonnull (tod_subversion_info);
  g_assert (*tod_subversion_info != '\0');

  tod_version = g_strndup (tod_version_info,
                           tod_subversion_info - tod_version_info);
  tod_subversion_info += 1;

  g_debug ("Tod version is '%s', subversion '%s'",
           tod_version, tod_subversion_info);

  g_assert_nonnull (tod_version);
  g_assert (*tod_version != '\0');
  g_assert (*tod_subversion_info != '\0');

  if (sub_version)
    *sub_version = tod_subversion_info;

  return atoi (tod_version);
}

#endif

static gboolean
tod_check_version (FpDeviceClass *device_class,
                   int            tod_version,
                   const char    *tod_subversion)
{
#ifdef TEST_TOD_DRIVER
  g_auto(GStrv) versions = NULL;
  g_auto(GStrv) wanted_versions = NULL;
  int version;
  const char *sub_version;

  version = tod_get_version (device_class, &sub_version);

  if (version != tod_version)
    return FALSE;

  if (!tod_subversion)
    return TRUE;

  versions = g_strsplit (sub_version, ".", -1);
  g_assert_cmpuint (g_strv_length (versions), ==, 3);

  wanted_versions = g_strsplit (tod_subversion, ".", -1);
  g_assert_cmpuint (g_strv_length (wanted_versions), ==, 3);

  if (atoi (versions[0]) >= atoi (wanted_versions[0]) &&
      atoi (versions[1]) > atoi (wanted_versions[1]))
    return TRUE;

  return atoi (versions[0]) == atoi (wanted_versions[0]) &&
         atoi (versions[1]) == atoi (wanted_versions[1]) &&
         atoi (versions[2]) >= atoi (wanted_versions[2]);
#endif
  return TRUE;
}

static gboolean
tod_check_device_version (FpDevice   *device_class,
                          int         tod_version,
                          const char *tod_subversion)
{
  return tod_check_version (FP_DEVICE_GET_CLASS (device_class),
                            tod_version,
                            tod_subversion);
}

/* gcc 12.0.1 is complaining about dangling pointers in the auto_close* functions */
#if G_GNUC_CHECK_VERSION (12, 0)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdangling-pointer"
#endif

/* Utility functions */

typedef FpDevice FpAutoCloseDevice;

/* gcc 12.0.1 is complaining about dangling pointers in the auto_close* functions */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdangling-pointer"

static FpAutoCloseDevice *
auto_close_fake_device_new (void)
{
  g_autoptr(GError) error = NULL;
  FpAutoCloseDevice *device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);

  if (!fp_device_open_sync (device, NULL, &error))
    g_error ("Could not open device: %s", error->message);

  return device;
}

static void
auto_close_fake_device_free (FpAutoCloseDevice *device)
{
  g_autoptr(GError) error = NULL;
  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))
    if (!fp_device_close_sync (device, NULL, &error))
      g_error ("Could not close device: %s", error->message);

  g_object_unref (device);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (FpAutoCloseDevice, auto_close_fake_device_free)

#if G_GNUC_CHECK_VERSION (12, 0)
#pragma GCC diagnostic pop
#endif

typedef FpDeviceClass FpAutoResetClass;
static FpAutoResetClass default_fake_dev_class = {0};

static FpAutoResetClass *
auto_reset_device_class (void)
{
  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 = type_class;
      g_assert_nonnull (dev_class);
    }

  default_fake_dev_class = *dev_class;

  return dev_class;
}

static void
auto_reset_device_class_cleanup (FpAutoResetClass *dev_class)
{
  *dev_class = default_fake_dev_class;

  g_assert_cmpint (memcmp (dev_class, &default_fake_dev_class,
                           sizeof (FpAutoResetClass)), ==, 0);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (FpAutoResetClass, auto_reset_device_class_cleanup)


static void
assert_equal_galleries (GPtrArray *g1,
                        GPtrArray *g2)
{
  unsigned i;

  g_assert ((g1 && g2) || (!g1 || !g1));

  if (g1 == g2)
    return;

  g_assert_cmpuint (g1->len, ==, g2->len);

  for (i = 0; i < g1->len; i++)
    {
      FpPrint *print = g_ptr_array_index (g1, i);

      g_assert_true (g_ptr_array_find_with_equal_func (g2, print, (GEqualFunc)
                                                       fp_print_equal, NULL));
    }
}

static void
on_device_notify (FpDevice *device, GParamSpec *spec, gpointer user_data)
{
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  fake_dev->last_called_function = on_device_notify;
  fake_dev->user_data = g_param_spec_ref (spec);
}

static FpPrint *
make_fake_print (FpDevice *device,
                 GVariant *print_data)
{
  FpPrint *enrolled_print = fp_print_new (device);

  fpi_print_set_type (enrolled_print, FPI_PRINT_RAW);

  if (!print_data)
    print_data = g_variant_new_string ("Test print private data");
  g_object_set (G_OBJECT (enrolled_print), "fpi-data", print_data, NULL);

  return enrolled_print;
}

static FpPrint *
make_fake_nbis_print (FpDevice *device)
{
  FpPrint *enrolled_print = fp_print_new (device);

  fpi_print_set_type (enrolled_print, FPI_PRINT_NBIS);

  return enrolled_print;
}

static FpPrint *
make_fake_print_reffed (FpDevice *device,
                        GVariant *print_data)
{
  return g_object_ref_sink (make_fake_print (device, print_data));
}

static GPtrArray *
make_fake_prints_gallery (FpDevice *device,
                          size_t    size)
{
  GPtrArray *array;
  size_t i;

  array = g_ptr_array_new_full (size, g_object_unref);

  for (i = 0; i < size; i++)
    g_ptr_array_add (array, make_fake_print_reffed (device, g_variant_new_uint64 (i)));

  return array;
}

/* Tests */

static void
test_driver_get_driver (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpDevice) device = NULL;

  dev_class->id = "test-fpi-device-driver";
  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);

  g_assert_cmpstr (fp_device_get_driver (device), ==, "test-fpi-device-driver");
}

static void
test_driver_get_device_id (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);

  g_assert_cmpstr (fp_device_get_device_id (device), ==, "0");
}

static void
test_driver_get_name (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpDevice) device = NULL;

  dev_class->full_name = "Test Device Full Name!";
  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);

  g_assert_cmpstr (fp_device_get_name (device), ==, "Test Device Full Name!");
}

static void
test_driver_is_open (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);

  g_assert_false (fp_device_is_open (device));
  fp_device_open_sync (device, NULL, NULL);
  g_assert_true (fp_device_is_open (device));
  fp_device_close_sync (FP_DEVICE (device), NULL, NULL);
  g_assert_false (fp_device_is_open (device));
}

static void
test_driver_get_scan_type_press (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpDevice) device = NULL;

  dev_class->scan_type = FP_SCAN_TYPE_PRESS;
  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  g_assert_cmpuint (fp_device_get_scan_type (device), ==, FP_SCAN_TYPE_PRESS);
}

static void
test_driver_get_scan_type_swipe (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpDevice) device = NULL;

  dev_class->scan_type = FP_SCAN_TYPE_SWIPE;
  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  g_assert_cmpuint (fp_device_get_scan_type (device), ==, FP_SCAN_TYPE_SWIPE);
}

static void
test_driver_set_scan_type_press (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  g_autoptr(GParamSpec) pspec = NULL;
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  g_signal_connect (device, "notify::scan-type", G_CALLBACK (on_device_notify), NULL);

  fpi_device_set_scan_type (device, FP_SCAN_TYPE_PRESS);
  g_assert_cmpuint (fp_device_get_scan_type (device), ==, FP_SCAN_TYPE_PRESS);
  g_assert (fake_dev->last_called_function == on_device_notify);

  pspec = g_steal_pointer (&fake_dev->user_data);
  g_assert_cmpstr (pspec->name, ==, "scan-type");
}

static void
test_driver_set_scan_type_swipe (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  g_autoptr(GParamSpec) pspec = NULL;
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  g_signal_connect (device, "notify::scan-type", G_CALLBACK (on_device_notify), NULL);

  fpi_device_set_scan_type (device, FP_SCAN_TYPE_SWIPE);
  g_assert_cmpuint (fp_device_get_scan_type (device), ==, FP_SCAN_TYPE_SWIPE);
  g_assert (fake_dev->last_called_function == on_device_notify);

  pspec = g_steal_pointer (&fake_dev->user_data);
  g_assert_cmpstr (pspec->name, ==, "scan-type");
}

static void
test_driver_finger_status_inactive (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
  FpFingerStatusFlags finger_status;

  g_signal_connect (device, "notify::finger-status", G_CALLBACK (on_device_notify), NULL);

  g_assert_false (fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE));
  g_assert_cmpuint (fp_device_get_finger_status (device), ==, FP_FINGER_STATUS_NONE);

  g_object_get (fake_dev, "finger-status", &finger_status, NULL);
  g_assert_cmpuint (finger_status, ==, FP_FINGER_STATUS_NONE);

  g_assert (fake_dev->last_called_function != on_device_notify);
  g_assert_null (g_steal_pointer (&fake_dev->user_data));
}

static void
test_driver_finger_status_needed (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  g_autoptr(GParamSpec) pspec = NULL;
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
  FpFingerStatusFlags finger_status;

  g_signal_connect (device, "notify::finger-status", G_CALLBACK (on_device_notify), NULL);

  g_assert_true (fpi_device_report_finger_status (device, FP_FINGER_STATUS_NEEDED));
  g_assert_cmpuint (fp_device_get_finger_status (device), ==, FP_FINGER_STATUS_NEEDED);

  g_object_get (fake_dev, "finger-status", &finger_status, NULL);
  g_assert_cmpuint (finger_status, ==, FP_FINGER_STATUS_NEEDED);

  g_assert (fake_dev->last_called_function == on_device_notify);
  pspec = g_steal_pointer (&fake_dev->user_data);
  g_assert_cmpstr (pspec->name, ==, "finger-status");

  fake_dev->last_called_function = NULL;
  g_assert_false (fpi_device_report_finger_status (device, FP_FINGER_STATUS_NEEDED));
  g_assert_null (fake_dev->last_called_function);
  g_assert_null (g_steal_pointer (&fake_dev->user_data));
}

static void
test_driver_finger_status_present (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  g_autoptr(GParamSpec) pspec = NULL;
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
  FpFingerStatusFlags finger_status;

  g_signal_connect (device, "notify::finger-status", G_CALLBACK (on_device_notify), NULL);

  g_assert_true (fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT));
  g_assert_cmpuint (fp_device_get_finger_status (device), ==, FP_FINGER_STATUS_PRESENT);

  g_object_get (fake_dev, "finger-status", &finger_status, NULL);
  g_assert_cmpuint (finger_status, ==, FP_FINGER_STATUS_PRESENT);

  g_assert (fake_dev->last_called_function == on_device_notify);
  pspec = g_steal_pointer (&fake_dev->user_data);
  g_assert_cmpstr (pspec->name, ==, "finger-status");

  fake_dev->last_called_function = NULL;
  g_assert_false (fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT));
  g_assert_null (fake_dev->last_called_function);
  g_assert_null (g_steal_pointer (&fake_dev->user_data));
}

static void
driver_finger_status_changes_check (FpDevice *device, gboolean add)
{
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  g_autoptr(GFlagsClass) status_class = g_type_class_ref (FP_TYPE_FINGER_STATUS_FLAGS);
  guint expected_status;
  guint initial_value;
  guint i;
  gulong signal_id;

  if (add)
    initial_value = FP_FINGER_STATUS_NONE;
  else
    initial_value = status_class->mask;

  g_assert_cmpuint (fp_device_get_finger_status (device), ==, initial_value);

  signal_id = g_signal_connect (device, "notify::finger-status",
                                G_CALLBACK (on_device_notify), NULL);

  for (i = 0, expected_status = initial_value; i < status_class->n_values; ++i)
    {
      g_autoptr(GParamSpec) pspec = NULL;
      FpFingerStatusFlags finger_status = status_class->values[i].value;
      FpFingerStatusFlags added_status = add ? finger_status : FP_FINGER_STATUS_NONE;
      FpFingerStatusFlags removed_status = add ? FP_FINGER_STATUS_NONE : finger_status;
      gboolean ret;

      fake_dev->last_called_function = NULL;
      ret = fpi_device_report_finger_status_changes (device,
                                                     added_status,
                                                     removed_status);
      if (finger_status != FP_FINGER_STATUS_NONE)
        g_assert_true (ret);
      else
        g_assert_false (ret);

      expected_status |= added_status;
      expected_status &= ~removed_status;

      g_assert_cmpuint (fp_device_get_finger_status (device), ==, expected_status);

      if (finger_status != FP_FINGER_STATUS_NONE)
        {
          g_assert (fake_dev->last_called_function == on_device_notify);
          pspec = g_steal_pointer (&fake_dev->user_data);
          g_assert_cmpstr (pspec->name, ==, "finger-status");
        }

      fake_dev->last_called_function = NULL;
      g_assert_false (fpi_device_report_finger_status_changes (device,
                                                               added_status,
                                                               removed_status));
      g_assert_null (fake_dev->last_called_function);
      g_assert_null (g_steal_pointer (&fake_dev->user_data));
    }

  if (add)
    g_assert_cmpuint (fp_device_get_finger_status (device), ==, status_class->mask);
  else
    g_assert_cmpuint (fp_device_get_finger_status (device), ==, FP_FINGER_STATUS_NONE);

  fake_dev->last_called_function = NULL;
  g_assert_false (fpi_device_report_finger_status_changes (device,
                                                           FP_FINGER_STATUS_NONE,
                                                           FP_FINGER_STATUS_NONE));

  g_assert_null (fake_dev->last_called_function);
  g_assert_null (g_steal_pointer (&fake_dev->user_data));

  g_signal_handler_disconnect (device, signal_id);
}

static void
test_driver_finger_status_changes (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);

  driver_finger_status_changes_check (device, TRUE);
  driver_finger_status_changes_check (device, FALSE);
}

static void
test_driver_get_nr_enroll_stages (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpDevice) device = NULL;
  int expected_stages = g_random_int_range (G_MININT32, G_MAXINT32);

  dev_class->nr_enroll_stages = expected_stages;
  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);

  g_assert_cmpint (fp_device_get_nr_enroll_stages (device), ==, expected_stages);
}

static void
test_driver_set_nr_enroll_stages (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  g_autoptr(GParamSpec) pspec = NULL;
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
  int expected_stages = g_random_int_range (1, G_MAXINT32);

  g_signal_connect (device, "notify::nr-enroll-stages", G_CALLBACK (on_device_notify), NULL);
  fpi_device_set_nr_enroll_stages (device, expected_stages);

  g_assert_cmpint (fp_device_get_nr_enroll_stages (device), ==, expected_stages);
  g_assert (fake_dev->last_called_function == on_device_notify);

  pspec = g_steal_pointer (&fake_dev->user_data);
  g_assert_cmpstr (pspec->name, ==, "nr-enroll-stages");

  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
                         "*enroll_stages > 0*");
  fpi_device_set_nr_enroll_stages (device, 0);
  g_assert_cmpint (fp_device_get_nr_enroll_stages (device), ==, expected_stages);
  g_test_assert_expected_messages ();

  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
                         "*enroll_stages > 0*");
  fpi_device_set_nr_enroll_stages (device, -2);
  g_assert_cmpint (fp_device_get_nr_enroll_stages (device), ==, expected_stages);
  g_test_assert_expected_messages ();
}

static void
test_driver_get_usb_device (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpDevice) device = NULL;

  dev_class->type = FP_DEVICE_TYPE_USB;
  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);
  dev_class->type = FP_DEVICE_TYPE_VIRTUAL;
  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
                         "*assertion*type*FP_DEVICE_TYPE_USB*failed*");
  g_assert_null (fpi_device_get_usb_device (device));
  g_test_assert_expected_messages ();
}

static void
test_driver_get_virtual_env (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpDevice) device = NULL;

  dev_class->type = FP_DEVICE_TYPE_VIRTUAL;
  device = g_object_new (FPI_TYPE_DEVICE_FAKE, "fpi-environ", "TEST_VIRTUAL_ENV_GETTER", NULL);
  g_assert_cmpstr (fpi_device_get_virtual_env (device), ==, "TEST_VIRTUAL_ENV_GETTER");

  g_clear_object (&device);
  dev_class->type = FP_DEVICE_TYPE_USB;
  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
                         "*assertion*type*FP_DEVICE_TYPE_VIRTUAL*failed*");
  g_assert_null (fpi_device_get_virtual_env (device));
  g_test_assert_expected_messages ();
}

static void
test_driver_get_driver_data (void)
{
  g_autoptr(FpDevice) device = NULL;
  guint64 driver_data;

  driver_data = g_random_int ();
  device = g_object_new (FPI_TYPE_DEVICE_FAKE, "fpi-driver-data", driver_data, NULL);
  g_assert_cmpuint (fpi_device_get_driver_data (device), ==, driver_data);
}

static void
test_driver_features_probe_updates (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev;

  if (!tod_check_device_version (device, 1, "1.92.0"))
    {
      g_test_skip ("Feature not supported by TODv1 versions before 1.92.0");
      return;
    }

  g_assert_cmpuint (dev_class->features, !=, FP_DEVICE_FEATURE_NONE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_CAPTURE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_IDENTIFY);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_VERIFY);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_DUPLICATES_CHECK);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_LIST);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_DELETE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);

  /* Effectively clears FP_DEVICE_FEATURE_STORAGE_DELETE */
  fake_dev = FPI_DEVICE_FAKE (device);
  fake_dev->probe_features_update = FP_DEVICE_FEATURE_STORAGE_LIST | FP_DEVICE_FEATURE_STORAGE_DELETE;
  fake_dev->probe_features_value = FP_DEVICE_FEATURE_STORAGE_LIST;

  g_async_initable_init_async (G_ASYNC_INITABLE (device),
                               G_PRIORITY_DEFAULT, NULL, NULL, NULL);
  while (g_main_context_iteration (NULL, FALSE))
    continue;

  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_CAPTURE));
  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_IDENTIFY));
  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_VERIFY));
  g_assert_false (fp_device_has_feature (device, FP_DEVICE_FEATURE_DUPLICATES_CHECK));
  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_STORAGE));
  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_STORAGE_LIST));
  g_assert_false (fp_device_has_feature (device, FP_DEVICE_FEATURE_STORAGE_DELETE));
  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_STORAGE_CLEAR));

  g_assert_cmpuint (fp_device_get_features (device),
                    ==,
                    FP_DEVICE_FEATURE_CAPTURE |
                    FP_DEVICE_FEATURE_IDENTIFY |
                    FP_DEVICE_FEATURE_VERIFY |
                    FP_DEVICE_FEATURE_STORAGE |
                    FP_DEVICE_FEATURE_STORAGE_LIST |
                    FP_DEVICE_FEATURE_STORAGE_CLEAR);
}

static void
test_driver_initial_features (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);

  g_assert_cmpuint (dev_class->features, !=, FP_DEVICE_FEATURE_NONE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_CAPTURE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_IDENTIFY);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_VERIFY);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_DUPLICATES_CHECK);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_LIST);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_DELETE);
  if (tod_check_device_version (device, 1, "1.92.0"))
    g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);
  else
    g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);

  g_async_initable_init_async (G_ASYNC_INITABLE (device),
                               G_PRIORITY_DEFAULT, NULL, NULL, NULL);
  while (g_main_context_iteration (NULL, FALSE))
    continue;

  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_CAPTURE));
  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_IDENTIFY));
  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_VERIFY));
  g_assert_false (fp_device_has_feature (device, FP_DEVICE_FEATURE_DUPLICATES_CHECK));
  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_STORAGE));
  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_STORAGE_LIST));
  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_STORAGE_DELETE));
  if (tod_check_device_version (device, 1, "1.92.0"))
    g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);
  else
    g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);

  g_assert_cmpuint (fp_device_get_features (device),
                    ==,
                    FP_DEVICE_FEATURE_CAPTURE |
                    FP_DEVICE_FEATURE_IDENTIFY |
                    FP_DEVICE_FEATURE_VERIFY |
                    FP_DEVICE_FEATURE_STORAGE |
                    FP_DEVICE_FEATURE_STORAGE_LIST |
                    FP_DEVICE_FEATURE_STORAGE_DELETE |
                    (tod_check_device_version (device, 1, "1.92.0") ?
                     FP_DEVICE_FEATURE_STORAGE_CLEAR : 0));
}

static void
test_driver_initial_features_none (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();

  dev_class->list = NULL;
  dev_class->capture = NULL;
  dev_class->verify = NULL;
  dev_class->identify = NULL;
  dev_class->delete = NULL;
  dev_class->clear_storage = NULL;
  dev_class->features = FP_DEVICE_FEATURE_NONE;

  fpi_device_class_auto_initialize_features (dev_class);

  g_assert_cmpuint (dev_class->features, ==, FP_DEVICE_FEATURE_NONE);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_CAPTURE);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_IDENTIFY);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_VERIFY);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_DUPLICATES_CHECK);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE_LIST);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE_DELETE);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);
}

static void
test_driver_initial_features_no_capture (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();

  dev_class->capture = NULL;
  dev_class->features = FP_DEVICE_FEATURE_NONE;

  fpi_device_class_auto_initialize_features (dev_class);

  g_assert_cmpuint (dev_class->features, !=, FP_DEVICE_FEATURE_NONE);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_CAPTURE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_IDENTIFY);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_VERIFY);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_DUPLICATES_CHECK);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_LIST);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_DELETE);
  if (tod_check_version (dev_class, 1, "1.92.0"))
    g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);
  else
    g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);
}

static void
test_driver_initial_features_no_verify (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();

  dev_class->verify = NULL;
  dev_class->features = FP_DEVICE_FEATURE_NONE;

  fpi_device_class_auto_initialize_features (dev_class);

  g_assert_cmpuint (dev_class->features, !=, FP_DEVICE_FEATURE_NONE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_CAPTURE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_IDENTIFY);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_VERIFY);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_DUPLICATES_CHECK);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_LIST);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_DELETE);
  if (tod_check_version (dev_class, 1, "1.92.0"))
    g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);
  else
    g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);
}

static void
test_driver_initial_features_no_identify (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();

  dev_class->identify = NULL;
  dev_class->features = FP_DEVICE_FEATURE_NONE;

  fpi_device_class_auto_initialize_features (dev_class);

  g_assert_cmpuint (dev_class->features, !=, FP_DEVICE_FEATURE_NONE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_CAPTURE);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_IDENTIFY);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_VERIFY);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_DUPLICATES_CHECK);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_LIST);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_DELETE);
  if (tod_check_version (dev_class, 1, "1.92.0"))
    g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);
  else
    g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);
}

static void
test_driver_initial_features_no_storage (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();

  dev_class->delete = NULL;
  dev_class->features = FP_DEVICE_FEATURE_NONE;

  fpi_device_class_auto_initialize_features (dev_class);

  g_assert_cmpuint (dev_class->features, !=, FP_DEVICE_FEATURE_NONE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_CAPTURE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_IDENTIFY);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_VERIFY);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_DUPLICATES_CHECK);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_LIST);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE_DELETE);
  if (tod_check_version (dev_class, 1, "1.92.0"))
    g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);
  else
    g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);
}

static void
test_driver_initial_features_no_list (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();

  dev_class->list = NULL;
  dev_class->features = FP_DEVICE_FEATURE_NONE;

  fpi_device_class_auto_initialize_features (dev_class);

  g_assert_cmpuint (dev_class->features, !=, FP_DEVICE_FEATURE_NONE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_CAPTURE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_IDENTIFY);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_VERIFY);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_DUPLICATES_CHECK);
  if (tod_check_version (dev_class, 1, "1.92.0"))
    g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE);
  else
    g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE_LIST);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_DELETE);
  if (tod_check_version (dev_class, 1, "1.92.0"))
    g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);
  else
    g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);
}

static void
test_driver_initial_features_no_delete (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();

  dev_class->delete = NULL;
  dev_class->features = FP_DEVICE_FEATURE_NONE;

  fpi_device_class_auto_initialize_features (dev_class);

  g_assert_cmpuint (dev_class->features, !=, FP_DEVICE_FEATURE_NONE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_CAPTURE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_IDENTIFY);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_VERIFY);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_DUPLICATES_CHECK);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_LIST);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE_DELETE);
  if (tod_check_version (dev_class, 1, "1.92.0"))
    g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);
  else
    g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);
}

static void
test_driver_initial_features_no_clear (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();

  dev_class->clear_storage = NULL;
  dev_class->features = FP_DEVICE_FEATURE_NONE;

  fpi_device_class_auto_initialize_features (dev_class);

  g_assert_cmpuint (dev_class->features, !=, FP_DEVICE_FEATURE_NONE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_CAPTURE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_IDENTIFY);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_VERIFY);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_DUPLICATES_CHECK);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_LIST);
  g_assert_true (dev_class->features & FP_DEVICE_FEATURE_STORAGE_DELETE);
  g_assert_false (dev_class->features & FP_DEVICE_FEATURE_STORAGE_CLEAR);
}

static void
on_driver_probe_async (GObject *initable, GAsyncResult *res, gpointer user_data)
{
  g_autoptr(GError) error = NULL;
  FpDevice **out_device = user_data;
  FpDevice *device;
  FpDeviceClass *dev_class;
  FpiDeviceFake *fake_dev;

  device = FP_DEVICE (g_async_initable_new_finish (G_ASYNC_INITABLE (initable), res, &error));
  dev_class = FP_DEVICE_GET_CLASS (device);
  fake_dev = FPI_DEVICE_FAKE (device);

  g_assert (fake_dev->last_called_function == dev_class->probe);
  g_assert_no_error (error);

  g_assert_false (fp_device_is_open (device));

  *out_device = device;
}

static void
test_driver_probe (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpDevice) device = NULL;

  dev_class->id = "Probed device ID";
  dev_class->full_name = "Probed device name";
  g_async_initable_new_async (FPI_TYPE_DEVICE_FAKE, G_PRIORITY_DEFAULT, NULL,
                              on_driver_probe_async, &device, NULL);

  while (!FP_IS_DEVICE (device))
    g_main_context_iteration (NULL, TRUE);

  g_assert_false (fp_device_is_open (device));
  g_assert_cmpstr (fp_device_get_device_id (device), ==, "Probed device ID");
  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)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  g_assert (fake_dev->last_called_function != dev_class->probe);

  g_assert_true (fp_device_open_sync (device, NULL, &error));
  g_assert (fake_dev->last_called_function == dev_class->open);
  g_assert_no_error (error);
  g_assert_true (fp_device_is_open (device));

  g_assert_true (fp_device_close_sync (FP_DEVICE (device), NULL, &error));
  g_assert_no_error (error);
}

static void
test_driver_open_error (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL);
  g_assert_false (fp_device_open_sync (device, NULL, &error));
  g_assert (fake_dev->last_called_function == dev_class->open);
  g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL);
  g_assert_false (fp_device_is_open (device));
  g_assert (error == g_steal_pointer (&fake_dev->ret_error));
}

static void
test_driver_close (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new ();
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  g_assert_true (fp_device_close_sync (device, NULL, &error));
  g_assert (fake_dev->last_called_function == dev_class->close);

  g_assert_no_error (error);
  g_assert_false (fp_device_is_open (device));
}

static void
test_driver_close_error (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new ();
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL);
  g_assert_false (fp_device_close_sync (device, NULL, &error));

  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_false (fp_device_is_open (device));
}

static void
test_driver_enroll (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new ();
  g_autoptr(FpPrint) template_print = fp_print_new (device);
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
  FpPrint *out_print = NULL;

  out_print =
    fp_device_enroll_sync (device, template_print, NULL, NULL, NULL, &error);

  g_assert (fake_dev->last_called_function == dev_class->enroll);
  g_assert (fake_dev->action_data == template_print);

  g_assert_no_error (error);
  g_assert (out_print == template_print);
}

static void
test_driver_enroll_error (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new ();
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
  FpPrint *template_print = fp_print_new (device);
  FpPrint *out_print = NULL;

  fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL);
  out_print =
    fp_device_enroll_sync (device, template_print, NULL, NULL, NULL, &error);

  g_assert (fake_dev->last_called_function == dev_class->enroll);
  g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL);
  g_assert (error == g_steal_pointer (&fake_dev->ret_error));
  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 = make_fake_print_reffed (device, NULL);
  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);
  g_clear_error (&error);

  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
                         "*Driver did not set the type on the returned print*");

  fake_dev->ret_error = NULL;
  fake_dev->ret_print = fp_print_new (device); /* Type not set. */
  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_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_assert_null (fake_dev->ret_print);
  g_clear_error (&error);
}

static void
test_driver_enroll_update_nbis (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpAutoCloseDevice) device = NULL;
  g_autoptr(FpPrint) template_print = NULL;
  FpiDeviceFake *fake_dev = NULL;
  FpPrint *out_print = NULL;

  dev_class->features |= FP_DEVICE_FEATURE_UPDATE_PRINT;
  device = auto_close_fake_device_new ();
  fake_dev = FPI_DEVICE_FAKE (device);

  template_print = make_fake_nbis_print (device);
  fake_dev->ret_print = template_print;

  out_print =
    fp_device_enroll_sync (device, template_print, NULL, NULL, NULL, &error);

  g_assert (fake_dev->last_called_function == dev_class->enroll);
  g_assert (fake_dev->action_data == template_print);

  g_assert_no_error (error);
  g_assert (out_print == template_print);
}

static void
test_driver_enroll_update_nbis_wrong_device (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpAutoCloseDevice) device = NULL;
  g_autoptr(FpPrint) template_print = NULL;
  FpiDeviceFake *fake_dev = NULL;
  FpPrint *out_print = NULL;

  dev_class->features |= FP_DEVICE_FEATURE_UPDATE_PRINT;

  device = auto_close_fake_device_new ();
  fake_dev = FPI_DEVICE_FAKE (device);

  template_print = make_fake_nbis_print (device);
  g_clear_pointer (&template_print->device_id, g_free);
  template_print->device_id = g_strdup ("wrong_device");
  fake_dev->ret_print = template_print;

  out_print =
    fp_device_enroll_sync (device, template_print, NULL, NULL, NULL, &error);

  g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID);
  g_assert (out_print == NULL);
}

static void
test_driver_enroll_update_nbis_wrong_driver (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpAutoCloseDevice) device = NULL;
  g_autoptr(FpPrint) template_print = NULL;
  FpiDeviceFake *fake_dev = NULL;
  FpPrint *out_print = NULL;

  dev_class->features |= FP_DEVICE_FEATURE_UPDATE_PRINT;

  device = auto_close_fake_device_new ();
  fake_dev = FPI_DEVICE_FAKE (device);

  template_print = make_fake_nbis_print (device);
  g_clear_pointer (&template_print->driver, g_free);
  template_print->driver = g_strdup ("wrong_driver");
  fake_dev->ret_print = template_print;

  out_print =
    fp_device_enroll_sync (device, template_print, NULL, NULL, NULL, &error);

  g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID);
  g_assert (out_print == NULL);
}

static void
test_driver_enroll_update_nbis_missing_feature (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpAutoCloseDevice) device = NULL;
  g_autoptr(FpPrint) template_print = NULL;
  FpiDeviceFake *fake_dev = NULL;
  FpPrint *out_print = NULL;

  device = auto_close_fake_device_new ();
  fake_dev = FPI_DEVICE_FAKE (device);

  template_print = make_fake_nbis_print (device);
  fake_dev->ret_print = template_print;

  out_print =
    fp_device_enroll_sync (device, template_print, NULL, NULL, NULL, &error);

  g_assert (fake_dev->last_called_function == dev_class->open);
  g_assert (fake_dev->action_data == NULL);

  g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID);
  g_assert (out_print == NULL);
}

typedef struct
{
  gint     completed_stages;
  FpPrint *print;
  GError  *error;
} ExpectedEnrollData;

static void
test_driver_enroll_progress_callback (FpDevice *device,
                                      gint      completed_stages,
                                      FpPrint  *print,
                                      gpointer  user_data,
                                      GError   *error)
{
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
  ExpectedEnrollData *expected_data = user_data;

  g_assert_cmpint (expected_data->completed_stages, ==, completed_stages);
  g_assert (expected_data->print == print);
  g_assert_true (print == NULL || FP_IS_PRINT (print));
  g_assert (expected_data->error == error);

  fake_dev->last_called_function = test_driver_enroll_progress_callback;
}

static void
test_driver_enroll_progress_vfunc (FpDevice *device)
{
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
  ExpectedEnrollData *expected_data = fake_dev->user_data;

  g_autoptr(GError) error = NULL;

  expected_data->completed_stages =
    g_random_int_range (fp_device_get_nr_enroll_stages (device), G_MAXINT32);
  expected_data->print = fp_print_new (device);
  expected_data->error = NULL;

  g_object_add_weak_pointer (G_OBJECT (expected_data->print),
                             (gpointer) & expected_data->print);

  fpi_device_enroll_progress (device, expected_data->completed_stages,
                              expected_data->print, expected_data->error);
  g_assert (fake_dev->last_called_function == test_driver_enroll_progress_callback);
  g_assert_null (expected_data->print);


  expected_data->completed_stages =
    g_random_int_range (fp_device_get_nr_enroll_stages (device), G_MAXINT32);
  expected_data->print = NULL;
  expected_data->error = fpi_device_retry_new (FP_DEVICE_RETRY_TOO_SHORT);

  fpi_device_enroll_progress (device, expected_data->completed_stages,
                              expected_data->print, expected_data->error);
  g_assert (fake_dev->last_called_function == test_driver_enroll_progress_callback);


  expected_data->completed_stages =
    g_random_int_range (fp_device_get_nr_enroll_stages (device), G_MAXINT32);
  expected_data->print = make_fake_print_reffed (device,
                                                 g_variant_new_int32 (expected_data->completed_stages));
  expected_data->error = NULL;

  error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL);
  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
                         "*assertion*error*FP_DEVICE_RETRY*failed");
  fpi_device_enroll_progress (device, expected_data->completed_stages,
                              expected_data->print, error);
  g_assert (fake_dev->last_called_function == test_driver_enroll_progress_callback);
  g_clear_object (&expected_data->print);
  g_test_assert_expected_messages ();

  expected_data->completed_stages =
    g_random_int_range (fp_device_get_nr_enroll_stages (device), G_MAXINT32);
  expected_data->print = NULL;
  expected_data->error = fpi_device_retry_new (FP_DEVICE_RETRY_TOO_SHORT);

  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
                         "*Driver passed an error and also provided a print*");
  fpi_device_enroll_progress (device, expected_data->completed_stages,
                              fp_print_new (device), expected_data->error);
  g_assert (fake_dev->last_called_function == test_driver_enroll_progress_callback);
  g_test_assert_expected_messages ();

  default_fake_dev_class.enroll (device);
  fake_dev->last_called_function = test_driver_enroll_progress_vfunc;
}

static void
test_driver_enroll_progress (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpAutoCloseDevice) device = NULL;
  G_GNUC_UNUSED g_autoptr(FpPrint) enrolled_print = NULL;
  ExpectedEnrollData expected_enroll_data = {0};
  FpiDeviceFake *fake_dev;

  dev_class->nr_enroll_stages = g_random_int_range (10, G_MAXINT32);
  dev_class->enroll = test_driver_enroll_progress_vfunc;
  device = auto_close_fake_device_new ();

  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
                         "*assertion*current_action*FPI_DEVICE_ACTION_ENROLL*failed");
  fpi_device_enroll_progress (device, 0, NULL, NULL);
  g_test_assert_expected_messages ();

  fake_dev = FPI_DEVICE_FAKE (device);
  fake_dev->user_data = &expected_enroll_data;

  enrolled_print = fp_device_enroll_sync (device, fp_print_new (device), NULL,
                                          test_driver_enroll_progress_callback,
                                          &expected_enroll_data, NULL);

  g_assert (fake_dev->last_called_function == test_driver_enroll_progress_vfunc);
}

typedef struct
{
  gboolean   called;
  FpPrint   *match;
  FpPrint   *print;
  GPtrArray *gallery;
  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);

  /* Compar gallery if this is an identify operation */
  if (data->gallery)
    {
      FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
      g_assert_false (fake_dev->action_data == data->gallery);
      assert_equal_galleries (fake_dev->action_data, data->gallery);
    }
}

static void
fake_device_stub_verify (FpDevice *device)
{
}

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 = make_fake_print_reffed (device, NULL);
  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_SUCCESS;
  g_assert_true (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);
}

static void
test_driver_verify_not_supported (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpPrint) enrolled_print = NULL;
  g_autoptr(FpPrint) out_print = NULL;
  g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1);
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpAutoCloseDevice) device = NULL;
  FpiDeviceFake *fake_dev;
  gboolean match;

  dev_class->features &= ~FP_DEVICE_FEATURE_VERIFY;

  device = auto_close_fake_device_new ();
  fake_dev = FPI_DEVICE_FAKE (device);
  fake_dev->last_called_function = NULL;

  enrolled_print = make_fake_print_reffed (device, g_variant_new_uint64 (3));
  g_assert_false (fp_device_verify_sync (device, enrolled_print, NULL,
                                         test_driver_match_cb, match_data,
                                         &match, &out_print, &error));

  g_assert_null (fake_dev->last_called_function);
  g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_NOT_SUPPORTED);

  g_assert_false (match_data->called);
  g_assert_no_error (match_data->error);

  g_assert_null (out_print);
  g_assert_false (match);
}

static void
test_driver_verify_fail (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new ();
  g_autoptr(FpPrint) enrolled_print = NULL;
  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;

  enrolled_print = make_fake_print_reffed (device, g_variant_new_uint64 (3));
  fake_dev->ret_result = FPI_MATCH_FAIL;
  g_assert_true (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 = make_fake_print_reffed (device, NULL);
  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);
  g_assert_false (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 = make_fake_print_reffed (device, NULL);
  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_error_new (FP_DEVICE_ERROR_GENERAL);
  g_assert_false (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);
  g_assert (error == g_steal_pointer (&fake_dev->ret_error));
  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 = make_fake_print_reffed (device, NULL);

  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*");

  g_assert_false (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 = make_fake_print_reffed (device, NULL);

  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);
  g_assert_false (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 = make_fake_print_reffed (device, NULL);

  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);
  g_assert_false (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);
  g_assert_false (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_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);

  g_assert_false (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_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;

  g_assert_false (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_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 = make_fake_print (device, NULL);
  g_object_add_weak_pointer (G_OBJECT (fake_dev->ret_print),
                             (gpointer) (&fake_dev->ret_print));

  g_assert_false (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)
{
}

static void
test_driver_identify_cb (FpDevice     *device,
                         GAsyncResult *res,
                         gpointer      user_data)
{
  MatchCbData *data = user_data;
  gboolean r;

  g_assert (data->called == FALSE);
  data->called = TRUE;

  r = fp_device_identify_finish (device, res, &data->match, &data->print, &data->error);

  if (r)
    g_assert_no_error (data->error);
  else
    g_assert_nonnull (data->error);

  if (data->match)
    g_assert_no_error (data->error);
}

static void
test_driver_supports_identify (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpDevice) device = NULL;

  dev_class->identify = fake_device_stub_identify;

  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
    g_assert_true (fp_device_supports_identify (device));
  G_GNUC_END_IGNORE_DEPRECATIONS
  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_IDENTIFY));
}

static void
test_driver_do_not_support_identify (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpDevice) device = NULL;

  dev_class->features &= ~FP_DEVICE_FEATURE_IDENTIFY;

  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
    g_assert_false (fp_device_supports_identify (device));
  G_GNUC_END_IGNORE_DEPRECATIONS
  g_assert_false (fp_device_has_feature (device, FP_DEVICE_FEATURE_IDENTIFY));
}

static void
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 = make_fake_prints_gallery (device, 500);
  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;

  expected_matched = g_ptr_array_index (prints, g_random_int_range (0, 499));
  fp_print_set_description (expected_matched, "fake-verified");

  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
    g_assert_true (fp_device_supports_identify (device));
  G_GNUC_END_IGNORE_DEPRECATIONS
  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_IDENTIFY));

  match_data->gallery = prints;

  fake_dev->ret_print = make_fake_print (device, NULL);
  g_assert_true (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_no_error (error);

  g_assert (print != NULL && print == fake_dev->ret_print);
  g_assert (expected_matched == matched_print);
}

static void
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 = make_fake_prints_gallery (device, 500);
  g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1);
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
    g_assert_true (fp_device_supports_identify (device));
  G_GNUC_END_IGNORE_DEPRECATIONS
  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_IDENTIFY));

  fake_dev->ret_print = make_fake_print (device, NULL);
  g_assert_true (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);

  g_assert (print != NULL && print == fake_dev->ret_print);
  g_assert_null (matched_print);
}

static 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 = make_fake_prints_gallery (device, 500);
  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;

  expected_matched = g_ptr_array_index (prints, g_random_int_range (0, 499));
  fp_print_set_description (expected_matched, "fake-verified");

  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
    g_assert_true (fp_device_supports_identify (device));
  G_GNUC_END_IGNORE_DEPRECATIONS
  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_IDENTIFY));

  fake_dev->ret_error = fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL);
  g_assert_false (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 = make_fake_prints_gallery (device, 500);
  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;

  expected_matched = g_ptr_array_index (prints, g_random_int_range (0, 499));
  fp_print_set_description (expected_matched, "fake-verified");

  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
    g_assert_true (fp_device_supports_identify (device));
  G_GNUC_END_IGNORE_DEPRECATIONS
  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_IDENTIFY));

  fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL);
  g_assert_false (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);
  g_assert (error == g_steal_pointer (&fake_dev->ret_error));
  g_assert_null (matched_print);
  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 = NULL;
  g_autoptr(GError) error = NULL;

  dev_class->identify = fake_device_identify_immediate_complete;
  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  prints = make_fake_prints_gallery (device, 500);

  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*");

  g_assert_false (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 = NULL;
  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;

  dev_class->identify = fake_device_identify_complete_error;
  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  fake_dev = FPI_DEVICE_FAKE (device);
  prints = make_fake_prints_gallery (device, 500);

  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);

  g_assert_false (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_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 = make_fake_print_reffed (device, NULL);
  g_object_add_weak_pointer (G_OBJECT (fake_dev->ret_match),
                             (gpointer) (&fake_dev->ret_match));
  g_assert_true (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 = make_fake_print (device, NULL);
  g_object_add_weak_pointer (G_OBJECT (fake_dev->ret_print),
                             (gpointer) (&fake_dev->ret_print));
  g_assert_false (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 = NULL;
  g_autoptr(FpAutoCloseDevice) device = NULL;
  G_GNUC_UNUSED 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);
  prints = make_fake_prints_gallery (device, 0);
  enrolled_print = make_fake_print_reffed (device, NULL);

  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);
  g_assert_false (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
test_driver_identify_suspend_continues (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1);
  g_autoptr(MatchCbData) identify_data = g_new0 (MatchCbData, 1);
  g_autoptr(GPtrArray) prints = NULL;
  g_autoptr(FpAutoCloseDevice) device = NULL;
  g_autoptr(GError) error = NULL;
  void (*orig_identify) (FpDevice *device);
  FpiDeviceFake *fake_dev;
  FpPrint *expected_matched;

  if (!tod_check_version (dev_class, 1, "1.94.0"))
    {
      g_test_skip ("Feature not supported by TODv1 versions before 1.94.0");
      return;
    }

  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  fake_dev = FPI_DEVICE_FAKE (device);
  orig_identify = dev_class->identify;
  dev_class->identify = fake_device_stub_identify;

  prints = make_fake_prints_gallery (device, 500);
  expected_matched = g_ptr_array_index (prints, g_random_int_range (0, 499));
  fp_print_set_description (expected_matched, "fake-verified");

  match_data->gallery = prints;

  fake_dev->ret_print = make_fake_print (device, NULL);

  g_assert_true (fp_device_open_sync (device, NULL, NULL));

  fp_device_identify (device, prints, NULL,
                      test_driver_match_cb, match_data, NULL,
                      (GAsyncReadyCallback) test_driver_identify_cb, identify_data);

  while (g_main_context_iteration (NULL, FALSE))
    continue;

  fake_dev->ret_suspend = NULL;
  fp_device_suspend_sync (device, NULL, &error);
  g_assert (fake_dev->last_called_function == dev_class->suspend);
  g_assert_no_error (error);

  while (g_main_context_iteration (NULL, FALSE))
    continue;

  g_assert_false (match_data->called);
  g_assert_false (identify_data->called);

  fake_dev->ret_resume = NULL;
  fp_device_resume_sync (device, NULL, &error);
  g_assert (fake_dev->last_called_function == dev_class->resume);
  g_assert_no_error (error);

  orig_identify (device);

  /* This currently happens immediately (not ABI though) */
  g_assert_true (match_data->called);
  g_assert (match_data->match == expected_matched);

  while (g_main_context_iteration (NULL, FALSE))
    continue;

  g_assert_true (identify_data->called);
  g_assert (identify_data->match == expected_matched);

  g_assert (fake_dev->last_called_function == orig_identify);
}

static void
test_driver_identify_suspend_succeeds (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1);
  g_autoptr(MatchCbData) identify_data = g_new0 (MatchCbData, 1);
  g_autoptr(GPtrArray) prints = NULL;
  g_autoptr(FpAutoCloseDevice) device = NULL;
  g_autoptr(GError) error = NULL;
  void (*orig_identify) (FpDevice *device);
  FpiDeviceFake *fake_dev;
  FpPrint *expected_matched;

  if (!tod_check_version (dev_class, 1, "1.94.0"))
    {
      g_test_skip ("Feature not supported by TODv1 versions before 1.94.0");
      return;
    }

  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  fake_dev = FPI_DEVICE_FAKE (device);
  orig_identify = dev_class->identify;
  dev_class->identify = fake_device_stub_identify;

  prints = make_fake_prints_gallery (device, 500);
  expected_matched = g_ptr_array_index (prints, g_random_int_range (0, 499));
  fp_print_set_description (expected_matched, "fake-verified");

  match_data->gallery = prints;

  g_assert_true (fp_device_open_sync (device, NULL, NULL));

  fake_dev->ret_print = make_fake_print (device, NULL);
  fp_device_identify (device, prints, NULL,
                      test_driver_match_cb, match_data, NULL,
                      (GAsyncReadyCallback) test_driver_identify_cb, identify_data);

  while (g_main_context_iteration (NULL, FALSE))
    continue;

  /* suspend_sync hangs until cancellation, so we need to trigger orig_identify
   * from the mainloop after calling suspend_sync.
   */
  fpi_device_add_timeout (device, 0, (FpTimeoutFunc) orig_identify, NULL, NULL);

  fake_dev->ret_suspend = fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED);
  fp_device_suspend_sync (device, NULL, &error);

  /* At this point we are done with everything */
  g_assert (fake_dev->last_called_function == orig_identify);
  g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_NOT_SUPPORTED);
  g_clear_error (&error);

  /* We suspended, but device reported success and that will be reported. */
  g_assert_true (match_data->called);
  g_assert (match_data->match == expected_matched);
  g_assert_true (identify_data->called);
  g_assert (identify_data->match == expected_matched);

  /* Resuming the device does not call resume handler, as the action was
   * cancelled already.
   */
  fake_dev->last_called_function = NULL;
  fp_device_resume_sync (device, NULL, &error);
  g_assert (fake_dev->last_called_function == NULL);
  g_assert_no_error (error);
}

static void
test_driver_identify_suspend_busy_error (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1);
  g_autoptr(MatchCbData) identify_data = g_new0 (MatchCbData, 1);
  g_autoptr(GPtrArray) prints = NULL;
  g_autoptr(FpAutoCloseDevice) device = NULL;
  g_autoptr(GError) error = NULL;
  void (*orig_identify) (FpDevice *device);
  FpiDeviceFake *fake_dev;
  FpPrint *expected_matched;

  if (!tod_check_version (dev_class, 1, "1.94.0"))
    {
      g_test_skip ("Feature not supported by TODv1 versions before 1.94.0");
      return;
    }

  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  fake_dev = FPI_DEVICE_FAKE (device);
  orig_identify = dev_class->identify;
  dev_class->identify = fake_device_stub_identify;

  prints = make_fake_prints_gallery (device, 500);
  expected_matched = g_ptr_array_index (prints, g_random_int_range (0, 499));
  fp_print_set_description (expected_matched, "fake-verified");

  match_data->gallery = prints;

  g_assert_true (fp_device_open_sync (device, NULL, NULL));

  fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL);
  fake_dev->ret_print = make_fake_print (device, NULL);
  fp_device_identify (device, prints, NULL,
                      test_driver_match_cb, match_data, NULL,
                      (GAsyncReadyCallback) test_driver_identify_cb, identify_data);

  while (g_main_context_iteration (NULL, FALSE))
    continue;

  /* suspend_sync hangs until cancellation, so we need to trigger orig_identify
   * from the mainloop after calling suspend_sync.
   */
  fpi_device_add_timeout (device, 0, (FpTimeoutFunc) orig_identify, NULL, NULL);

  fake_dev->ret_suspend = fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED);
  fp_device_suspend_sync (device, NULL, &error);
  fake_dev->ret_error = NULL;

  /* At this point we are done with everything */
  g_assert (fake_dev->last_called_function == orig_identify);
  g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_NOT_SUPPORTED);
  g_clear_error (&error);

  /* The device reported an error, an this error will be overwritten.
   */
  g_assert_false (match_data->called);
  g_assert_true (identify_data->called);
  g_assert_null (identify_data->match);
  g_assert_error (identify_data->error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_BUSY);

  fake_dev->last_called_function = NULL;
  fp_device_resume_sync (device, NULL, &error);
  g_assert (fake_dev->last_called_function == NULL);
  g_assert_no_error (error);
}

static void
test_driver_identify_suspend_while_idle (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpAutoCloseDevice) device = NULL;
  g_autoptr(GError) error = NULL;
  FpiDeviceFake *fake_dev;

  if (!tod_check_version (dev_class, 1, "1.94.0"))
    {
      g_test_skip ("Feature not supported by TODv1 versions before 1.94.0");
      return;
    }

  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  fake_dev = FPI_DEVICE_FAKE (device);

  /* Suspending and resuming a closed device works */
  fp_device_suspend (device, NULL, (GAsyncReadyCallback) fp_device_suspend_finish, &error);
  while (g_main_context_iteration (NULL, FALSE))
    continue;
  g_assert (fake_dev->last_called_function == NULL);
  g_assert_no_error (error);

  fp_device_resume (device, NULL, (GAsyncReadyCallback) fp_device_resume_finish, NULL);
  while (g_main_context_iteration (NULL, FALSE))
    continue;
  g_assert (fake_dev->last_called_function == NULL);
  g_assert_no_error (error);

  g_assert_true (fp_device_open_sync (device, NULL, NULL));

  fake_dev->last_called_function = NULL;
  fp_device_suspend (device, NULL, (GAsyncReadyCallback) fp_device_suspend_finish, &error);
  while (g_main_context_iteration (NULL, FALSE))
    continue;
  g_assert (fake_dev->last_called_function == NULL);
  g_assert_no_error (error);

  fp_device_resume (device, NULL, (GAsyncReadyCallback) fp_device_resume_finish, NULL);
  while (g_main_context_iteration (NULL, FALSE))
    continue;
  g_assert (fake_dev->last_called_function == NULL);
  g_assert_no_error (error);
}

static void
test_driver_identify_warmup_cooldown (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(MatchCbData) identify_data = g_new0 (MatchCbData, 1);
  g_autoptr(GPtrArray) prints = NULL;
  g_autoptr(FpAutoCloseDevice) device = NULL;
  g_autoptr(GError) error = NULL;
  void (*orig_identify) (FpDevice *device);
  FpiDeviceFake *fake_dev;
  gint64 start_time;

  if (!tod_check_version (dev_class, 1, "1.94.0"))
    {
      g_test_skip ("Feature not supported by TODv1 versions before 1.94.0");
      return;
    }

  dev_class->temp_hot_seconds = 2;
  dev_class->temp_cold_seconds = 5;

  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  fake_dev = FPI_DEVICE_FAKE (device);
  orig_identify = dev_class->identify;
  dev_class->identify = fake_device_stub_identify;

  prints = make_fake_prints_gallery (device, 500);

  g_assert_true (fp_device_open_sync (device, NULL, NULL));
  fake_dev->last_called_function = NULL;

  fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL);

  /* Undefined: Whether match_cb is called. */
  fp_device_identify (device, prints, NULL,
                      NULL, NULL, NULL,
                      (GAsyncReadyCallback) test_driver_identify_cb, identify_data);

  /* Identify is running, the temperature will change after only a short time.
   * Changes are delayed by 100ms and we give 150ms of slack for the test.
   */
  start_time = g_get_monotonic_time ();
  g_assert_cmpint (fp_device_get_temperature (device), ==, FP_TEMPERATURE_COLD);
  while (fp_device_get_temperature (device) == FP_TEMPERATURE_COLD)
    g_main_context_iteration (NULL, TRUE);
  g_assert_cmpint (fp_device_get_temperature (device), ==, FP_TEMPERATURE_WARM);
  g_assert_false (g_cancellable_is_cancelled (fpi_device_get_cancellable (device)));
  g_assert_cmpint (g_get_monotonic_time () - start_time, <, 0 + 250000);

  /* we reach hot 2 seconds later */
  while (fp_device_get_temperature (device) == FP_TEMPERATURE_WARM)
    g_main_context_iteration (NULL, TRUE);
  g_assert_cmpint (fp_device_get_temperature (device), ==, FP_TEMPERATURE_HOT);
  g_assert_true (g_cancellable_is_cancelled (fpi_device_get_cancellable (device)));
  g_assert_cmpint (g_get_monotonic_time () - start_time, <, 2000000 + 250000);

  /* cancel vfunc will be called now */
  g_assert (fake_dev->last_called_function == NULL);
  while (g_main_context_iteration (NULL, FALSE))
    continue;
  g_assert (fake_dev->last_called_function == dev_class->cancel);

  orig_identify (device);
  fake_dev->ret_error = NULL;
  while (g_main_context_iteration (NULL, FALSE))
    continue;
  g_assert_true (identify_data->called);
  g_assert_error (identify_data->error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_TOO_HOT);

  /* Try to identify again, and ensure that we fail early */
  fp_device_identify_sync (device, prints, NULL, NULL, NULL, NULL, NULL, &error);
  g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_TOO_HOT);
  g_clear_error (&error);

  /* Now, wait for it to cool down again;
   * WARM should be reached after about 2s
   * COLD after 5s but give it some more slack. */
  start_time = g_get_monotonic_time ();
  while (fp_device_get_temperature (device) == FP_TEMPERATURE_HOT)
    g_main_context_iteration (NULL, TRUE);
  g_assert_cmpint (fp_device_get_temperature (device), ==, FP_TEMPERATURE_WARM);
  g_assert_cmpint (g_get_monotonic_time () - start_time, <, 2000000 + 250000);

  while (fp_device_get_temperature (device) == FP_TEMPERATURE_WARM)
    g_main_context_iteration (NULL, TRUE);
  g_assert_cmpint (fp_device_get_temperature (device), ==, FP_TEMPERATURE_COLD);
  g_assert_cmpint (g_get_monotonic_time () - start_time, <, 5000000 + 500000);
}

static void
fake_device_stub_capture (FpDevice *device)
{
}

static void
test_driver_supports_capture (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpDevice) device = NULL;

  dev_class->features |= FP_DEVICE_FEATURE_CAPTURE;
  dev_class->capture = fake_device_stub_capture;

  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
    g_assert_true (fp_device_supports_capture (device));
  G_GNUC_END_IGNORE_DEPRECATIONS
  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_CAPTURE));
}

static void
test_driver_do_not_support_capture (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpDevice) device = NULL;

  dev_class->features &= ~FP_DEVICE_FEATURE_CAPTURE;
  dev_class->capture = NULL;

  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
    g_assert_false (fp_device_supports_capture (device));
  G_GNUC_END_IGNORE_DEPRECATIONS
  g_assert_false (fp_device_has_feature (device, FP_DEVICE_FEATURE_CAPTURE));
}

static void
test_driver_capture (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpImage) image = NULL;
  g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new ();
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
  gboolean wait_for_finger = TRUE;

  fake_dev->ret_image = fp_image_new (500, 500);
  image = fp_device_capture_sync (device, wait_for_finger, NULL, &error);
  g_assert (fake_dev->last_called_function == dev_class->capture);
  g_assert_true (GPOINTER_TO_UINT (fake_dev->action_data));
  g_assert_no_error (error);

  g_assert (image == fake_dev->ret_image);
}

static void
test_driver_capture_not_supported (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpImage) image = NULL;
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpAutoCloseDevice) device = NULL;
  gboolean wait_for_finger = TRUE;
  FpiDeviceFake *fake_dev;

  dev_class->features &= ~FP_DEVICE_FEATURE_CAPTURE;

  device = auto_close_fake_device_new ();
  fake_dev = FPI_DEVICE_FAKE (device);
  fake_dev->last_called_function = NULL;

  image = fp_device_capture_sync (device, wait_for_finger, NULL, &error);
  g_assert_null (fake_dev->last_called_function);
  g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_NOT_SUPPORTED);

  g_assert_null (image);
}

static void
test_driver_capture_error (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpImage) image = NULL;
  g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new ();
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
  gboolean wait_for_finger = TRUE;

  fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL);
  image = fp_device_capture_sync (device, wait_for_finger, NULL, &error);
  g_assert (fake_dev->last_called_function == dev_class->capture);
  g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL);
  g_assert (error == g_steal_pointer (&fake_dev->ret_error));

  g_assert_null (image);
}

static void
test_driver_has_storage (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpDevice) device = NULL;

  dev_class->features |= FP_DEVICE_FEATURE_STORAGE;

  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
    g_assert_true (fp_device_has_storage (device));
  G_GNUC_END_IGNORE_DEPRECATIONS
  g_assert_true (fp_device_has_feature (device, FP_DEVICE_FEATURE_STORAGE));
}

static void
test_driver_has_not_storage (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpDevice) device = NULL;

  dev_class->features &= ~FP_DEVICE_FEATURE_STORAGE;

  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
    g_assert_false (fp_device_has_storage (device));
  G_GNUC_END_IGNORE_DEPRECATIONS
  g_assert_false (fp_device_has_feature (device, FP_DEVICE_FEATURE_STORAGE));
}

static void
test_driver_list (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new ();
  g_autoptr(GPtrArray) prints = make_fake_prints_gallery (device, 500);
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  fake_dev->ret_list = g_steal_pointer (&prints);
  prints = fp_device_list_prints_sync (device, NULL, &error);

  g_assert (fake_dev->last_called_function == dev_class->list);
  g_assert_no_error (error);

  g_assert (prints == fake_dev->ret_list);
}

static void
test_driver_list_error (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new ();
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  g_autoptr(GPtrArray) prints = NULL;

  fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL);
  prints = fp_device_list_prints_sync (device, NULL, &error);

  g_assert (fake_dev->last_called_function == dev_class->list);
  g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL);
  g_assert (error == g_steal_pointer (&fake_dev->ret_error));

  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->features &= ~FP_DEVICE_FEATURE_STORAGE;

  device = auto_close_fake_device_new ();
  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
    g_assert_false (fp_device_has_storage (device));
  G_GNUC_END_IGNORE_DEPRECATIONS
  g_assert_false (fp_device_has_feature (device, FP_DEVICE_FEATURE_STORAGE));

  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)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new ();
  g_autoptr(FpPrint) enrolled_print = make_fake_print_reffed (device, NULL);
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
  gboolean ret;

  ret = fp_device_delete_print_sync (device, enrolled_print, NULL, &error);
  g_assert (fake_dev->last_called_function == dev_class->delete);
  g_assert (fake_dev->action_data == enrolled_print);
  g_assert_no_error (error);
  g_assert_true (ret);
}

static void
test_driver_delete_error (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new ();
  g_autoptr(FpPrint) enrolled_print = make_fake_print_reffed (device, NULL);
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
  gboolean ret;

  fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL);
  ret = fp_device_delete_print_sync (device, enrolled_print, NULL, &error);
  g_assert (fake_dev->last_called_function == dev_class->delete);
  g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL);
  g_assert (error == g_steal_pointer (&fake_dev->ret_error));

  g_assert_false (ret);
}

static void
test_driver_clear_storage (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new ();
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
  gboolean ret;

  if (!tod_check_device_version (device, 1, "1.92.0"))
    {
      g_test_skip ("Feature not supported by TODv1 versions before 1.92.0");
      return;
    }

  ret = fp_device_clear_storage_sync (device, NULL, &error);
  g_assert (fake_dev->last_called_function == dev_class->clear_storage);
  g_assert_no_error (error);
  g_assert_true (ret);
}

static void
test_driver_clear_storage_error (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new ();
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
  gboolean ret;

  if (!tod_check_device_version (device, 1, "1.92.0"))
    {
      g_test_skip ("Feature not supported by TODv1 versions before 1.92.0");
      return;
    }

  fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL);
  ret = fp_device_clear_storage_sync (device, NULL, &error);
  g_assert (fake_dev->last_called_function == dev_class->clear_storage);
  g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL);
  g_assert (error == g_steal_pointer (&fake_dev->ret_error));

  g_assert_false (ret);
}

static gboolean
fake_device_delete_wait_for_cancel_timeout (gpointer data)
{
  FpDevice *device = data;
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);

  g_assert (fake_dev->last_called_function == dev_class->cancel);
  default_fake_dev_class.delete (device);

  g_assert (fake_dev->last_called_function == default_fake_dev_class.delete);
  fake_dev->last_called_function = fake_device_delete_wait_for_cancel_timeout;

  return G_SOURCE_REMOVE;
}

static void
fake_device_delete_wait_for_cancel (FpDevice *device)
{
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  fake_dev->last_called_function = fake_device_delete_wait_for_cancel;

  g_timeout_add (100, fake_device_delete_wait_for_cancel_timeout, device);
}

static void
on_driver_cancel_delete (GObject *obj, GAsyncResult *res, gpointer user_data)
{
  g_autoptr(GError) error = NULL;
  FpDevice *device = FP_DEVICE (obj);
  gboolean *completed = user_data;

  fp_device_delete_print_finish (device, res, &error);
  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);

  *completed = TRUE;
}

static void
test_driver_cancel (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpAutoCloseDevice) device = NULL;
  g_autoptr(GCancellable) cancellable = NULL;
  g_autoptr(FpPrint) enrolled_print = NULL;
  gboolean completed = FALSE;
  FpiDeviceFake *fake_dev;

  dev_class->delete = fake_device_delete_wait_for_cancel;

  device = auto_close_fake_device_new ();
  fake_dev = FPI_DEVICE_FAKE (device);
  cancellable = g_cancellable_new ();
  enrolled_print = make_fake_print_reffed (device, NULL);

  fp_device_delete_print (device, enrolled_print, cancellable,
                          on_driver_cancel_delete, &completed);
  g_cancellable_cancel (cancellable);

  while (!completed)
    g_main_context_iteration (NULL, TRUE);

  g_assert (fake_dev->last_called_function == fake_device_delete_wait_for_cancel_timeout);
}

static void
test_driver_cancel_fail (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new ();
  g_autoptr(GCancellable) cancellable = g_cancellable_new ();
  g_autoptr(FpPrint) enrolled_print = make_fake_print_reffed (device, NULL);
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  g_assert_true (fp_device_delete_print_sync (device, enrolled_print, cancellable, &error));
  g_assert (fake_dev->last_called_function == dev_class->delete);
  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);
}

static void
test_driver_critical (void)
{
  g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new ();
  g_autoptr(GCancellable) cancellable = g_cancellable_new ();
  g_autoptr(FpPrint) enrolled_print = make_fake_print_reffed (device, NULL);
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  void (*orig_verify) (FpDevice *device) = dev_class->verify;
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  if (!tod_check_device_version (device, 1, "1.94.0"))
    {
      g_test_skip ("Feature not supported by TODv1 versions before 1.94.0");
      return;
    }

  fake_dev->last_called_function = NULL;

  dev_class->verify = fake_device_stub_verify;
  fp_device_verify (device, enrolled_print, cancellable,
                    NULL, NULL, NULL,
                    NULL, NULL);

  /* We started a verify operation, now emulate a "critical" section */
  fpi_device_critical_enter (device);

  /* Throw a suspend and external cancellation against it. */
  fp_device_suspend (device, NULL, NULL, NULL);
  g_cancellable_cancel (cancellable);

  /* The only thing that happens is that the cancellable is cancelled */
  g_assert_true (fpi_device_action_is_cancelled (device));
  g_assert (fake_dev->last_called_function == NULL);
  while (g_main_context_iteration (NULL, FALSE))
    continue;
  g_assert (fake_dev->last_called_function == NULL);

  /* Leaving and entering the critical section in the same mainloop iteration
   * does not do anything. */
  fpi_device_critical_leave (device);
  fpi_device_critical_enter (device);
  while (g_main_context_iteration (NULL, FALSE))
    continue;
  g_assert (fake_dev->last_called_function == NULL);

  /* Leaving it and running the mainloop will first run the cancel handler */
  fpi_device_critical_leave (device);
  while (g_main_context_iteration (NULL, FALSE) && !fake_dev->last_called_function)
    continue;
  g_assert (fake_dev->last_called_function == dev_class->cancel);
  g_assert_true (fpi_device_action_is_cancelled (device));
  fake_dev->last_called_function = NULL;

  /* Then the suspend handler */
  while (g_main_context_iteration (NULL, FALSE) && !fake_dev->last_called_function)
    continue;
  g_assert (fake_dev->last_called_function == dev_class->suspend);
  fake_dev->last_called_function = NULL;

  /* Nothing happens afterwards */
  while (g_main_context_iteration (NULL, FALSE))
    continue;
  g_assert (fake_dev->last_called_function == NULL);


  /* Throw a resume at the system */
  fpi_device_critical_enter (device);
  fp_device_resume (device, NULL, NULL, NULL);

  /* Nothing will happen, as the resume is delayed */
  while (g_main_context_iteration (NULL, FALSE))
    continue;
  g_assert (fake_dev->last_called_function == NULL);

  /* Finally the resume is called from the mainloop after leaving the critical section */
  fpi_device_critical_leave (device);
  g_assert (fake_dev->last_called_function == NULL);
  while (g_main_context_iteration (NULL, FALSE) && !fake_dev->last_called_function)
    continue;
  g_assert (fake_dev->last_called_function == dev_class->resume);
  fake_dev->last_called_function = NULL;


  /* The "verify" operation is still ongoing, finish it. */
  orig_verify (device);
  while (g_main_context_iteration (NULL, FALSE))
    continue;
}

static void
test_driver_current_action (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);

  g_assert_cmpint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_NONE);
}

static void
test_driver_current_action_open_vfunc (FpDevice *device)
{
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_OPEN);
  fake_dev->last_called_function = test_driver_current_action_open_vfunc;

  fpi_device_open_complete (device, NULL);
}

static void
test_driver_current_action_open (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpAutoCloseDevice) device = NULL;
  FpiDeviceFake *fake_dev;

  dev_class->open = test_driver_current_action_open_vfunc;
  device = auto_close_fake_device_new ();
  fake_dev = FPI_DEVICE_FAKE (device);
  g_assert (fake_dev->last_called_function == test_driver_current_action_open_vfunc);

  g_assert_cmpint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_NONE);
}

static void
test_driver_action_get_cancellable_open_vfunc (FpDevice *device)
{
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_OPEN);
  fake_dev->last_called_function = test_driver_action_get_cancellable_open_vfunc;

  g_assert_true (G_IS_CANCELLABLE (fpi_device_get_cancellable (device)));

  fpi_device_open_complete (device, NULL);
}

static void
test_driver_action_get_cancellable_open (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpAutoCloseDevice) device = NULL;
  g_autoptr(GCancellable) cancellable = NULL;
  FpiDeviceFake *fake_dev;

  dev_class->open = test_driver_action_get_cancellable_open_vfunc;
  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  fake_dev = FPI_DEVICE_FAKE (device);

  cancellable = g_cancellable_new ();
  g_assert_true (fp_device_open_sync (device, cancellable, NULL));

  g_assert (fake_dev->last_called_function == test_driver_action_get_cancellable_open_vfunc);
}

static void
test_driver_action_get_cancellable_open_internal_vfunc (FpDevice *device)
{
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_OPEN);
  fake_dev->last_called_function = test_driver_action_get_cancellable_open_internal_vfunc;

  g_assert_true (G_IS_CANCELLABLE (fpi_device_get_cancellable (device)));

  fpi_device_open_complete (device, NULL);
}

static void
test_driver_action_get_cancellable_open_internal (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpAutoCloseDevice) device = NULL;
  FpiDeviceFake *fake_dev;

  dev_class->open = test_driver_action_get_cancellable_open_internal_vfunc;
  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  fake_dev = FPI_DEVICE_FAKE (device);

  g_assert_true (fp_device_open_sync (device, NULL, NULL));

  g_assert (fake_dev->last_called_function == test_driver_action_get_cancellable_open_internal_vfunc);
}

static void
test_driver_action_get_cancellable_error (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);

  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
                         "*assertion*current_action*FPI_DEVICE_ACTION_NONE*failed");
  g_assert_null (fpi_device_get_cancellable (device));
  g_test_assert_expected_messages ();
}

static void
test_driver_action_is_cancelled_open_vfunc (FpDevice *device)
{
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_OPEN);
  fake_dev->last_called_function = test_driver_action_is_cancelled_open_vfunc;

  g_assert_true (G_IS_CANCELLABLE (fpi_device_get_cancellable (device)));
  g_assert_false (fpi_device_action_is_cancelled (device));

  if (fake_dev->ext_cancellable)
    g_cancellable_cancel (fake_dev->ext_cancellable);
  else
    g_cancellable_cancel (fpi_device_get_cancellable (device));

  g_assert_true (fpi_device_action_is_cancelled (device));

  fpi_device_open_complete (device, NULL);
}

static void
test_driver_action_is_cancelled_open (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpAutoCloseDevice) device = NULL;
  g_autoptr(GCancellable) cancellable = NULL;
  g_autoptr(GError) error = NULL;
  FpiDeviceFake *fake_dev;

  if (!tod_check_version (dev_class, 1, "1.94.0"))
    {
      g_test_skip ("Feature not supported by TODv1 versions before 1.94.0");
      return;
    }

  dev_class->open = test_driver_action_is_cancelled_open_vfunc;
  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  fake_dev = FPI_DEVICE_FAKE (device);

  cancellable = fake_dev->ext_cancellable = g_cancellable_new ();
  g_assert_false (fp_device_open_sync (device, cancellable, &error));
  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);

  g_assert (fake_dev->last_called_function == test_driver_action_is_cancelled_open_vfunc);
}

static void
test_driver_action_internally_cancelled_open (void)
{
  g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
  g_autoptr(FpAutoCloseDevice) device = NULL;
  g_autoptr(GCancellable) cancellable = NULL;
  g_autoptr(GError) error = NULL;
  FpiDeviceFake *fake_dev;

  if (!tod_check_version (dev_class, 1, "1.94.0"))
    {
      g_test_skip ("Feature not supported by TODv1 versions before 1.94.0");
      return;
    }

  dev_class->open = test_driver_action_is_cancelled_open_vfunc;
  device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  fake_dev = FPI_DEVICE_FAKE (device);

  /* No error, just some internal cancellation but we let nothing happen externally. */
  cancellable = g_cancellable_new ();
  g_assert_true (fp_device_open_sync (device, cancellable, &error));
  g_assert_null (error);

  g_assert (fake_dev->last_called_function == test_driver_action_is_cancelled_open_vfunc);
}

static void
test_driver_action_is_cancelled_error (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);

  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
                         "*assertion*current_action*FPI_DEVICE_ACTION_NONE*failed");
  g_assert_true (fpi_device_action_is_cancelled (device));
  g_test_assert_expected_messages ();
}

static void
test_driver_complete_actions_errors (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);

  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
                         "*assertion*current_action*failed");
  fpi_device_probe_complete (device, NULL, NULL, NULL);
  g_test_assert_expected_messages ();

  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
                         "*assertion*current_action*failed");
  fpi_device_open_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_close_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_enroll_complete (device, NULL, NULL);
  g_test_assert_expected_messages ();

  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
                         "*assertion*current_action*failed");
  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);
  g_test_assert_expected_messages ();

  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
                         "*assertion*current_action*failed");
  fpi_device_capture_complete (device, NULL, NULL);
  g_test_assert_expected_messages ();

  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
                         "*assertion*current_action*failed");
  fpi_device_delete_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_list_complete (device, NULL, NULL);
  g_test_assert_expected_messages ();
}

static void
test_driver_action_error_error (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);

  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
                         "*assertion*current_action*FPI_DEVICE_ACTION_NONE*failed");
  fpi_device_action_error (device, NULL);
  g_test_assert_expected_messages ();
}

static void
test_driver_action_error_all (void)
{
  g_autoptr(FpAutoCloseDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  g_autoptr(FpPrint) enrolled_print = make_fake_print_reffed (device, NULL);
  g_autoptr(GPtrArray) prints = make_fake_prints_gallery (device, 0);
  g_autoptr(GError) error = NULL;
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev;

  fake_dev = FPI_DEVICE_FAKE (device);
  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);

  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_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);

  if (tod_check_device_version (device, 1, "1.92.0"))
    {
      fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID);
      g_assert_false (fp_device_clear_storage_sync (device, NULL, &error));
      g_assert_true (fake_dev->last_called_function == dev_class->clear_storage);
      g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID);
      g_clear_error (&error);
    }
  else
    {
      g_assert_false (fp_device_clear_storage_sync (device, NULL, &error));
      g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_NOT_SUPPORTED);
      g_clear_error (&error);
    }

  /* Test close last, as we can't operate on a closed device. */
  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);
}

static void
test_driver_action_error_fallback_all (void)
{
  g_autoptr(FpAutoCloseDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  g_autoptr(FpPrint) enrolled_print = make_fake_print_reffed (device, NULL);
  g_autoptr(GPtrArray) prints = make_fake_prints_gallery (device, 0);
  g_autoptr(GError) error = NULL;
  FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
  FpiDeviceFake *fake_dev;

  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_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_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);

  if (tod_check_device_version (device, 1, "1.92.0"))
    {
      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_clear_storage_sync (device, NULL, &error));
      g_test_assert_expected_messages ();
      g_assert_true (fake_dev->last_called_function == dev_class->clear_storage);
      g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL);
      g_clear_error (&error);
    }

  /* Test close last, as we can't operate on a closed device. */
  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_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);
}

static void
test_driver_add_timeout_func (FpDevice *device, gpointer user_data)
{
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);

  fake_dev->last_called_function = test_driver_add_timeout_func;
}

static void
test_driver_add_timeout (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
  FpDevice *data_check = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);

  g_object_add_weak_pointer (G_OBJECT (data_check), (gpointer) & data_check);
  fpi_device_add_timeout (device, 50, test_driver_add_timeout_func,
                          data_check, g_object_unref);

  g_assert_nonnull (data_check);

  while (FP_IS_DEVICE (data_check))
    g_main_context_iteration (NULL, TRUE);

  g_assert_null (data_check);
  g_assert (fake_dev->last_called_function == test_driver_add_timeout_func);
}

static gboolean
test_driver_add_timeout_cancelled_timeout (gpointer data)
{
  GSource *source = data;

  g_source_destroy (source);

  return G_SOURCE_REMOVE;
}

static void
test_driver_add_timeout_cancelled (void)
{
  g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
  FpDevice *data_check = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
  GSource *source;

  g_object_add_weak_pointer (G_OBJECT (data_check), (gpointer) & data_check);
  source = fpi_device_add_timeout (device, 2000, test_driver_add_timeout_func,
                                   data_check, g_object_unref);

  g_timeout_add (20, test_driver_add_timeout_cancelled_timeout, source);
  g_assert_nonnull (data_check);

  while (FP_IS_DEVICE (data_check))
    g_main_context_iteration (NULL, TRUE);

  g_assert_null (data_check);
  g_assert_null (fake_dev->last_called_function);
}

static void
test_driver_error_types (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(GEnumClass) errors_enum = g_type_class_ref (FP_TYPE_DEVICE_ERROR);
  int i;

  for (i = 0; g_enum_get_value (errors_enum, i); ++i)
    {
      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*");
  error = fpi_device_error_new (i + 1);
  g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL);
  g_test_assert_expected_messages ();
}

static void
test_driver_retry_error_types (void)
{
  g_autoptr(GError) error = NULL;
  g_autoptr(GEnumClass) errors_enum = g_type_class_ref (FP_TYPE_DEVICE_RETRY);
  int i;

  for (i = 0; g_enum_get_value (errors_enum, i); ++i)
    {
      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*");
  error = fpi_device_retry_new (i + 1);
  g_assert_error (error, FP_DEVICE_RETRY, FP_DEVICE_RETRY_GENERAL);
  g_test_assert_expected_messages ();
}

int
main (int argc, char *argv[])
{
#ifdef TEST_TOD_DRIVER
  g_autoptr(FptContext) tctx = fpt_context_fake_dev_default ();
#endif

  g_test_init (&argc, &argv, NULL);

  g_test_add_func ("/driver/get_driver", test_driver_get_driver);
  g_test_add_func ("/driver/get_device_id", test_driver_get_device_id);
  g_test_add_func ("/driver/get_name", test_driver_get_name);
  g_test_add_func ("/driver/is_open", test_driver_is_open);
  g_test_add_func ("/driver/get_scan_type/press", test_driver_get_scan_type_press);
  g_test_add_func ("/driver/get_scan_type/swipe", test_driver_get_scan_type_swipe);
  g_test_add_func ("/driver/set_scan_type/press", test_driver_set_scan_type_press);
  g_test_add_func ("/driver/set_scan_type/swipe", test_driver_set_scan_type_swipe);
  g_test_add_func ("/driver/finger_status/inactive", test_driver_finger_status_inactive);
  g_test_add_func ("/driver/finger_status/waiting", test_driver_finger_status_needed);
  g_test_add_func ("/driver/finger_status/present", test_driver_finger_status_present);
  g_test_add_func ("/driver/finger_status/changes", test_driver_finger_status_changes);
  g_test_add_func ("/driver/get_nr_enroll_stages", test_driver_get_nr_enroll_stages);
  g_test_add_func ("/driver/set_nr_enroll_stages", test_driver_set_nr_enroll_stages);
  g_test_add_func ("/driver/supports_identify", test_driver_supports_identify);
  g_test_add_func ("/driver/supports_capture", test_driver_supports_capture);
  g_test_add_func ("/driver/has_storage", test_driver_has_storage);
  g_test_add_func ("/driver/do_not_support_identify", test_driver_do_not_support_identify);
  g_test_add_func ("/driver/do_not_support_capture", test_driver_do_not_support_capture);
  g_test_add_func ("/driver/has_not_storage", test_driver_has_not_storage);
  g_test_add_func ("/driver/get_usb_device", test_driver_get_usb_device);
  g_test_add_func ("/driver/get_virtual_env", test_driver_get_virtual_env);
  g_test_add_func ("/driver/get_driver_data", test_driver_get_driver_data);
  g_test_add_func ("/driver/features/probe_updates", test_driver_features_probe_updates);
  g_test_add_func ("/driver/initial_features", test_driver_initial_features);
  g_test_add_func ("/driver/initial_features/none", test_driver_initial_features_none);
  g_test_add_func ("/driver/initial_features/no_capture", test_driver_initial_features_no_capture);
  g_test_add_func ("/driver/initial_features/no_verify", test_driver_initial_features_no_verify);
  g_test_add_func ("/driver/initial_features/no_identify", test_driver_initial_features_no_identify);
  g_test_add_func ("/driver/initial_features/no_storage", test_driver_initial_features_no_storage);
  g_test_add_func ("/driver/initial_features/no_list", test_driver_initial_features_no_list);
  g_test_add_func ("/driver/initial_features/no_delete", test_driver_initial_features_no_delete);
  g_test_add_func ("/driver/initial_features/no_clear", test_driver_initial_features_no_clear);


  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/enroll/update_nbis", test_driver_enroll_update_nbis);
  g_test_add_func ("/driver/enroll/update_nbis_wrong_device",
                   test_driver_enroll_update_nbis_wrong_device);
  g_test_add_func ("/driver/enroll/update_nbis_wrong_driver",
                   test_driver_enroll_update_nbis_wrong_driver);
  g_test_add_func ("/driver/enroll/update_nbis_missing_feature",
                   test_driver_enroll_update_nbis_missing_feature);
  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/not_supported", test_driver_verify_not_supported);
  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/identify/suspend_continues", test_driver_identify_suspend_continues);
  g_test_add_func ("/driver/identify/suspend_succeeds", test_driver_identify_suspend_succeeds);
  g_test_add_func ("/driver/identify/suspend_busy_error", test_driver_identify_suspend_busy_error);
  g_test_add_func ("/driver/identify/suspend_while_idle", test_driver_identify_suspend_while_idle);

  g_test_add_func ("/driver/identify/warmup_cooldown", test_driver_identify_warmup_cooldown);

  g_test_add_func ("/driver/capture", test_driver_capture);
  g_test_add_func ("/driver/capture/not_supported", test_driver_capture_not_supported);
  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/clear_storage", test_driver_clear_storage);
  g_test_add_func ("/driver/clear_storage/error", test_driver_clear_storage_error);
  g_test_add_func ("/driver/cancel", test_driver_cancel);
  g_test_add_func ("/driver/cancel/fail", test_driver_cancel_fail);

  g_test_add_func ("/driver/critical", test_driver_critical);

  g_test_add_func ("/driver/get_current_action", test_driver_current_action);
  g_test_add_func ("/driver/get_current_action/open", test_driver_current_action_open);
  g_test_add_func ("/driver/get_cancellable/error", test_driver_action_get_cancellable_error);
  g_test_add_func ("/driver/get_cancellable/open", test_driver_action_get_cancellable_open);
  g_test_add_func ("/driver/get_cancellable/open/internal", test_driver_action_get_cancellable_open_internal);
  g_test_add_func ("/driver/action_is_cancelled/open", test_driver_action_is_cancelled_open);
  g_test_add_func ("/driver/action_is_cancelled/open/internal", test_driver_action_internally_cancelled_open);
  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/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);

  g_test_add_func ("/driver/error_types", test_driver_error_types);
  g_test_add_func ("/driver/retry_error_types", test_driver_retry_error_types);

  return g_test_run ();
}
