/*
 * Copyright 2015 Richard Hughes <richard@hughsie.com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "config.h"

#include <fwupdplugin.h>

#include <fcntl.h>
#include <glib/gstdio.h>
#include <string.h>

#include "fwupd-remote-private.h"

#include "../plugins/test/fu-test-plugin.h"
#include "fu-bios-settings-private.h"
#include "fu-cabinet.h"
#include "fu-client-list.h"
#include "fu-config-private.h"
#include "fu-console.h"
#include "fu-context-private.h"
#include "fu-device-list.h"
#include "fu-device-private.h"
#include "fu-drm-device-private.h"
#include "fu-efivars-private.h"
#include "fu-engine-config.h"
#include "fu-engine-helper.h"
#include "fu-engine-requirements.h"
#include "fu-engine.h"
#include "fu-history.h"
#include "fu-idle.h"
#include "fu-plugin-list.h"
#include "fu-plugin-private.h"
#include "fu-release-common.h"
#include "fu-remote-list.h"
#include "fu-remote.h"
#include "fu-security-attrs-private.h"
#include "fu-usb-backend.h"
#include "fu-util-common.h"

#ifdef HAVE_GIO_UNIX
#include "fu-unix-seekable-input-stream.h"
#endif

#pragma GCC diagnostic ignored "-Wanalyzer-null-argument"

typedef struct {
	FuPlugin *plugin;
	FuContext *ctx;
} FuTest;

/* nocheck:static */
static GMainLoop *_test_loop = NULL;
static guint _test_loop_timeout_id = 0;

static gboolean
fu_test_hang_check_cb(gpointer user_data)
{
	g_main_loop_quit(_test_loop);
	_test_loop_timeout_id = 0;
	return G_SOURCE_REMOVE;
}

static void
fu_test_loop_run_with_timeout(guint timeout_ms)
{
	g_assert_cmpint(_test_loop_timeout_id, ==, 0);
	g_assert_null(_test_loop);
	_test_loop = g_main_loop_new(NULL, FALSE);
	_test_loop_timeout_id = g_timeout_add(timeout_ms, fu_test_hang_check_cb, NULL);
	g_main_loop_run(_test_loop);
}

static void
fu_test_loop_quit(void)
{
	if (_test_loop_timeout_id > 0) {
		g_source_remove(_test_loop_timeout_id);
		_test_loop_timeout_id = 0;
	}
	if (_test_loop != NULL) {
		g_main_loop_quit(_test_loop);
		g_main_loop_unref(_test_loop);
		_test_loop = NULL;
	}
}

static void
fu_self_test_mkroot(void)
{
	if (g_file_test("/tmp/fwupd-self-test", G_FILE_TEST_EXISTS)) {
		g_autoptr(GError) error = NULL;
		if (!fu_path_rmtree("/tmp/fwupd-self-test", &error))
			g_warning("failed to mkroot: %s", error->message);
	}
	g_assert_cmpint(g_mkdir_with_parents("/tmp/fwupd-self-test/var/lib/fwupd", 0755), ==, 0);
}

static void
fu_test_copy_file(const gchar *source, const gchar *target)
{
	gboolean ret;
	g_autofree gchar *data = NULL;
	g_autoptr(GError) error = NULL;

	g_debug("copying %s to %s", source, target);
	ret = g_file_get_contents(source, &data, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = g_file_set_contents(target, data, -1, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_test_free(FuTest *self)
{
	if (self->ctx != NULL)
		g_object_unref(self->ctx);
	g_free(self);
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuTest, fu_test_free)
#pragma clang diagnostic pop

static void
fu_client_list_func(void)
{
	g_autoptr(FuClient) client_find = NULL;
	g_autoptr(FuClient) client = NULL;
	g_autoptr(FuClient) client_orig = NULL;
	g_autoptr(FuClientList) client_list = fu_client_list_new(NULL);
	g_autoptr(GPtrArray) clients_empty = NULL;
	g_autoptr(GPtrArray) clients_full = NULL;

	/* ensure empty */
	clients_empty = fu_client_list_get_all(client_list);
	g_assert_cmpint(clients_empty->len, ==, 0);

	/* register a client, then find it */
	client_orig = fu_client_list_register(client_list, ":hello");
	g_assert_nonnull(client_orig);
	client_find = fu_client_list_get_by_sender(client_list, ":hello");
	g_assert_nonnull(client_find);
	g_assert_true(client_orig == client_find);
	clients_full = fu_client_list_get_all(client_list);
	g_assert_cmpint(clients_full->len, ==, 1);

	/* register a duplicate, check properties */
	client = fu_client_list_register(client_list, ":hello");
	g_assert_nonnull(client);
	g_assert_true(client_orig == client);
	g_assert_cmpstr(fu_client_get_sender(client), ==, ":hello");
	g_assert_cmpint(fu_client_get_feature_flags(client), ==, FWUPD_FEATURE_FLAG_NONE);
	g_assert_cmpstr(fu_client_lookup_hint(client, "key"), ==, NULL);
	g_assert_true(fu_client_has_flag(client, FU_CLIENT_FLAG_ACTIVE));
	fu_client_insert_hint(client, "key", "value");
	fu_client_set_feature_flags(client, FWUPD_FEATURE_FLAG_UPDATE_ACTION);
	g_assert_cmpstr(fu_client_lookup_hint(client, "key"), ==, "value");
	g_assert_cmpint(fu_client_get_feature_flags(client), ==, FWUPD_FEATURE_FLAG_UPDATE_ACTION);

	/* emulate disconnect */
	fu_client_remove_flag(client, FU_CLIENT_FLAG_ACTIVE);
	g_assert_false(fu_client_has_flag(client, FU_CLIENT_FLAG_ACTIVE));
}

static void
fu_util_func(void)
{
	const gchar *tmp;
	g_autoptr(FwupdClient) client = fwupd_client_new();
	g_autoptr(FwupdDevice) device = fwupd_device_new();

	for (FwupdDeviceProblem i = 1; i < G_MAXUINT64; i <<= 1) {
		g_autofree gchar *str = NULL;
		tmp = fwupd_device_problem_to_string(i);
		if (tmp == NULL)
			break;
		str = fu_util_device_problem_to_string(client, device, i);
		g_assert_nonnull(str);
	}
	for (FwupdReleaseFlags i = 1; i < G_MAXUINT64; i <<= 1) {
		tmp = fwupd_release_flag_to_string(i);
		if (tmp == NULL)
			break;
		g_assert_nonnull(fu_util_release_flag_to_string(i));
	}
	for (FwupdRequestFlags i = 1; i < G_MAXUINT64; i <<= 1) {
		tmp = fwupd_request_flag_to_string(i);
		if (tmp == NULL)
			break;
		g_assert_nonnull(fu_util_request_flag_to_string(i));
	}
	for (FwupdPluginFlags i = 1; i < G_MAXUINT64; i <<= 1) {
		g_autofree gchar *str = NULL;
		if (i == FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE || i == FWUPD_PLUGIN_FLAG_USER_WARNING)
			continue;
		tmp = fwupd_plugin_flag_to_string(i);
		if (tmp == NULL)
			break;
		str = fu_util_plugin_flag_to_string(i);
		g_assert_nonnull(str);
	}
}

static void
fu_idle_func(void)
{
	guint token;
	g_autoptr(FuIdle) idle = fu_idle_new();

	fu_idle_reset(idle);
	g_assert_false(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_TIMEOUT));
	g_assert_false(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_SIGNALS));

	token = fu_idle_inhibit(idle, FU_IDLE_INHIBIT_TIMEOUT | FU_IDLE_INHIBIT_SIGNALS, NULL);
	g_assert_true(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_TIMEOUT));
	g_assert_true(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_SIGNALS));

	/* wrong token */
	fu_idle_uninhibit(idle, token + 1);
	g_assert_true(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_SIGNALS));

	/* correct token */
	fu_idle_uninhibit(idle, token);
	g_assert_false(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_TIMEOUT));
	g_assert_false(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_SIGNALS));

	/* locker section */
	{
		g_autoptr(FuIdleLocker) idle_locker1 =
		    fu_idle_locker_new(idle, FU_IDLE_INHIBIT_TIMEOUT, NULL);
		g_autoptr(FuIdleLocker) idle_locker2 =
		    fu_idle_locker_new(idle, FU_IDLE_INHIBIT_SIGNALS, NULL);
		g_assert_nonnull(idle_locker1);
		g_assert_nonnull(idle_locker2);
		g_assert_true(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_TIMEOUT));
		g_assert_true(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_SIGNALS));
	}
	g_assert_false(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_TIMEOUT));
	g_assert_false(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_SIGNALS));
}

static void
fu_engine_generate_md_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	const gchar *tmp;
	gboolean ret;
	g_autofree gchar *filename = NULL;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GBytes) data = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;

	/* put cab file somewhere we can parse it */
	filename = g_test_build_filename(G_TEST_BUILT,
					 "..",
					 "libfwupdplugin",
					 "tests",
					 "colorhug",
					 "colorhug-als-3.0.2.cab",
					 NULL);
	data = fu_bytes_get_contents(filename, &error);
	g_assert_no_error(error);
	g_assert_nonnull(data);
	ret = fu_bytes_set_contents("/tmp/fwupd-self-test/var/cache/fwupd/foo.cab", data, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* load engine and check the device was found */
	ret = fu_engine_load(engine,
			     FU_ENGINE_LOAD_FLAG_REMOTES | FU_ENGINE_LOAD_FLAG_NO_CACHE,
			     progress,
			     &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.3");
	component = fu_engine_get_component_by_guids(engine, device);
	g_assert_nonnull(component);

	/* check remote ID set */
	tmp = xb_node_query_text(component, "../custom/value[@key='fwupd::RemoteId']", NULL);
	g_assert_cmpstr(tmp, ==, "directory");

	/* verify checksums */
	tmp = xb_node_query_text(component, "releases/release/checksum[@target='container']", NULL);
	g_assert_cmpstr(tmp, ==, "71aefb2a9b412833d8c519d5816ef4c5668e5e76");
	tmp = xb_node_query_text(component, "releases/release/checksum[@target='content']", NULL);
	g_assert_cmpstr(tmp, ==, NULL);
}

static void
fu_engine_requirements_missing_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	const gchar *xml = "<component>"
			   "  <requires>"
			   "    <id compare=\"ge\" version=\"1.2.3\">not.going.to.exist</id>"
			   "  </requires>"
			   "  <releases>"
			   "    <release version=\"1.2.3\"/>"
			   "  </releases>"
			   "</component>";

	/* set up a dummy version */
	fu_engine_add_runtime_version(engine, "org.test.dummy", "1.2.3");

	/* make the component require one thing */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this fails */
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND);
	g_assert_false(ret);
}

static void
fu_engine_requirements_soft_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	const gchar *xml = "<component>"
			   "  <suggests>"
			   "    <id compare=\"ge\" version=\"1.2.3\">not.going.to.exist</id>"
			   "  </suggests>"
			   "  <releases>"
			   "    <release version=\"1.2.3\"/>"
			   "  </releases>"
			   "</component>";

	/* set up a dummy version */
	fu_engine_add_runtime_version(engine, "org.test.dummy", "1.2.3");

	/* make the component require one thing */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this passes */
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine,
					   release,
					   FWUPD_INSTALL_FLAG_IGNORE_REQUIREMENTS,
					   &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_engine_requirements_client_fail_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	const gchar *xml = "<component>"
			   "  <requires>"
			   "    <client>detach-action</client>"
			   "  </requires>"
			   "  <releases>"
			   "    <release version=\"1.2.3\"/>"
			   "  </releases>"
			   "</component>";

	/* make the component require one thing */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this fails */
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_false(ret);
}

static void
fu_engine_requirements_client_invalid_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	const gchar *xml = "<component>"
			   "  <requires>"
			   "    <client>hello-dave</client>"
			   "    <id compare=\"ge\" version=\"1.4.5\">org.freedesktop.fwupd</id>\n"
			   "  </requires>"
			   "  <releases>"
			   "    <release version=\"1.2.3\"/>"
			   "  </releases>"
			   "</component>";

	/* make the component require one thing */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this fails */
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND);
	g_assert_false(ret);
}

static void
fu_engine_requirements_client_pass_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	const gchar *xml = "<component>"
			   "  <requires>"
			   "    <client>detach-action</client>"
			   "    <id compare=\"ge\" version=\"1.4.5\">org.freedesktop.fwupd</id>\n"
			   "  </requires>"
			   "  <releases>"
			   "    <release version=\"1.2.3\"/>"
			   "  </releases>"
			   "</component>";

	/* set up a dummy version */
	fu_engine_request_set_feature_flags(request, FWUPD_FEATURE_FLAG_DETACH_ACTION);

	/* make the component require one thing */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this passes */
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_engine_requirements_vercmp_glob_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	const gchar *xml = "<component>"
			   "  <requires>"
			   "    <client>id-requirement-glob</client>"
			   "    <id compare=\"ge\" "
			   "version=\"1.8.*=1.8.5|1.9.*=1.9.7|2.0.13\">org.freedesktop.fwupd</id>\n"
			   "  </requires>"
			   "  <releases>"
			   "    <release version=\"1.2.3\"/>"
			   "  </releases>"
			   "</component>";

	/* hardcode to specific branch */
	fu_context_add_runtime_version(self->ctx, "org.freedesktop.fwupd", "1.9.8");

	/* make the component require one thing */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this passes */
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* reset back to reality */
	fu_context_add_runtime_version(self->ctx, "org.freedesktop.fwupd", VERSION);
}

static void
fu_engine_requirements_vercmp_glob_fallback_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	const gchar *xml = "<component>"
			   "  <requires>"
			   "    <id compare=\"ge\" "
			   "version=\"1.8.*=1.8.5|1.9.*=1.9.7|2.0.13\">org.freedesktop.fwupd</id>\n"
			   "    <client>id-requirement-glob</client>"
			   "  </requires>"
			   "  <releases>"
			   "    <release version=\"1.2.3\"/>"
			   "  </releases>"
			   "</component>";

	/* make the component require one thing */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this passes */
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_engine_requirements_not_hardware_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	const gchar *xml = "<component>"
			   "  <requires>"
			   "    <not_hardware>ffffffff-ffff-ffff-ffff-ffffffffffff</not_hardware>"
			   "    <id compare=\"ge\" version=\"1.9.10\">org.freedesktop.fwupd</id>\n"
			   "  </requires>"
			   "  <releases>"
			   "    <release version=\"1.2.3\"/>"
			   "  </releases>"
			   "</component>";

	/* set up a dummy version */
	fu_engine_request_set_feature_flags(request, FWUPD_FEATURE_FLAG_DETACH_ACTION);

	/* make the component require one thing */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this passes */
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_engine_requirements_phased_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(FwupdRemote) remote = fwupd_remote_new();
	g_autoptr(GError) error = NULL;
	const gchar *xml = "<component>"
			   "  <requires>"
			   "    <phased_update>10</phased_update>"
			   "    <id compare=\"ge\" version=\"2.0.17\">org.freedesktop.fwupd</id>\n"
			   "  </requires>"
			   "  <releases>"
			   "    <release version=\"1.2.3\"/>"
			   "  </releases>"
			   "</component>";

	/* do not include into seed */
	g_assert_cmpstr(fu_engine_get_host_machine_id(engine), ==, NULL);

	/* make the component require one thing */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this passes */
	fu_release_set_request(release, request);
	fu_release_set_remote(release, remote);
	fwupd_remote_set_mtime(remote, 12340);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* check this still passes as we're ignoring */
	fwupd_remote_set_mtime(remote, 12345);
	ret = fu_engine_requirements_check(engine,
					   release,
					   FWUPD_INSTALL_FLAG_IGNORE_REQUIREMENTS,
					   &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* check this now fails */
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_false(ret);
	g_clear_error(&error);

	/* user disabled this */
	fwupd_remote_add_flag(remote, FWUPD_REMOTE_FLAG_NO_PHASED_UPDATES);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_engine_requirements_phased_old_fwupd_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(FwupdRemote) remote = fwupd_remote_new();
	g_autoptr(GError) error = NULL;
	const gchar *xml = "<component>"
			   "  <requires>"
			   "    <phased_update>10</phased_update>"
			   "    <id compare=\"ge\" version=\"2.0.16\">org.freedesktop.fwupd</id>\n"
			   "  </requires>"
			   "  <releases>"
			   "    <release version=\"1.2.3\"/>"
			   "  </releases>"
			   "</component>";

	/* do not include into seed */
	g_assert_cmpstr(fu_engine_get_host_machine_id(engine), ==, NULL);

	/* make the component require one thing */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this passes */
	fu_release_set_request(release, request);
	fu_release_set_remote(release, remote);
	fwupd_remote_set_mtime(remote, 12340);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* check this fails because the fwupd requirement is too low */
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_false(ret);
}

static void
fu_engine_requirements_version_require_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	const gchar *xml =
	    "<component>"
	    "  <provides>"
	    "    <firmware type=\"flashed\">12345678-1234-1234-1234-123456789012</firmware>"
	    "  </provides>"
	    "  <releases>"
	    "    <release version=\"1.2.4\">"
	    "    </release>"
	    "  </releases>"
	    "</component>";

	/* set up a dummy device */
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.3");
	fu_device_set_version_bootloader(device, "4.5.6");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED);
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");

	/* make the component require one thing */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this fails */
	fu_release_set_device(release, device);
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_true(
	    g_str_has_prefix(error->message, "device requires firmware with a version check"));
	g_assert_false(ret);
}

static void
fu_engine_requirements_version_lowest_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	const gchar *xml =
	    "<component>"
	    "  <provides>"
	    "    <firmware type=\"flashed\">12345678-1234-1234-1234-123456789012</firmware>"
	    "  </provides>"
	    "  <releases>"
	    "    <release version=\"1.2.2\">"
	    "    </release>"
	    "  </releases>"
	    "</component>";

	/* set up a dummy device */
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.3");
	fu_device_set_version_lowest(device, "1.2.3");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");

	/* make the component require one thing */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this fails */
	fu_release_set_device(release, device);
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_release_check_version(release, component, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_true(
	    g_str_has_prefix(error->message, "Specified firmware is older than the minimum"));
	g_assert_false(ret);
}

static void
fu_engine_requirements_unsupported_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	const gchar *xml = "<component>"
			   "  <requires>"
			   "    <UNKNOWN compare=\"ge\" version=\"2.6.0\"/>"
			   "  </requires>"
			   "  <releases>"
			   "    <release version=\"1.2.3\"/>"
			   "  </releases>"
			   "</component>";

	/* set up a dummy version */
	fu_engine_add_runtime_version(engine, "org.test.dummy", "1.2.3");

	/* make the component require one thing that we don't support */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this fails */
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_false(ret);
}

static void
fu_engine_requirements_child_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuDevice) child = fu_device_new(NULL);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	const gchar *xml =
	    "<component>"
	    "  <requires>"
	    "    <firmware compare=\"eq\" version=\"0.0.1\">not-child</firmware>"
	    "    <id compare=\"ge\" version=\"1.2.11\">org.freedesktop.fwupd</id>\n"
	    "  </requires>"
	    "  <provides>"
	    "    <firmware type=\"flashed\">12345678-1234-1234-1234-123456789012</firmware>"
	    "  </provides>"
	    "  <releases>"
	    "    <release version=\"1.2.4\">"
	    "      <checksum type=\"sha1\" filename=\"bios.bin\" target=\"content\"/>"
	    "    </release>"
	    "  </releases>"
	    "</component>";

	/* set up a dummy device */
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.3");
	fu_device_set_version_bootloader(device, "4.5.6");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");

	fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(child, "0.0.999");
	fu_device_set_physical_id(child, "dummy");
	fu_device_add_child(device, child);

	/* make the component require three things */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this passes */
	fu_release_set_device(release, device);
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_engine_requirements_child_fail_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuDevice) child = fu_device_new(NULL);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	const gchar *xml =
	    "<component>"
	    "  <requires>"
	    "    <firmware compare=\"glob\" version=\"0.0.*\">not-child</firmware>"
	    "    <id compare=\"ge\" version=\"1.2.11\">org.freedesktop.fwupd</id>\n"
	    "  </requires>"
	    "  <provides>"
	    "    <firmware type=\"flashed\">12345678-1234-1234-1234-123456789012</firmware>"
	    "  </provides>"
	    "  <releases>"
	    "    <release version=\"1.2.4\">"
	    "      <checksum type=\"sha1\" filename=\"bios.bin\" target=\"content\"/>"
	    "    </release>"
	    "  </releases>"
	    "</component>";

	/* set up a dummy device */
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.3");
	fu_device_set_version_bootloader(device, "4.5.6");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");

	fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(child, "0.0.1");
	fu_device_set_physical_id(child, "dummy");
	fu_device_add_child(device, child);

	/* make the component require three things */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this passes */
	fu_release_set_device(release, device);
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_nonnull(
	    g_strstr_len(error->message, -1, "Not compatible with child device version"));
	g_assert_false(ret);
}

static void
fu_engine_requirements_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	const gchar *xml = "<component>"
			   "  <requires>"
			   "    <id compare=\"ge\" version=\"1.2.3\">org.test.dummy</id>"
			   "    <hardware>6ff95c9c-ae41-5f59-9d90-3ec1ea66091e</hardware>"
			   "    <id compare=\"ge\" version=\"1.0.1\">org.freedesktop.fwupd</id>\n"
			   "    <id compare=\"ge\" version=\"1.9.10\">org.freedesktop.fwupd</id>\n"
			   "  </requires>"
			   "  <releases>"
			   "    <release version=\"1.2.3\"/>"
			   "  </releases>"
			   "</component>";

	/* set up some dummy versions */
	fu_engine_add_runtime_version(engine, "org.test.dummy", "1.2.3");
	fu_engine_add_runtime_version(engine, "com.hughski.colorhug", "7.8.9");

	/* make the component require one thing */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this passes */
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_engine_requirements_device_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	const gchar *xml =
	    "<component>"
	    "  <requires>"
	    "    <firmware compare=\"ge\" version=\"1.2.3\"/>"
	    "    <firmware compare=\"eq\" version=\"4.5.6\">bootloader</firmware>"
	    "    <firmware compare=\"regex\" version=\"USB:0xFFFF|DMI:Lenovo\">vendor-id</firmware>"
#ifdef __linux__
	    "    <id compare=\"ge\" version=\"4.0.0\">org.kernel</id>"
#endif
	    "    <id compare=\"ge\" version=\"1.2.11\">org.freedesktop.fwupd</id>\n"
	    "  </requires>"
	    "  <provides>"
	    "    <firmware type=\"flashed\">12345678-1234-1234-1234-123456789012</firmware>"
	    "  </provides>"
	    "  <releases>"
	    "    <release version=\"1.2.4\">"
	    "      <checksum type=\"sha1\" filename=\"bios.bin\" target=\"content\"/>"
	    "    </release>"
	    "  </releases>"
	    "</component>";

	/* set up a dummy device */
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.3");
	fu_device_set_version_bootloader(device, "4.5.6");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_build_vendor_id_u16(device, "PCI", 0x0000);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED);
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");

	/* make the component require three things */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this passes */
	fu_release_set_device(release, device);
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* check this fails, as the wrong requirement is specified */
	fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_ENFORCE_REQUIRES);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_nonnull(g_strstr_len(error->message, -1, "child, parent or sibling requirement"));
	g_assert_false(ret);

#ifndef SUPPORTED_BUILD
	/* we can force this */
	g_clear_error(&error);
	ret = fu_engine_requirements_check(engine,
					   release,
					   FWUPD_INSTALL_FLAG_IGNORE_REQUIREMENTS,
					   &error);
	g_assert_no_error(error);
	g_assert_true(ret);
#endif
}

static void
fu_engine_requirements_device_plain_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	const gchar *xml =
	    "<component>"
	    "  <provides>"
	    "    <firmware type=\"flashed\">12345678-1234-1234-1234-123456789012</firmware>"
	    "  </provides>"
	    "  <releases>"
	    "    <release version=\"51H0AALB\">"
	    "      <checksum type=\"sha1\" filename=\"bios.bin\" target=\"content\"/>"
	    "      <checksum type=\"sha1\" filename=\"bios.cab\" target=\"container\"/>"
	    "    </release>"
	    "  </releases>"
	    "</component>";

	/* set up a dummy device */
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PLAIN);
	fu_device_set_version(device, "5101AALB");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");

	/* make the component require three things */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this passes */
	fu_release_set_device(release, device);
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpstr(fu_release_get_filename(release), ==, "bios.cab");
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_engine_requirements_version_format_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	const gchar *xml =
	    "<component>"
	    "  <provides>"
	    "    <firmware type=\"flashed\">12345678-1234-1234-1234-123456789012</firmware>"
	    "  </provides>"
	    "  <releases>"
	    "    <release version=\"1.2.4\">"
	    "      <checksum type=\"sha1\" filename=\"bios.bin\" target=\"content\"/>"
	    "    </release>"
	    "  </releases>"
	    "  <custom>"
	    "    <value key=\"LVFS::VersionFormat\">triplet</value>"
	    "  </custom>"
	    "</component>";

	/* set up a dummy device */
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_QUAD);
	fu_device_set_version(device, "1.2.3.4");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");

	/* make the component require three things */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this fails */
	fu_release_set_device(release, device);
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_release_check_version(release, component, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_nonnull(
	    g_strstr_len(error->message, -1, "Firmware version formats were different"));
	g_assert_false(ret);
}

static void
fu_engine_requirements_only_upgrade_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	const gchar *xml =
	    "<component>"
	    "  <provides>"
	    "    <firmware type=\"flashed\">12345678-1234-1234-1234-123456789012</firmware>"
	    "  </provides>"
	    "  <releases>"
	    "    <release version=\"1.2.3\"/>"
	    "  </releases>"
	    "</component>";

	/* set up a dummy device */
	fu_device_set_version(device, "1.2.4");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_ONLY_VERSION_UPGRADE);
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");

	/* make the component require three things */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this fails */
	fu_release_set_device(release, device);
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_release_check_version(release, component, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_nonnull(g_strstr_len(error->message, -1, "Device only supports version upgrades"));
	g_assert_false(ret);
}

static void
fu_engine_requirements_only_upgrade_reinstall_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	const gchar *xml =
	    "<component>"
	    "  <provides>"
	    "    <firmware type=\"flashed\">12345678-1234-1234-1234-123456789012</firmware>"
	    "  </provides>"
	    "  <releases>"
	    "    <release version=\"1.2.3\"/>"
	    "  </releases>"
	    "</component>";

	/* set up a dummy device */
	fu_device_set_version(device, "1.2.3");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_ONLY_VERSION_UPGRADE);
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");

	/* make the component require three things */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this fails */
	fu_release_set_device(release, device);
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_release_check_version(release, component, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_nonnull(g_strstr_len(error->message, -1, "Device only supports version upgrades"));
	g_assert_false(ret);
}

static void
fu_engine_plugin_device_gtype(FuTest *self, GType gtype)
{
	GType proxy_gtype;
	gboolean ret;
	g_autofree gchar *str = NULL;
	g_autoptr(FuDevice) device = NULL;
	g_autoptr(FuDeviceLocker) locker = NULL;
	g_autoptr(FuFirmware) firmware = NULL;
	g_autoptr(FuProgress) progress_tmp = fu_progress_new(G_STRLOC);
	g_autoptr(FuSecurityAttrs) attrs = fu_security_attrs_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(GHashTable) metadata_post = NULL;
	g_autoptr(GHashTable) metadata_pre = NULL;
	g_autoptr(GInputStream) stream = g_memory_input_stream_new();

	g_debug("loading %s", g_type_name(gtype));
	device = g_object_new(gtype, "context", self->ctx, "physical-id", "/sys", NULL);
	g_assert_nonnull(device);
	fu_device_set_plugin(device, "test");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_EMULATED);

	/* version convert */
	if (fu_device_get_version_format(device) != FWUPD_VERSION_FORMAT_UNKNOWN)
		fu_device_set_version_raw(device, 0);

	/* progress steps */
	fu_device_set_progress(device, progress_tmp);

	/* report metadata */
	metadata_pre = fu_device_report_metadata_pre(device);
	if (metadata_pre != NULL)
		g_debug("got %u metadata items", g_hash_table_size(metadata_pre));
	metadata_post = fu_device_report_metadata_post(device);
	if (metadata_post != NULL)
		g_debug("got %u metadata items", g_hash_table_size(metadata_post));

	/* security attrs */
	fu_device_add_security_attrs(device, attrs);

	/* quirk kvs */
	ret = fu_device_set_quirk_kv(device,
				     "NoGoingTo",
				     "Exist",
				     FU_CONTEXT_QUIRK_SOURCE_FALLBACK,
				     &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_false(ret);

	/* to string */
	str = fu_device_to_string(device);
	g_assert_nonnull(str);

	/* proxy required */
	proxy_gtype = fu_device_get_proxy_gtype(device);
	if (proxy_gtype != G_TYPE_INVALID && G_TYPE_FUNDAMENTAL(proxy_gtype) == G_TYPE_OBJECT) {
		g_autoptr(FuDevice) proxy =
		    g_object_new(proxy_gtype, "context", self->ctx, "physical-id", "/sys", NULL);
		fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_REFCOUNTED_PROXY);
		fu_device_set_proxy(device, proxy);
	}

	/* ->probe() and ->setup */
	locker = fu_device_locker_new(device, NULL);
	if (locker != NULL)
		g_debug("did ->probe() and ->setup()!");

	/* ->prepare(), ->attach(), ->detach, ->cleanup */
	if (fu_device_prepare(device, progress_tmp, FWUPD_INSTALL_FLAG_FORCE, NULL))
		g_debug("did ->prepare()");
	if (fu_device_attach_full(device, progress_tmp, NULL))
		g_debug("did ->attach()");
	if (fu_device_detach_full(device, progress_tmp, NULL))
		g_debug("did ->detach()");
	if (fu_device_cleanup(device, progress_tmp, FWUPD_INSTALL_FLAG_FORCE, NULL))
		g_debug("did ->cleanup()");

	/* ->prepare_firmware() */
	firmware = fu_device_prepare_firmware(device,
					      stream,
					      progress_tmp,
					      FU_FIRMWARE_PARSE_FLAG_NONE,
					      NULL);
	if (firmware == NULL) {
		GType firmware_gtype = fu_device_get_firmware_gtype(device);
		if (firmware_gtype != G_TYPE_INVALID)
			firmware = g_object_new(firmware_gtype, NULL);
	}
	if (firmware != NULL) {
		if (fu_device_write_firmware(device,
					     firmware,
					     progress_tmp,
					     FWUPD_INSTALL_FLAG_FORCE,
					     NULL)) {
			g_debug("did ->write_firmware()!");
		}
	}
}

static void
fu_engine_plugin_firmware_gtype(FuTest *self, GType gtype)
{
	gboolean ret;
	g_autoptr(FuFirmware) firmware = NULL;
	g_autoptr(GBytes) blob = NULL;
	g_autoptr(GBytes) fw = g_bytes_new_static((const guint8 *)"x", 1);
	g_autoptr(GError) error = NULL;
	const gchar *noxml[] = {
	    "FuArchiveFirmware",
	    "FuGenesysUsbhubFirmware",
	    "FuIntelThunderboltFirmware",
	    "FuIntelThunderboltNvm",
	    "FuUefiUpdateInfo",
	    NULL,
	};

	g_debug("loading %s", g_type_name(gtype));
	firmware = g_object_new(gtype, NULL);
	g_assert_nonnull(firmware);

	/* ensure we have data set even if parsing fails */
	fu_firmware_set_bytes(firmware, fw);

	/* version convert */
	if (fu_firmware_get_version_format(firmware) != FWUPD_VERSION_FORMAT_UNKNOWN)
		fu_firmware_set_version_raw(firmware, 0);

	/* parse nonsense */
	if (gtype != FU_TYPE_FIRMWARE &&
	    !fu_firmware_has_flag(FU_FIRMWARE(firmware), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION)) {
		ret = fu_firmware_parse_bytes(firmware,
					      fw,
					      0x0,
					      FU_FIRMWARE_PARSE_FLAG_NO_SEARCH |
						  FU_FIRMWARE_PARSE_FLAG_CACHE_STREAM,
					      NULL);
		g_assert_false(ret);
	}

	/* write */
	blob = fu_firmware_write(firmware, NULL);
	if (blob != NULL && g_bytes_get_size(blob) > 0)
		g_debug("saved 0x%x bytes", (guint)g_bytes_get_size(blob));

	/* export -> build */
	if (!g_strv_contains(noxml, g_type_name(gtype))) {
		g_autofree gchar *xml = fu_firmware_export_to_xml(
		    firmware,
		    FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG | FU_FIRMWARE_EXPORT_FLAG_ASCII_DATA,
		    NULL);
		if (xml != NULL) {
			ret = fu_firmware_build_from_xml(firmware, xml, &error);
			g_assert_no_error(error);
			g_assert_true(ret);
		}
	}
}

static void
fu_engine_test_plugin_mutable_enumeration(gconstpointer user_data)
{
	const gchar *fake_localconf_fn = "/tmp/fwupd-self-test/var/etc/fwupd/fwupd.conf";
	FuTest *self = (FuTest *)user_data;
	g_autoptr(FuEngine) engine = NULL;
	g_autoptr(FuPlugin) plugin = fu_plugin_new(NULL);
	g_autoptr(GError) error = NULL;
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	gboolean ret;

	/* ensure empty tree */
	fu_self_test_mkroot();

	g_unsetenv("CONFIGURATION_DIRECTORY");
	(void)g_setenv("FWUPD_SYSCONFDIR", "/tmp/fwupd-self-test", TRUE);

	ret = fu_path_mkdir_parent(fake_localconf_fn, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = g_file_set_contents(fake_localconf_fn,
				  "# use `man 5 fwupd.conf` for documentation\n"
				  "[fwupd]\n"
				  "RequireImmutableEnumeration=true\n",
				  -1,
				  &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	engine = fu_engine_new(self->ctx);
	g_assert_nonnull(engine);

	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* engine requires, plugin doesn't have */
	ret = fu_engine_plugin_allows_enumeration(engine, plugin);
	g_assert_true(ret);

	/* engine requires, plugin does have */
	fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_MUTABLE_ENUMERATION);
	ret = fu_engine_plugin_allows_enumeration(engine, plugin);
	g_assert_false(ret);

	/* clear config and reload engine */
	fu_self_test_mkroot();
	g_clear_object(&engine);

	engine = fu_engine_new(self->ctx);
	g_assert_nonnull(engine);

	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* engine requires, plugin does have */
	ret = fu_engine_plugin_allows_enumeration(engine, plugin);
	g_assert_true(ret);

	/* drop flag, engine shouldn't care */
	fu_plugin_remove_flag(plugin, FWUPD_PLUGIN_FLAG_MUTABLE_ENUMERATION);
	ret = fu_engine_plugin_allows_enumeration(engine, plugin);
	g_assert_true(ret);
}

static void
fu_engine_plugin_gtypes_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	GPtrArray *plugins;
	gboolean ret;
	g_autoptr(FuDrmDevice) drm_device = g_object_new(FU_TYPE_DRM_DEVICE, NULL);
	g_autoptr(FuEdid) edid = fu_edid_new();
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(FuSecurityAttrs) attrs = fu_security_attrs_new();
	g_autoptr(GArray) firmware_gtypes = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();
	const gchar *plugin_names_safe[] = {
	    "linux_lockdown",
	    "linux_sleep",
	    "linux_swap",
	    "linux_tainted",
	};
	const gchar *external_plugins[] = {
	    "flashrom",
	    "modem-manager",
	};

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* load these from the build directory, not the install directory */
	if (g_getenv("G_TEST_BUILDDIR") != NULL) {
		g_autoptr(GPtrArray) external_plugin_dirs = g_ptr_array_new_with_free_func(g_free);
		g_autofree gchar *external_plugindir = NULL;
		for (guint i = 0; i < G_N_ELEMENTS(external_plugins); i++) {
			g_ptr_array_add(external_plugin_dirs,
					g_test_build_filename(G_TEST_BUILT,
							      "..",
							      "plugins",
							      external_plugins[i],
							      NULL));
		}
		external_plugindir = fu_strjoin(",", external_plugin_dirs);
		(void)g_setenv("FWUPD_LIBDIR_PKG", external_plugindir, TRUE);
	}

	/* load all plugins */
	ret = fu_engine_load(engine,
			     FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS |
				 FU_ENGINE_LOAD_FLAG_EXTERNAL_PLUGINS,
			     progress,
			     &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	plugins = fu_engine_get_plugins(engine);
	g_assert_nonnull(plugins);
	g_assert_cmpint(plugins->len, >, 5);

	/* start up some "safe" plugins */
	for (guint i = 0; i < G_N_ELEMENTS(plugin_names_safe); i++) {
		FuPlugin *plugin = fu_engine_get_plugin_by_name(engine, plugin_names_safe[i], NULL);
		if (plugin != NULL) {
			ret = fu_plugin_runner_startup(plugin, progress, &error);
			g_assert_no_error(error);
			g_assert_true(ret);
		}
	}

	/* add security attrs where possible */
	for (guint i = 0; i < plugins->len; i++) {
		FuPlugin *plugin = g_ptr_array_index(plugins, i);
		fu_plugin_runner_add_security_attrs(plugin, attrs);
	}

	/* run the post-reboot action */
	for (guint i = 0; i < plugins->len; i++) {
		FuPlugin *plugin = g_ptr_array_index(plugins, i);
		g_autoptr(FuDevice) device_nop = fu_device_new(self->ctx);
		g_autoptr(GError) error_local = NULL;
		if (!fu_plugin_runner_reboot_cleanup(plugin, device_nop, &error_local))
			g_debug("ignoring: %s", error_local->message);
	}

	/* run the device unlock action */
	for (guint i = 0; i < plugins->len; i++) {
		FuPlugin *plugin = g_ptr_array_index(plugins, i);
		g_autoptr(FuDevice) device_nop = fu_device_new(self->ctx);
		g_autoptr(GError) error_local = NULL;
		if (!fu_plugin_runner_unlock(plugin, device_nop, &error_local))
			g_debug("ignoring: %s", error_local->message);
	}

	/* run the backend-device-added action */
	for (guint i = 0; i < plugins->len; i++) {
		FuPlugin *plugin = g_ptr_array_index(plugins, i);
		GArray *device_gtypes = fu_plugin_get_device_gtypes(plugin);
		if (device_gtypes == NULL || device_gtypes->len == 0) {
			g_autoptr(FuDevice) device_nop = fu_device_new(self->ctx);
			g_autoptr(GError) error_local = NULL;
			if (!fu_plugin_runner_backend_device_added(plugin,
								   device_nop,
								   progress,
								   &error_local))
				g_debug("ignoring: %s", error_local->message);
		}
	}

	/* register a DRM device as some plugins use these for quirks */
	fu_edid_set_pnp_id(edid, "PNP");
	fu_edid_set_eisa_id(edid, "IBM");
	fu_edid_set_product_name(edid, "Display");
	fu_edid_set_serial_number(edid, "123456");
	fu_edid_set_product_code(edid, 0x1234);
	fu_drm_device_set_edid(drm_device, edid);
	for (guint i = 0; i < plugins->len; i++) {
		FuPlugin *plugin = g_ptr_array_index(plugins, i);
		fu_plugin_runner_device_register(plugin, FU_DEVICE(drm_device));
	}

	/* create each custom device with a context only */
	for (guint i = 0; i < plugins->len; i++) {
		FuPlugin *plugin = g_ptr_array_index(plugins, i);
		GArray *device_gtypes = fu_plugin_get_device_gtypes(plugin);
		for (guint j = 0; device_gtypes != NULL && j < device_gtypes->len; j++) {
			GType gtype = g_array_index(device_gtypes, GType, j);
			fu_engine_plugin_device_gtype(self, gtype);
		}
	}

	/* create each firmware */
	firmware_gtypes = fu_context_get_firmware_gtypes(self->ctx);
	for (guint j = 0; j < firmware_gtypes->len; j++) {
		GType gtype = g_array_index(firmware_gtypes, GType, j);
		fu_engine_plugin_firmware_gtype(self, gtype);
	}
}

static void
fu_engine_requirements_sibling_device_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device1 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device2 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) unrelated_device3 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) parent = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release1 = fu_release_new();
	g_autoptr(FuRelease) release2 = fu_release_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();
	const gchar *xml =
	    "<component>"
	    "  <requires>"
	    "    <firmware depth=\"0\">1ff60ab2-3905-06a1-b476-0371f00c9e9b</firmware>"
	    "    <id compare=\"ge\" version=\"1.6.1\">org.freedesktop.fwupd</id>\n"
	    "  </requires>"
	    "  <provides>"
	    "    <firmware type=\"flashed\">12345678-1234-1234-1234-123456789012</firmware>"
	    "  </provides>"
	    "  <releases>"
	    "    <release version=\"1.2.4\">"
	    "      <checksum type=\"sha1\" filename=\"bios.bin\" target=\"content\"/>"
	    "    </release>"
	    "  </releases>"
	    "</component>";

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* set up a dummy device */
	fu_device_set_id(device1, "id1");
	fu_device_set_version_format(device1, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device1, "1.2.3");
	fu_device_build_vendor_id_u16(device1, "USB", 0xFFFF);
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_instance_id(device1, "12345678-1234-1234-1234-123456789012");
	fu_device_add_protocol(device1, "com.acme");
	fu_engine_add_device(engine, device1);

	/* setup the parent */
	fu_device_set_id(parent, "parent");
	fu_device_set_version_format(parent, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(parent, "1.0.0");
	fu_device_add_flag(parent, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(parent, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_instance_id(parent, "42f3d696-0b6f-4d69-908f-357f98ef115e");
	fu_device_add_protocol(parent, "com.acme");
	fu_device_add_child(parent, device1);
	fu_engine_add_device(engine, parent);

	/* set up a different device */
	fu_device_set_id(unrelated_device3, "id3");
	fu_device_build_vendor_id(unrelated_device3, "USB", "FFFF");
	fu_device_add_protocol(unrelated_device3, "com.acme");
	fu_device_set_name(unrelated_device3, "Foo bar device");
	fu_device_set_version_format(unrelated_device3, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(unrelated_device3, "1.5.3");
	fu_device_add_flag(unrelated_device3, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(unrelated_device3, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_instance_id(unrelated_device3, "3e455c08-352e-4a16-84d3-f04287289fa2");
	fu_engine_add_device(engine, unrelated_device3);

	/* import firmware metainfo */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this fails */
	fu_release_set_device(release1, device1);
	fu_release_set_request(release1, request);
	ret = fu_release_load(release1, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release1, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_false(ret);
	g_clear_error(&error);

	/* set up a sibling device */
	fu_device_set_id(device2, "id2");
	fu_device_build_vendor_id_u16(device2, "USB", 0xFFFF);
	fu_device_add_protocol(device2, "com.acme");
	fu_device_set_name(device2, "Secondary firmware");
	fu_device_set_version_format(device2, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device2, "4.5.6");
	fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_instance_id(device2, "1ff60ab2-3905-06a1-b476-0371f00c9e9b");
	fu_device_add_child(parent, device2);
	fu_engine_add_device(engine, device2);

	/* check this passes */
	fu_release_set_device(release2, device1);
	fu_release_set_request(release2, request);
	ret = fu_release_load(release2, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release2, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* check this still works, as a child requirement is specified */
	fu_device_add_private_flag(device1, FU_DEVICE_PRIVATE_FLAG_ENFORCE_REQUIRES);
	ret = fu_engine_requirements_check(engine, release2, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_engine_requirements_other_device_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device1 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device2 = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();
	const gchar *xml =
	    "<component>"
	    "  <requires>"
	    "    <firmware compare=\"gt\" "
	    "version=\"4.0.0\">1ff60ab2-3905-06a1-b476-0371f00c9e9b</firmware>"
	    "    <id compare=\"ge\" version=\"1.2.11\">org.freedesktop.fwupd</id>\n"
	    "  </requires>"
	    "  <provides>"
	    "    <firmware type=\"flashed\">12345678-1234-1234-1234-123456789012</firmware>"
	    "  </provides>"
	    "  <releases>"
	    "    <release version=\"1.2.4\">"
	    "      <checksum type=\"sha1\" filename=\"bios.bin\" target=\"content\"/>"
	    "    </release>"
	    "  </releases>"
	    "</component>";

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* set up a dummy device */
	fu_device_set_version_format(device1, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device1, "1.2.3");
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_instance_id(device1, "12345678-1234-1234-1234-123456789012");

	/* set up a different device */
	fu_device_set_id(device2, "id2");
	fu_device_build_vendor_id_u16(device2, "USB", 0xFFFF);
	fu_device_add_protocol(device2, "com.acme");
	fu_device_set_name(device2, "Secondary firmware");
	fu_device_set_version_format(device2, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device2, "4.5.6");
	fu_device_add_instance_id(device2, "1ff60ab2-3905-06a1-b476-0371f00c9e9b");
	fu_engine_add_device(engine, device2);

	/* import firmware metainfo */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this passes */
	fu_release_set_device(release, device1);
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_engine_requirements_protocol_check_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	g_autoptr(FuDevice) device1 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device2 = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(FuRelease) release1 = fu_release_new();
	g_autoptr(FuRelease) release2 = fu_release_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();
	gboolean ret;

	const gchar *xml =
	    "<component>"
	    "  <provides>"
	    "    <firmware type=\"flashed\">12345678-1234-1234-1234-123456789012</firmware>"
	    "  </provides>"
	    "  <releases>"
	    "    <release version=\"4.5.7\">"
	    "      <checksum type=\"sha1\" filename=\"bios.bin\" target=\"content\"/>"
	    "    </release>"
	    "  </releases>"
	    "  <custom>"
	    "    <value key=\"LVFS::UpdateProtocol\">org.bar</value>"
	    "  </custom>"

	    "</component>";

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	fu_device_set_id(device1, "NVME");
	fu_device_add_protocol(device1, "com.acme");
	fu_device_set_name(device1, "NVME device");
	fu_device_build_vendor_id(device1, "DMI", "ACME");
	fu_device_set_version_format(device1, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device1, "1.2.3");
	fu_device_add_instance_id(device1, "12345678-1234-1234-1234-123456789012");
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_engine_add_device(engine, device1);

	fu_device_set_id(device2, "UEFI");
	fu_device_add_protocol(device2, "org.bar");
	fu_device_set_name(device2, "UEFI device");
	fu_device_build_vendor_id(device2, "DMI", "ACME");
	fu_device_set_version_format(device2, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device2, "1.2.3");
	fu_device_add_instance_id(device2, "12345678-1234-1234-1234-123456789012");
	fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_engine_add_device(engine, device2);

	/* make sure both devices added */
	devices = fu_engine_get_devices(engine, &error);
	g_assert_no_error(error);
	g_assert_nonnull(devices);
	g_assert_cmpint(devices->len, ==, 2);

	/* import firmware metainfo */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this fails */
	fu_release_set_device(release1, device1);
	fu_release_set_request(release1, request);
	ret = fu_release_load(release1, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_false(ret);
	g_clear_error(&error);

	/* check this passes */
	fu_release_set_device(release2, device2);
	fu_release_set_request(release2, request);
	ret = fu_release_load(release2, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_engine_requirements_parent_device_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device1 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device2 = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();
	const gchar *xml =
	    "<component>"
	    "  <requires>"
	    "    <firmware depth=\"1\" compare=\"eq\" version=\"1.2.3\"/>"
	    "    <firmware depth=\"1\">12345678-1234-1234-1234-123456789012</firmware>"
	    "    <id compare=\"ge\" version=\"1.3.4\">org.freedesktop.fwupd</id>\n"
	    "  </requires>"
	    "  <provides>"
	    "    <firmware type=\"flashed\">1ff60ab2-3905-06a1-b476-0371f00c9e9b</firmware>"
	    "  </provides>"
	    "  <releases>"
	    "    <release version=\"4.5.7\">"
	    "      <checksum type=\"sha1\" filename=\"bios.bin\" target=\"content\"/>"
	    "    </release>"
	    "  </releases>"
	    "</component>";

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* set up child device */
	fu_device_set_id(device2, "child");
	fu_device_set_name(device2, "child");
	fu_device_set_version_format(device2, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device2, "4.5.6");
	fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_instance_id(device2, "1ff60ab2-3905-06a1-b476-0371f00c9e9b");

	/* set up a parent device */
	fu_device_set_id(device1, "parent");
	fu_device_build_vendor_id_u16(device1, "USB", 0xFFFF);
	fu_device_add_protocol(device1, "com.acme");
	fu_device_set_name(device1, "parent");
	fu_device_set_version_format(device1, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device1, "1.2.3");
	fu_device_add_instance_id(device1, "12345678-1234-1234-1234-123456789012");
	fu_device_add_child(device1, device2);
	fu_engine_add_device(engine, device1);

	/* import firmware metainfo */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this passes */
	fu_release_set_device(release, device2);
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_engine_requirements_child_device_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device1 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device2 = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();
	const gchar *xml =
	    "<component>"
	    "  <requires>"
	    "    <firmware depth=\"-1\">1ff60ab2-3905-06a1-b476-0371f00c9e9b</firmware>"
	    "    <id compare=\"ge\" version=\"1.9.7\">org.freedesktop.fwupd</id>\n"
	    "  </requires>"
	    "  <provides>"
	    "    <firmware type=\"flashed\">12345678-1234-1234-1234-123456789012</firmware>"
	    "  </provides>"
	    "  <releases>"
	    "    <release version=\"4.5.7\">"
	    "      <checksum type=\"sha1\" filename=\"bios.bin\" target=\"content\"/>"
	    "    </release>"
	    "  </releases>"
	    "</component>";

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* set up a parent device */
	fu_device_set_id(device1, "parent");
	fu_device_build_vendor_id_u16(device1, "USB", 0xFFFF);
	fu_device_add_protocol(device1, "com.acme");
	fu_device_set_name(device1, "parent");
	fu_device_set_version_format(device1, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device1, "1.2.3");
	fu_device_add_instance_id(device1, "12345678-1234-1234-1234-123456789012");
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);

	/* set up child device */
	fu_device_set_id(device2, "child");
	fu_device_set_name(device2, "child");
	fu_device_set_version_format(device2, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device2, "4.5.6");
	fu_device_add_instance_id(device2, "1ff60ab2-3905-06a1-b476-0371f00c9e9b");
	fu_device_add_child(device1, device2);
	fu_engine_add_device(engine, device1);

	/* import firmware metainfo */
	silo = xb_silo_new_from_xml(xml, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	component = xb_silo_query_first(silo, "component", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check this passes */
	fu_release_set_device(release, device1);
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_engine_device_parent_guid_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	g_autoptr(FuDevice) device1 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device2 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device3 = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* add child */
	fu_device_set_id(device1, "child");
	fu_device_build_vendor_id_u16(device2, "USB", 0xFFFF);
	fu_device_add_protocol(device2, "com.acme");
	fu_device_add_instance_id(device1, "child-GUID-1");
	fu_device_add_parent_guid(device1, "parent-GUID");
	fu_engine_add_device(engine, device1);

	/* parent */
	fu_device_set_id(device2, "parent");
	fu_device_build_vendor_id_u16(device2, "USB", 0xFFFF);
	fu_device_add_protocol(device2, "com.acme");
	fu_device_add_instance_id(device2, "parent-GUID");
	fu_device_set_vendor(device2, "oem");

	/* add another child */
	fu_device_set_id(device3, "child2");
	fu_device_add_instance_id(device3, "child-GUID-2");
	fu_device_add_parent_guid(device3, "parent-GUID");
	fu_device_add_child(device2, device3);

	/* add two together */
	fu_engine_add_device(engine, device2);

	/* this is normally done by fu_plugin_device_add() */
	fu_engine_add_device(engine, device3);

	/* verify both children were adopted */
	g_assert_true(fu_device_get_parent_internal(device3) == device2);
	g_assert_true(fu_device_get_parent_internal(device1) == device2);
	g_assert_cmpstr(fu_device_get_vendor(device3), ==, "oem");

	/* verify order */
	g_assert_cmpint(fu_device_get_order(device1), ==, -1);
	g_assert_cmpint(fu_device_get_order(device2), ==, 0);
	g_assert_cmpint(fu_device_get_order(device3), ==, -1);
}

static void
fu_engine_device_parent_id_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	g_autoptr(FuDevice) device1 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device2 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device3 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device4 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device5 = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* add child */
	fu_device_set_id(device1, "child1");
	fu_device_set_name(device1, "Child1");
	fu_device_set_physical_id(device1, "child-ID1");
	fu_device_build_vendor_id_u16(device1, "USB", 0xFFFF);
	fu_device_add_protocol(device1, "com.acme");
	fu_device_add_instance_id(device1, "child-GUID-1");
	fu_device_add_parent_physical_id(device1, "parent-ID-notfound");
	fu_device_add_parent_physical_id(device1, "parent-ID");
	fu_engine_add_device(engine, device1);

	/* parent */
	fu_device_set_id(device2, "parent");
	fu_device_set_name(device2, "Parent");
	fu_device_set_backend_id(device2, "/sys/devices/foo/bar/baz");
	fu_device_set_physical_id(device2, "parent-ID");
	fu_device_build_vendor_id_u16(device2, "USB", 0xFFFF);
	fu_device_add_protocol(device2, "com.acme");
	fu_device_add_instance_id(device2, "parent-GUID");
	fu_device_set_vendor(device2, "oem");
	fu_device_add_private_flag(device2, FU_DEVICE_PRIVATE_FLAG_AUTO_PARENT_CHILDREN);

	/* add another child */
	fu_device_set_id(device3, "child2");
	fu_device_set_name(device3, "Child2");
	fu_device_set_physical_id(device3, "child-ID2");
	fu_device_add_instance_id(device3, "child-GUID-2");
	fu_device_add_parent_physical_id(device3, "parent-ID");
	fu_device_add_child(device2, device3);

	/* add two together */
	fu_engine_add_device(engine, device2);

	/* add non-child */
	fu_device_set_id(device4, "child4");
	fu_device_set_name(device4, "Child4");
	fu_device_set_physical_id(device4, "child-ID4");
	fu_device_build_vendor_id(device4, "USB", "FFFF");
	fu_device_add_protocol(device4, "com.acme");
	fu_device_add_instance_id(device4, "child-GUID-4");
	fu_device_add_parent_physical_id(device4, "parent-ID");
	fu_engine_add_device(engine, device4);

	/* this is normally done by fu_plugin_device_add() */
	fu_engine_add_device(engine, device4);

	/* add child with the parent backend ID */
	fu_device_set_id(device5, "child5");
	fu_device_set_name(device5, "Child5");
	fu_device_set_physical_id(device5, "child-ID5");
	fu_device_build_vendor_id(device5, "USB", "FFFF");
	fu_device_add_protocol(device5, "com.acme");
	fu_device_add_instance_id(device5, "child-GUID-5");
	fu_device_add_parent_backend_id(device5, "/sys/devices/foo/bar/baz");
	fu_engine_add_device(engine, device5);

	/* this is normally done by fu_plugin_device_add() */
	fu_engine_add_device(engine, device5);

	/* verify both children were adopted */
	g_assert_true(fu_device_get_parent_internal(device3) == device2);
	g_assert_true(fu_device_get_parent_internal(device4) == device2);
	g_assert_true(fu_device_get_parent_internal(device5) == device2);
	g_assert_true(fu_device_get_parent_internal(device1) == device2);
	g_assert_cmpstr(fu_device_get_vendor(device3), ==, "oem");
}

static void
fu_engine_partial_hash_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device1 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device2 = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuPlugin) plugin = fu_plugin_new(NULL);
	g_autoptr(GError) error = NULL;
	g_autoptr(GError) error_none = NULL;
	g_autoptr(GError) error_both = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* set up dummy plugin */
	fu_plugin_set_name(plugin, "test");
	fu_engine_add_plugin(engine, plugin);

	/* add two dummy devices */
	fu_device_set_id(device1, "device1");
	fu_device_build_vendor_id_u16(device1, "USB", 0xFFFF);
	fu_device_add_protocol(device1, "com.acme");
	fu_device_set_plugin(device1, "test");
	fu_device_add_instance_id(device1, "12345678-1234-1234-1234-123456789012");
	fu_device_set_id(device1, "99249eb1bd9ef0b6e192b271a8cb6a3090cfec7a");
	fu_engine_add_device(engine, device1);
	fu_device_set_id(device2, "device21");
	fu_device_build_vendor_id_u16(device2, "USB", 0xFFFF);
	fu_device_add_protocol(device2, "com.acme");
	fu_device_set_plugin(device2, "test");
	fu_device_set_equivalent_id(device2, "b92f5b7560b84ca005a79f5a15de3c003ce494cf");
	fu_device_add_instance_id(device2, "87654321-1234-1234-1234-123456789012");
	fu_device_set_id(device2, "99244162a6daa0b033d649c8d464529cec41d3de");
	fu_engine_add_device(engine, device2);

	/* match nothing */
	ret = fu_engine_unlock(engine, "deadbeef", &error_none);
	g_assert_error(error_none, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND);
	g_assert_false(ret);

	/* match both */
	ret = fu_engine_unlock(engine, "9924", &error_both);
	g_assert_error(error_both, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_false(ret);

	/* match one exactly */
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_LOCKED);
	fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_LOCKED);
	ret = fu_engine_unlock(engine, "99244162a6daa0b033d649c8d464529cec41d3de", &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* match one partially */
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_LOCKED);
	fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_LOCKED);
	ret = fu_engine_unlock(engine, "99249", &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* match equivalent ID */
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_LOCKED);
	fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_LOCKED);
	ret = fu_engine_unlock(engine, "b92f", &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_engine_device_unlock_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	FwupdRelease *rel;
	gboolean ret;
	g_autofree gchar *filename = NULL;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(GFile) file = NULL;
	g_autoptr(XbBuilder) builder = xb_builder_new();
	g_autoptr(XbBuilderSource) source = xb_builder_source_new();
	g_autoptr(XbSilo) silo = NULL;

	/* load engine to get FuConfig set up */
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add the hardcoded 'fwupd' metadata */
	filename = g_test_build_filename(G_TEST_DIST, "tests", "metadata.xml", NULL);
	file = g_file_new_for_path(filename);
	ret = xb_builder_source_load_file(source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	xb_builder_import_source(builder, source);
	silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	fu_engine_set_silo(engine, silo);

	/* add a dummy device */
	fu_device_set_id(device, "UEFI-dummy-dev0");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_add_instance_id(device, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_LOCKED);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PLAIN);
	fu_engine_add_device(engine, device);

	/* ensure the metainfo was matched */
	rel = fwupd_device_get_release_default(FWUPD_DEVICE(device));
	g_assert_nonnull(rel);
	g_assert_false(fwupd_release_has_flag(rel, FWUPD_RELEASE_FLAG_TRUSTED_REPORT));
}

static void
fu_engine_device_equivalent_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device1 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device2 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device_best = NULL;
	g_autoptr(FuDevice) device_worst = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(GError) error = NULL;

	/* load engine to get FuConfig set up */
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add a wireless (worse) device */
	fu_device_set_id(device1, "99249eb1bd9ef0b6e192b271a8cb6a3090cfec7a");
	fu_device_set_name(device1, "device1");
	fu_device_build_vendor_id_u16(device1, "USB", 0xFFFF);
	fu_device_add_protocol(device1, "com.acme");
	fu_device_add_instance_id(device1, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e");
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_engine_add_device(engine, device1);

	/* add a wired (better) device */
	fu_device_set_id(device2, "1a8d0d9a96ad3e67ba76cf3033623625dc6d6882");
	fu_device_set_name(device2, "device2");
	fu_device_set_equivalent_id(device2, "99249eb1bd9ef0b6e192b271a8cb6a3090cfec7a");
	fu_device_set_priority(device2, 999);
	fu_device_build_vendor_id_u16(device2, "USB", 0xFFFF);
	fu_device_add_protocol(device2, "com.acme");
	fu_device_add_instance_id(device2, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e");
	fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_engine_add_device(engine, device2);

	/* make sure the daemon chooses the best device */
	devices = fu_engine_get_devices(engine, &error);
	g_assert_no_error(error);
	g_assert_nonnull(devices);
	g_assert_cmpint(devices->len, ==, 2);
	device_best = fu_engine_get_device(engine, "9924", &error);
	g_assert_no_error(error);
	g_assert_nonnull(device_best);
	g_assert_cmpstr(fu_device_get_id(device_best),
			==,
			"1a8d0d9a96ad3e67ba76cf3033623625dc6d6882");
	g_assert_true(fu_device_has_flag(device_best, FWUPD_DEVICE_FLAG_UPDATABLE));
	g_assert_false(fu_device_has_problem(device_best, FWUPD_DEVICE_PROBLEM_LOWER_PRIORITY));

	/* get the worst device and make sure it's not updatable */
	for (guint i = 0; i < devices->len; i++) {
		FuDevice *device_tmp = g_ptr_array_index(devices, i);
		if (device_tmp != device_best) {
			device_worst = g_object_ref(device_tmp);
			break;
		}
	}
	g_assert_nonnull(device_worst);
	g_assert_false(fu_device_has_flag(device_worst, FWUPD_DEVICE_FLAG_UPDATABLE));
	g_assert_true(fu_device_has_problem(device_worst, FWUPD_DEVICE_PROBLEM_LOWER_PRIORITY));
}

static void
fu_engine_device_md_set_flags_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(XbBuilder) builder = xb_builder_new();
	g_autoptr(XbBuilderSource) source = xb_builder_source_new();
	g_autoptr(XbSilo) silo = NULL;
	const gchar *xml =
	    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
	    "<components version=\"0.9\">\n"
	    "  <component type=\"firmware\">\n"
	    "    <id>org.fwupd.8330a096d9f1af8567c7374cb8403e1ce9cf3163.device</id>\n"
	    "    <provides>\n"
	    "      <firmware type=\"flashed\">2d47f29b-83a2-4f31-a2e8-63474f4d4c2e</firmware>\n"
	    "    </provides>\n"
	    "    <releases>\n"
	    "      <release version=\"1\" />\n"
	    "    </releases>\n"
	    "    <custom>\n"
	    "      <value key=\"LVFS::DeviceFlags\">save-into-backup-remote</value>\n"
	    "    </custom>\n"
	    "  </component>\n"
	    "</components>\n";

	/* load engine to get FuConfig set up */
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add the XML metadata */
	ret = xb_builder_source_load_xml(source, xml, XB_BUILDER_SOURCE_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	xb_builder_import_source(builder, source);
	silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	fu_engine_set_silo(engine, silo);

	/* add a dummy device */
	fu_device_set_id(device, "UEFI-dummy-dev0");
	fu_device_set_version(device, "0");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_add_instance_id(device, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_SET_FLAGS);
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PLAIN);
	fu_engine_add_device(engine, device);

	/* check the flag got set */
	g_assert_true(
	    fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_SAVE_INTO_BACKUP_REMOTE));
}

static void
fu_engine_device_md_checksum_set_version_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(XbBuilder) builder = xb_builder_new();
	g_autoptr(XbBuilderSource) source = xb_builder_source_new();
	g_autoptr(XbSilo) silo = NULL;
	const gchar *xml =
	    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
	    "<components version=\"0.9\">\n"
	    "  <component type=\"firmware\">\n"
	    "    <id>org.fwupd.8330a096d9f1af8567c7374cb8403e1ce9cf3163.device</id>\n"
	    "    <provides>\n"
	    "      <firmware type=\"flashed\">2d47f29b-83a2-4f31-a2e8-63474f4d4c2e</firmware>\n"
	    "    </provides>\n"
	    "    <releases>\n"
	    "      <release version=\"124\">\n"
	    "        <location>https://test.org/foo.cab</location>"
	    "        <checksum type=\"sha256\" "
	    "target=\"device\">cdb7c90d3ab8833d5324f5d8516d41fa990b9ca721fe643fffaef9057d9f9e48</"
	    "checksum>\n"
	    "      </release>\n"
	    "    </releases>\n"
	    "    <custom>\n"
	    "      <value key=\"LVFS::UpdateProtocol\">com.acme</value>\n"
	    "      <value key=\"LVFS::VersionFormat\">plain</value>"
	    "    </custom>\n"
	    "  </component>\n"
	    "</components>\n";

	/* load engine to get FuConfig set up */
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add the XML metadata */
	ret = xb_builder_source_load_xml(source, xml, XB_BUILDER_SOURCE_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	xb_builder_import_source(builder, source);
	silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	fu_engine_set_silo(engine, silo);

	/* add a dummy device */
	fu_device_set_id(device, "UEFI-dummy-dev0");
	fu_device_set_version(device, "123");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_add_instance_id(device, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_ONLY_CHECKSUM);
	fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_SET_VERSION);
	fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_SET_VERFMT);
	fu_device_add_checksum(device,
			       "cdb7c90d3ab8833d5324f5d8516d41fa990b9ca721fe643fffaef9057d9f9e48");
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_NUMBER);
	fu_engine_add_device(engine, device);

	/* check the version got set */
	g_assert_cmpstr(fu_device_get_version(device), ==, "124");
	g_assert_cmpint(fu_device_get_version_format(device), ==, FWUPD_VERSION_FORMAT_PLAIN);
}

static void
fu_engine_device_md_checksum_set_version_wrong_proto_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(XbBuilder) builder = xb_builder_new();
	g_autoptr(XbBuilderSource) source = xb_builder_source_new();
	g_autoptr(XbSilo) silo = NULL;
	const gchar *xml =
	    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
	    "<components version=\"0.9\">\n"
	    "  <component type=\"firmware\">\n"
	    "    <id>org.fwupd.8330a096d9f1af8567c7374cb8403e1ce9cf3163.device</id>\n"
	    "    <provides>\n"
	    "      <firmware type=\"flashed\">2d47f29b-83a2-4f31-a2e8-63474f4d4c2e</firmware>\n"
	    "    </provides>\n"
	    "    <releases>\n"
	    "      <release version=\"124\">\n"
	    "        <location>https://test.org/foo.cab</location>"
	    "        <checksum type=\"sha256\" "
	    "target=\"device\">cdb7c90d3ab8833d5324f5d8516d41fa990b9ca721fe643fffaef9057d9f9e48</"
	    "checksum>\n"
	    "      </release>\n"
	    "    </releases>\n"
	    "    <custom>\n"
	    "      <value key=\"LVFS::UpdateProtocol\">com.acme</value>\n"
	    "      <value key=\"LVFS::VersionFormat\">plain</value>"
	    "    </custom>\n"
	    "  </component>\n"
	    "</components>\n";

	/* load engine to get FuConfig set up */
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add the XML metadata */
	ret = xb_builder_source_load_xml(source, xml, XB_BUILDER_SOURCE_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	xb_builder_import_source(builder, source);
	silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	fu_engine_set_silo(engine, silo);

	/* add a dummy device */
	fu_device_set_id(device, "UEFI-dummy-dev0");
	fu_device_set_version(device, "123");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "SOMETHING_ELSE_ENTIRELY");
	fu_device_add_instance_id(device, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_ONLY_CHECKSUM);
	fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_SET_VERSION);
	fu_device_add_checksum(device,
			       "cdb7c90d3ab8833d5324f5d8516d41fa990b9ca721fe643fffaef9057d9f9e48");
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_NUMBER);
	fu_engine_add_device(engine, device);

	/* check the version did not get set, because the protocol was different */
	g_assert_cmpstr(fu_device_get_version(device), ==, "123");
	g_assert_cmpint(fu_device_get_version_format(device), ==, FWUPD_VERSION_FORMAT_NUMBER);
}

static void
fu_engine_require_hwid_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autofree gchar *filename = NULL;
	g_autoptr(FuCabinet) cabinet = NULL;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(GInputStream) stream = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* load engine to get FuConfig set up */
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* get generated file as a blob */
	filename =
	    g_test_build_filename(G_TEST_BUILT, "tests", "missing-hwid", "hwid-1.2.3.cab", NULL);
	stream = fu_input_stream_from_path(filename, &error);
	g_assert_no_error(error);
	g_assert_nonnull(stream);
	cabinet = fu_engine_build_cabinet_from_stream(engine, stream, &error);
	g_assert_no_error(error);
	g_assert_nonnull(cabinet);

	/* add a dummy device */
	fu_device_set_id(device, "test_device");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.2");
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_engine_add_device(engine, device);

	/* get component */
	component = fu_cabinet_get_component(cabinet, "com.hughski.test.firmware", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* check requirements */
	fu_release_set_device(release, device);
	fu_release_set_request(release, request);
	ret = fu_release_load(release, NULL, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE);
	g_assert_nonnull(error);
	g_assert_cmpstr(error->message,
			==,
			"no HWIDs matched 9342d47a-1bab-5709-9869-c840b2eac501");
	g_assert_false(ret);
}

static void
fu_engine_get_details_added_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	FuDevice *device_tmp;
	FwupdRelease *release;
	gboolean ret;
	g_autofree gchar *checksum_sha256 = NULL;
	g_autofree gchar *filename = NULL;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(GInputStream) stream = NULL;
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* load engine to get FuConfig set up */
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add a dummy device */
	fu_device_set_id(device, "test_device");
	fu_device_set_name(device, "test device");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.2");
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_engine_add_device(engine, device);

	/* get details */
	filename =
	    g_test_build_filename(G_TEST_BUILT, "tests", "missing-hwid", "hwid-1.2.3.cab", NULL);
	stream = fu_input_stream_from_path(filename, &error);
	g_assert_no_error(error);
	g_assert_nonnull(stream);
	checksum_sha256 = fu_input_stream_compute_checksum(stream, G_CHECKSUM_SHA256, &error);
	g_assert_no_error(error);
	g_assert_nonnull(checksum_sha256);
	devices = fu_engine_get_details(engine, request, stream, &error);
	g_assert_no_error(error);
	g_assert_nonnull(devices);
	g_assert_cmpint(devices->len, ==, 1);
	device_tmp = g_ptr_array_index(devices, 0);
	g_assert_cmpstr(fu_device_get_name(device_tmp), ==, "test device");
	release = fu_device_get_release_default(device_tmp);
	g_assert_nonnull(release);
	g_assert_cmpstr(fwupd_release_get_version(release), ==, "1.2.3");
	g_assert_true(fwupd_release_has_checksum(release, checksum_sha256));
}

static void
fu_engine_get_details_missing_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	FuDevice *device_tmp;
	FwupdRelease *release;
	gboolean ret;
	g_autofree gchar *filename = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GInputStream) stream = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* load engine to get FuConfig set up */
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* get details */
	filename =
	    g_test_build_filename(G_TEST_BUILT, "tests", "missing-hwid", "hwid-1.2.3.cab", NULL);
	stream = fu_input_stream_from_path(filename, &error);
	g_assert_no_error(error);
	g_assert_nonnull(stream);
	devices = fu_engine_get_details(engine, request, stream, &error);
	g_assert_no_error(error);
	g_assert_nonnull(devices);
	g_assert_cmpint(devices->len, ==, 1);
	device_tmp = g_ptr_array_index(devices, 0);
	g_assert_cmpstr(fu_device_get_name(device_tmp), ==, NULL);
	release = fu_device_get_release_default(device_tmp);
	g_assert_nonnull(release);
	g_assert_cmpstr(fwupd_release_get_version(release), ==, "1.2.3");
}

static void
fu_engine_downgrade_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	FwupdRelease *rel;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(GPtrArray) devices_pre = NULL;
	g_autoptr(GPtrArray) releases_dg = NULL;
	g_autoptr(GPtrArray) releases = NULL;
	g_autoptr(GPtrArray) releases_up = NULL;
	g_autoptr(GPtrArray) releases_up2 = NULL;
	g_autoptr(GPtrArray) remotes = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* ensure empty tree */
	fu_self_test_mkroot();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* write a broken file */
	ret = g_file_set_contents("/tmp/fwupd-self-test/broken.xml.gz",
				  "this is not a valid",
				  -1,
				  &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* write the main file */
	ret = g_file_set_contents(
	    "/tmp/fwupd-self-test/stable.xml",
	    "<components>"
	    "  <component type=\"firmware\">"
	    "    <id>test</id>"
	    "    <name>Test Device</name>"
	    "    <provides>"
	    "      <firmware type=\"flashed\">aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee</firmware>"
	    "    </provides>"
	    "    <releases>"
	    "      <release version=\"1.2.3\" date=\"2017-09-15\">"
	    "        <size type=\"installed\">123</size>"
	    "        <size type=\"download\">456</size>"
	    "        <location>https://test.org/foo.cab</location>"
	    "        <checksum filename=\"foo.cab\" target=\"container\" "
	    "type=\"md5\">deadbeefdeadbeefdeadbeefdead1111</checksum>"
	    "        <checksum filename=\"firmware.bin\" target=\"content\" "
	    "type=\"md5\">deadbeefdeadbeefdeadbeefdeadbeef</checksum>"
	    "      </release>"
	    "      <release version=\"1.2.2\" date=\"2017-09-01\">"
	    "        <size type=\"installed\">123</size>"
	    "        <size type=\"download\">456</size>"
	    "        <location>https://test.org/foo.cab</location>"
	    "        <checksum filename=\"foo.cab\" target=\"container\" "
	    "type=\"md5\">deadbeefdeadbeefdeadbeefdead2222</checksum>"
	    "        <checksum filename=\"firmware.bin\" target=\"content\" "
	    "type=\"md5\">deadbeefdeadbeefdeadbeefdeadbeef</checksum>"
	    "      </release>"
	    "    </releases>"
	    "  </component>"
	    "</components>",
	    -1,
	    &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* write the extra file */
	ret = g_file_set_contents(
	    "/tmp/fwupd-self-test/testing.xml",
	    "<components>"
	    "  <component type=\"firmware\">"
	    "    <id>test</id>"
	    "    <name>Test Device</name>"
	    "    <provides>"
	    "      <firmware type=\"flashed\">aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee</firmware>"
	    "    </provides>"
	    "    <releases>"
	    "      <release version=\"1.2.5\" date=\"2017-09-16\">"
	    "        <size type=\"installed\">123</size>"
	    "        <size type=\"download\">456</size>"
	    "        <location>https://test.org/foo.cab</location>"
	    "        <checksum filename=\"foo.cab\" target=\"container\" "
	    "type=\"md5\">deadbeefdeadbeefdeadbeefdead3333</checksum>"
	    "        <checksum filename=\"firmware.bin\" target=\"content\" "
	    "type=\"md5\">deadbeefdeadbeefdeadbeefdeadbeef</checksum>"
	    "      </release>"
	    "      <release version=\"1.2.4\" date=\"2017-09-15\">"
	    "        <size type=\"installed\">123</size>"
	    "        <size type=\"download\">456</size>"
	    "        <location>https://test.org/foo.cab</location>"
	    "        <checksum filename=\"foo.cab\" target=\"container\" "
	    "type=\"md5\">deadbeefdeadbeefdeadbeefdead4444</checksum>"
	    "        <checksum filename=\"firmware.bin\" target=\"content\" "
	    "type=\"md5\">deadbeefdeadbeefdeadbeefdeadbeef</checksum>"
	    "      </release>"
	    "    </releases>"
	    "  </component>"
	    "</components>",
	    -1,
	    &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	ret = fu_engine_load(engine,
			     FU_ENGINE_LOAD_FLAG_REMOTES | FU_ENGINE_LOAD_FLAG_NO_CACHE,
			     progress,
			     &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_test_assert_expected_messages();

	/* return all the remotes, even the broken one */
	remotes = fu_engine_get_remotes(engine, &error);
	g_assert_no_error(error);
	g_assert_nonnull(remotes);
	g_assert_cmpint(remotes->len, ==, 7);

	/* ensure there are no devices already */
	devices_pre = fu_engine_get_devices(engine, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO);
	g_assert_null(devices_pre);
	g_clear_error(&error);

	/* add a device so we can get upgrades and downgrades */
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.3");
	fu_device_set_id(device, "test_device");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_set_name(device, "Test Device");
	fu_device_add_instance_id(device, "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_engine_add_device(engine, device);
	devices = fu_engine_get_devices(engine, &error);
	g_assert_no_error(error);
	g_assert_nonnull(devices);
	g_assert_cmpint(devices->len, ==, 1);
	g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED));
	g_assert_true(fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_REGISTERED));

	/* get the releases for one device */
	releases = fu_engine_get_releases(engine, request, fu_device_get_id(device), &error);
	g_assert_no_error(error);
	g_assert_nonnull(releases);
	g_assert_cmpint(releases->len, ==, 4);

	/* no upgrades, as no firmware is approved */
	releases_up = fu_engine_get_upgrades(engine, request, fu_device_get_id(device), &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO);
	g_assert_null(releases_up);
	g_clear_error(&error);

	/* retry with approved firmware set */
	fu_engine_add_approved_firmware(engine, "deadbeefdeadbeefdeadbeefdead1111");
	fu_engine_add_approved_firmware(engine, "deadbeefdeadbeefdeadbeefdead2222");
	fu_engine_add_approved_firmware(engine, "deadbeefdeadbeefdeadbeefdead3333");
	fu_engine_add_approved_firmware(engine, "deadbeefdeadbeefdeadbeefdead4444");
	fu_engine_add_approved_firmware(engine, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");

	/* upgrades */
	releases_up = fu_engine_get_upgrades(engine, request, fu_device_get_id(device), &error);
	g_assert_no_error(error);
	g_assert_nonnull(releases_up);
	g_assert_cmpint(releases_up->len, ==, 2);

	/* ensure the list is sorted */
	rel = FWUPD_RELEASE(g_ptr_array_index(releases_up, 0));
	g_assert_cmpstr(fwupd_release_get_version(rel), ==, "1.2.5");
	rel = FWUPD_RELEASE(g_ptr_array_index(releases_up, 1));
	g_assert_cmpstr(fwupd_release_get_version(rel), ==, "1.2.4");

	/* downgrades */
	releases_dg = fu_engine_get_downgrades(engine, request, fu_device_get_id(device), &error);
	g_assert_no_error(error);
	g_assert_nonnull(releases_dg);
	g_assert_cmpint(releases_dg->len, ==, 1);
	rel = FWUPD_RELEASE(g_ptr_array_index(releases_dg, 0));
	g_assert_cmpstr(fwupd_release_get_version(rel), ==, "1.2.2");

	/* enforce that updates have to be explicit */
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_ONLY_EXPLICIT_UPDATES);
	releases_up2 = fu_engine_get_upgrades(engine, request, fu_device_get_id(device), &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO);
	g_assert_null(releases_up2);
}

static void
fu_engine_md_verfmt_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	FwupdRemote *remote;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(GPtrArray) releases = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* ensure empty tree */
	fu_self_test_mkroot();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* write the main file */
	ret = g_file_set_contents(
	    "/tmp/fwupd-self-test/stable.xml",
	    "<components>"
	    "  <component type=\"firmware\">"
	    "    <id>test</id>"
	    "    <name>Test Device</name>"
	    "    <icon>computer</icon>"
	    "    <developer_name>ACME</developer_name>"
	    "    <provides>"
	    "      <firmware type=\"flashed\">aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee</firmware>"
	    "    </provides>"
	    "    <categories>"
	    "      <category>X-GraphicsTablet</category>"
	    "    </categories>"
	    "    <releases>"
	    "      <release version=\"1.2.3\" date=\"2017-09-15\">"
	    "        <size type=\"installed\">123</size>"
	    "        <size type=\"download\">456</size>"
	    "        <location>https://test.org/foo.cab</location>"
	    "        <checksum filename=\"foo.cab\" target=\"container\" "
	    "type=\"md5\">deadbeefdeadbeefdeadbeefdeadbeef</checksum>"
	    "        <checksum filename=\"firmware.bin\" target=\"content\" "
	    "type=\"md5\">deadbeefdeadbeefdeadbeefdeadbeef</checksum>"
	    "        <artifacts>"
	    "          <artifact type=\"binary\">"
	    "            <size type=\"installed\">1024</size>"
	    "            <size type=\"download\">2048</size>"
	    "          </artifact>"
	    "        </artifacts>"
	    "      </release>"
	    "    </releases>"
	    "    <custom>"
	    "      <value key=\"LVFS::VersionFormat\">triplet</value>"
	    "      <value key=\"LVFS::DeviceIntegrity\">signed</value>"
	    "      <value key=\"LVFS::DeviceFlags\">host-cpu,needs-shutdown</value>"
	    "    </custom>"
	    "  </component>"
	    "</components>",
	    -1,
	    &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	ret = fu_engine_load(engine,
			     FU_ENGINE_LOAD_FLAG_REMOTES | FU_ENGINE_LOAD_FLAG_NO_CACHE,
			     progress,
			     &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_test_assert_expected_messages();

	/* pretend this has a signature */
	remote = fu_engine_get_remote_by_id(engine, "stable", &error);
	g_assert_no_error(error);
	g_assert_nonnull(remote);

	/* add a device with no defined version format */
	fu_device_set_version(device, "16908291");
	fu_device_set_version_raw(device, 0x01020003);
	fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_SET_NAME_CATEGORY);
	fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_SET_ICON);
	fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_SET_VENDOR);
	fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_SET_SIGNED);
	fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_SET_VERFMT);
	fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_SET_FLAGS);
	fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_SET_REQUIRED_FREE);
	fu_device_set_id(device, "test_device");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_add_instance_id(device, "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_engine_add_device(engine, device);

	/* ensure the version format was set from the metadata */
	g_assert_cmpint(fu_device_get_version_format(device), ==, FWUPD_VERSION_FORMAT_TRIPLET);
	g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.3");
	g_assert_cmpstr(fu_device_get_name(device), ==, "Graphics Tablet");
	g_assert_cmpstr(fu_device_get_vendor(device), ==, "ACME");
	g_assert_true(fu_device_has_icon(device, "computer"));
	g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD));
	g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN));
	g_assert_true(fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_HOST_CPU));
	g_assert_cmpint(fu_device_get_required_free(device), ==, 1024);

	/* ensure the device was added */
	devices = fu_engine_get_devices(engine, &error);
	g_assert_no_error(error);
	g_assert_nonnull(devices);
	g_assert_cmpint(devices->len, ==, 1);
	g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED));
	g_assert_true(fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_REGISTERED));

	/* ensure the releases are set */
	releases = fu_engine_get_releases(engine, request, fu_device_get_id(device), &error);
	g_assert_no_error(error);
	g_assert_nonnull(releases);
	g_assert_cmpint(releases->len, ==, 1);
}

static void
fu_engine_install_duration_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	FwupdRelease *rel;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(GPtrArray) releases = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* ensure empty tree */
	fu_self_test_mkroot();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* write the main file */
	ret = g_file_set_contents(
	    "/tmp/fwupd-self-test/stable.xml",
	    "<components>"
	    "  <component type=\"firmware\">"
	    "    <id>test</id>"
	    "    <provides>"
	    "      <firmware type=\"flashed\">aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee</firmware>"
	    "    </provides>"
	    "    <releases>"
	    "      <release version=\"1.2.3\" date=\"2017-09-15\" install_duration=\"120\">"
	    "        <location>https://test.org/foo.cab</location>"
	    "        <checksum filename=\"foo.cab\" target=\"container\" "
	    "type=\"md5\">deadbeefdeadbeefdeadbeefdeadbeef</checksum>"
	    "        <checksum filename=\"firmware.bin\" target=\"content\" "
	    "type=\"md5\">deadbeefdeadbeefdeadbeefdeadbeef</checksum>"
	    "      </release>"
	    "    </releases>"
	    "  </component>"
	    "</components>",
	    -1,
	    &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	ret = fu_engine_load(engine,
			     FU_ENGINE_LOAD_FLAG_REMOTES | FU_ENGINE_LOAD_FLAG_NO_CACHE,
			     progress,
			     &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add a device so we can get the install duration */
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.3");
	fu_device_set_id(device, "test_device");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_add_instance_id(device, "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
	fu_device_set_install_duration(device, 999);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_engine_add_device(engine, device);
	devices = fu_engine_get_devices(engine, &error);
	g_assert_no_error(error);
	g_assert_nonnull(devices);
	g_assert_cmpint(devices->len, ==, 1);
	g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED));

	/* check the release install duration */
	releases = fu_engine_get_releases(engine, request, fu_device_get_id(device), &error);
	g_assert_no_error(error);
	g_assert_nonnull(releases);
	g_assert_cmpint(releases->len, ==, 1);
	rel = FWUPD_RELEASE(g_ptr_array_index(releases, 0));
	g_assert_cmpint(fwupd_release_get_install_duration(rel), ==, 120);
}

static void
fu_engine_release_dedupe_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(GPtrArray) releases = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* ensure empty tree */
	fu_self_test_mkroot();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* write the main file */
	ret = g_file_set_contents(
	    "/tmp/fwupd-self-test/stable.xml",
	    "<components>"
	    "  <component type=\"firmware\">"
	    "    <id>test</id>"
	    "    <provides>"
	    "      <firmware type=\"flashed\">aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee</firmware>"
	    "    </provides>"
	    "    <releases>"
	    "      <release version=\"1.2.3\" date=\"2017-09-15\" install_duration=\"120\">"
	    "        <location>https://test.org/foo.cab</location>"
	    "        <checksum filename=\"foo.cab\" target=\"container\" "
	    "type=\"md5\">deadbeefdeadbeefdeadbeefdeadbeef</checksum>"
	    "        <checksum filename=\"firmware.bin\" target=\"content\" "
	    "type=\"md5\">deadbeefdeadbeefdeadbeefdeadbeef</checksum>"
	    "      </release>"
	    "      <release version=\"1.2.3\" date=\"2017-09-15\" install_duration=\"120\">"
	    "        <location>https://test.org/foo.cab</location>"
	    "        <checksum filename=\"foo.cab\" target=\"container\" "
	    "type=\"md5\">deadbeefdeadbeefdeadbeefdeadbeef</checksum>"
	    "        <checksum filename=\"firmware.bin\" target=\"content\" "
	    "type=\"md5\">deadbeefdeadbeefdeadbeefdeadbeef</checksum>"
	    "      </release>"
	    "    </releases>"
	    "  </component>"
	    "</components>",
	    -1,
	    &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	ret = fu_engine_load(engine,
			     FU_ENGINE_LOAD_FLAG_REMOTES | FU_ENGINE_LOAD_FLAG_NO_CACHE,
			     progress,
			     &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add a device so we can get the install duration */
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.3");
	fu_device_set_id(device, "test_device");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_add_instance_id(device, "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
	fu_device_set_install_duration(device, 999);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_engine_add_device(engine, device);
	devices = fu_engine_get_devices(engine, &error);
	g_assert_no_error(error);
	g_assert_nonnull(devices);
	g_assert_cmpint(devices->len, ==, 1);
	g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED));

	/* check the release install duration */
	releases = fu_engine_get_releases(engine, request, fu_device_get_id(device), &error);
	g_assert_no_error(error);
	g_assert_nonnull(releases);
	g_assert_cmpint(releases->len, ==, 1);
}

static void
fu_engine_history_modify_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuHistory) history = fu_history_new(self->ctx);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;

	/* add a new entry */
	fu_device_set_id(device, "foobarbaz");
	fu_history_remove_device(history, device, NULL);
	ret = fu_history_add_device(history, device, release, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* try to modify something that does exist */
	ret = fu_history_modify_device(history, device, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* does not exist */
	fu_device_set_id(device, "DOES-NOT-EXIST");
	ret = fu_history_modify_device(history, device, &error);
	g_assert_false(ret);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND);
}

static void
fu_engine_history_convert_version_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	FuDevice *device_tmp;
	FwupdRelease *release_tmp;
	gboolean ret;
	g_autofree gchar *device_str = NULL;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuHistory) history = fu_history_new(self->ctx);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(XbBuilder) builder = xb_builder_new();
	g_autoptr(XbBuilderSource) source = xb_builder_source_new();
	g_autoptr(XbSilo) silo = NULL;

	/* add the fake metadata */
	ret = xb_builder_source_load_xml(
	    source,
	    "<?xml version=\"1.0\"?>\n"
	    "<components>\n"
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.example.firmware</id>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">b585990a-003e-5270-89d5-3705a17f9a43</firmware>\n"
	    "  </provides>\n"
	    "  <custom>\n"
	    "  </custom>\n"
	    "  <releases>\n"
	    "    <release id=\"1\" version=\"0x01020004\">\n"
	    "      <checksum type=\"sha1\" target=\"content\">abcd</checksum>\n"
	    "      <artifacts>\n"
	    "      </artifacts>\n"
	    "    </release>\n"
	    "  </releases>\n"
	    "</component>\n"
	    "</components>",
	    XB_BUILDER_SOURCE_FLAG_NONE,
	    &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	xb_builder_import_source(builder, source);
	silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	fu_engine_set_silo(engine, silo);

	fu_device_set_id(device, "abc");
	fu_device_set_version(device, "1.2.3");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_AFFECTS_FDE);
	fu_release_set_appstream_id(release, "com.acme.example.firmware");
	fu_release_add_checksum(release, "abcd");
	fu_release_set_version(release, "1.2.4");

	ret = fu_history_remove_all(history, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_history_add_device(history, device, release, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* do not overwrite the history-saved 1.2.4 with the release-provided 0x01020004 */
	devices = fu_engine_get_history(engine, &error);
	g_assert_no_error(error);
	g_assert_nonnull(devices);
	g_assert_cmpint(devices->len, ==, 1);
	device_tmp = g_ptr_array_index(devices, 0);

	device_str = fu_device_to_string(device_tmp);
	g_debug("%s", device_str);

	g_assert_cmpstr(fu_device_get_id(device_tmp),
			==,
			"a9993e364706816aba3e25717850c26c9cd0d89d");
	g_assert_cmpstr(fu_device_get_version(device_tmp), ==, "1.2.3");
	g_assert_true(fu_device_has_flag(device_tmp, FWUPD_DEVICE_FLAG_SUPPORTED));
	g_assert_true(fu_device_has_flag(device_tmp, FWUPD_DEVICE_FLAG_HISTORICAL));
	release_tmp = fu_device_get_release_default(device_tmp);
	g_assert_cmpstr(fwupd_release_get_version(release_tmp), ==, "1.2.4");
}

static void
fu_engine_history_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autofree gchar *checksum = NULL;
	g_autofree gchar *device_str_expected = NULL;
	g_autofree gchar *device_str = NULL;
	g_autofree gchar *filename = NULL;
	g_autoptr(FuCabinet) cabinet = NULL;
	g_autoptr(FuDevice) device2 = NULL;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuHistory) history = NULL;
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(FuPlugin) plugin = fu_plugin_new_from_gtype(fu_test_plugin_get_type(), self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(FwupdDevice) device3 = NULL;
	g_autoptr(FwupdDevice) device4 = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(GInputStream) stream = NULL;
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* ensure empty tree */
	fu_self_test_mkroot();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* set up dummy plugin */
	ret = fu_plugin_reset_config_values(plugin, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_plugin_set_config_value(plugin, "AnotherWriteRequired", "true", &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	fu_engine_add_plugin(engine, plugin);

	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add a device so we can get upgrade it */
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.2");
	fu_device_set_id(device, "test_device");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_set_name(device, "Test Device");
	fu_device_set_plugin(device, "test");
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");
	fu_device_add_checksum(device, "0123456789abcdef0123456789abcdef01234567");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_set_created_usec(device, 1515338000ull * G_USEC_PER_SEC);
	fu_engine_add_device(engine, device);
	devices = fu_engine_get_devices(engine, &error);
	g_assert_no_error(error);
	g_assert_nonnull(devices);
	g_assert_cmpint(devices->len, ==, 1);
	g_assert_true(fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_REGISTERED));

	filename =
	    g_test_build_filename(G_TEST_BUILT, "tests", "missing-hwid", "noreqs-1.2.3.cab", NULL);
	stream = fu_input_stream_from_path(filename, &error);
	g_assert_no_error(error);
	g_assert_nonnull(stream);
	cabinet = fu_engine_build_cabinet_from_stream(engine, stream, &error);
	g_assert_no_error(error);
	g_assert_nonnull(cabinet);

	/* get component */
	component = fu_cabinet_get_component(cabinet, "com.hughski.test.firmware", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* set the counter */
	fu_device_set_metadata_integer(device, "nr-update", 0);

	/* install it */
	fu_release_set_device(release, device);
	ret = fu_release_load(release, cabinet, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_install_release(engine, release, progress, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* check the write was done more than once */
	g_assert_cmpint(fu_device_get_metadata_integer(device, "nr-update"), ==, 2);

	/* check the history database */
	history = fu_history_new(self->ctx);
	device2 = fu_history_get_device_by_id(history, fu_device_get_id(device), &error);
	g_assert_no_error(error);
	g_assert_nonnull(device2);
	g_assert_cmpint(fu_device_get_update_state(device2), ==, FWUPD_UPDATE_STATE_SUCCESS);
	g_assert_cmpstr(fu_device_get_update_error(device2), ==, NULL);
	fu_device_set_modified_usec(device2, 1514338000ull * G_USEC_PER_SEC);
	g_hash_table_remove_all(fwupd_release_get_metadata(fu_device_get_release_default(device2)));
	device_str = fu_device_to_string(device2);
	checksum = fu_input_stream_compute_checksum(stream, G_CHECKSUM_SHA1, &error);
	g_assert_no_error(error);
	g_assert_nonnull(checksum);
	device_str_expected =
	    g_strdup_printf("FuDevice:\n"
			    "  DeviceId:             894e8c17a29428b09d10cd90d1db74ea76fbcfe8\n"
			    "  Name:                 Test Device\n"
			    "  Guid:                 12345678-1234-1234-1234-123456789012\n"
			    "  Plugin:               test\n"
			    "  Flags:                updatable|historical|unsigned-payload\n"
			    "  Version:              1.2.2\n"
			    "  VersionFormat:        triplet\n"
			    "  Created:              2018-01-07 15:13:20\n"
			    "  Modified:             2017-12-27 01:26:40\n"
			    "  UpdateState:          success\n"
			    "  FuRelease:\n"
			    "    AppstreamId:        com.hughski.test.firmware\n"
			    "    Version:            1.2.3\n"
			    "    Checksum:           SHA1(%s)\n"
			    "    Flags:              trusted-payload|trusted-metadata\n"
			    "  InstanceId[vi]:       12345678-1234-1234-1234-123456789012\n"
			    "  AcquiesceDelay:       50\n",
			    checksum);
	g_debug("%s", device_str);
	ret = g_strcmp0(device_str, device_str_expected) == 0;
	g_assert_true(ret);

	/* GetResults() */
	device3 = fu_engine_get_results(engine, FWUPD_DEVICE_ID_ANY, &error);
	g_assert_no_error(error);
	g_assert_nonnull(device3);
	g_assert_cmpstr(fu_device_get_id(device3), ==, "894e8c17a29428b09d10cd90d1db74ea76fbcfe8");
	g_assert_cmpint(fu_device_get_update_state(device3), ==, FWUPD_UPDATE_STATE_SUCCESS);
	g_assert_cmpstr(fu_device_get_update_error(device3), ==, NULL);

	/* ClearResults() */
	ret = fu_engine_clear_results(engine, FWUPD_DEVICE_ID_ANY, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* GetResults() */
	device4 = fu_engine_get_results(engine, FWUPD_DEVICE_ID_ANY, &error);
	g_assert_null(device4);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO);
}

static void
fu_engine_history_verfmt_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = g_object_new(FU_TYPE_DPAUX_DEVICE, "context", self->ctx, NULL);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuPlugin) plugin = fu_plugin_new_from_gtype(fu_test_plugin_get_type(), self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* set up dummy plugin */
	fu_engine_add_plugin(engine, plugin);
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* absorb version format from the database */
	fu_device_set_version_raw(device, 65563);
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_NUMBER);
	fu_device_set_id(device, "test_device");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_set_plugin(device, "test");
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");
	fu_device_add_checksum(device, "0123456789abcdef0123456789abcdef01234567");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_MD_SET_VERFMT);
	fu_device_set_created_usec(device, 1515338000ull * G_USEC_PER_SEC);
	fu_engine_add_device(engine, device);
	g_assert_cmpint(fu_device_get_version_format(device), ==, FWUPD_VERSION_FORMAT_TRIPLET);
	g_assert_cmpstr(fu_device_get_version(device), ==, "0.1.27");
}

static void
fu_engine_install_loop_restart_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuPlugin) plugin = fu_plugin_new_from_gtype(fu_test_plugin_get_type(), self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(GInputStream) stream_fw = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* ensure empty tree */
	fu_self_test_mkroot();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* set up dummy plugin */
	ret = fu_plugin_reset_config_values(plugin, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_plugin_set_config_value(plugin, "InstallLoopRestart", "true", &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	fu_engine_add_plugin(engine, plugin);

	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add a device so we can install it */
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.2");
	fu_device_set_id(device, "test_device");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_set_plugin(device, "test");
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_engine_add_device(engine, device);

	/* set up counters */
	fu_device_set_metadata_integer(device, "nr-update", 0);
	fu_device_set_metadata_integer(device, "nr-attach", 0);

	stream_fw = g_memory_input_stream_new_from_data((const guint8 *)"1.2.3", 5, NULL);
	fu_release_set_stream(release, stream_fw);
	ret = fu_engine_install_blob(engine,
				     device,
				     release,
				     progress,
				     FWUPD_INSTALL_FLAG_NO_HISTORY |
					 FU_FIRMWARE_PARSE_FLAG_CACHE_STREAM,
				     FWUPD_FEATURE_FLAG_NONE,
				     &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* check we did two write loops */
	g_assert_cmpint(fu_device_get_metadata_integer(device, "nr-update"), ==, 2);

	/* check we only attached once */
	g_assert_cmpint(fu_device_get_metadata_integer(device, "nr-attach"), ==, 1);
}

static void
fu_engine_multiple_rels_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autofree gchar *filename = NULL;
	g_autoptr(FuCabinet) cabinet = NULL;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuPlugin) plugin = fu_plugin_new_from_gtype(fu_test_plugin_get_type(), self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(GInputStream) stream = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(GPtrArray) releases = NULL;
	g_autoptr(GPtrArray) rels = NULL;
	g_autoptr(XbQuery) query = NULL;

#ifndef HAVE_LIBARCHIVE
	g_test_skip("no libarchive support");
	return;
#endif

	/* ensure empty tree */
	fu_self_test_mkroot();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* set up dummy plugin */
	ret = fu_plugin_reset_config_values(plugin, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	fu_engine_add_plugin(engine, plugin);

	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add a device so we can get upgrade it */
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.2");
	fu_device_set_id(device, "test_device");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_set_name(device, "Test Device");
	fu_device_set_plugin(device, "test");
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");
	fu_device_add_checksum(device, "0123456789abcdef0123456789abcdef01234567");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES);
	fu_device_set_created_usec(device, 1515338000ull * G_USEC_PER_SEC);
	fu_engine_add_device(engine, device);

	filename = g_test_build_filename(G_TEST_BUILT,
					 "tests",
					 "multiple-rels",
					 "multiple-rels-1.2.4.cab",
					 NULL);
	stream = fu_input_stream_from_path(filename, &error);
	g_assert_no_error(error);
	g_assert_nonnull(stream);
	cabinet = fu_engine_build_cabinet_from_stream(engine, stream, &error);
	g_assert_no_error(error);
	g_assert_nonnull(cabinet);

	/* get component */
	component = fu_cabinet_get_component(cabinet, "com.hughski.test.firmware", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* set up counters */
	fu_device_set_metadata_integer(device, "nr-update", 0);
	fu_device_set_metadata_integer(device, "nr-attach", 0);

	/* get all */
	query = xb_query_new_full(xb_node_get_silo(component),
				  "releases/release",
				  XB_QUERY_FLAG_FORCE_NODE_CACHE,
				  &error);
	g_assert_no_error(error);
	g_assert_nonnull(query);
	rels = xb_node_query_full(component, query, &error);
	g_assert_no_error(error);
	g_assert_nonnull(rels);

	releases = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
	for (guint i = 0; i < rels->len; i++) {
		XbNode *rel = g_ptr_array_index(rels, i);
		g_autoptr(FuRelease) release = fu_release_new();
		fu_release_set_device(release, device);
		ret = fu_release_load(release,
				      cabinet,
				      component,
				      rel,
				      FWUPD_INSTALL_FLAG_NONE,
				      &error);
		g_assert_no_error(error);
		g_assert_true(ret);
		g_ptr_array_add(releases, g_object_ref(release));
	}

	/* install them */
	fu_progress_reset(progress);
	ret = fu_engine_install_releases(engine,
					 request,
					 releases,
					 cabinet,
					 progress,
					 FWUPD_INSTALL_FLAG_NONE,
					 &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* check we did 1.2.2 -> 1.2.3 -> 1.2.4 */
	g_assert_cmpint(fu_device_get_metadata_integer(device, "nr-update"), ==, 2);
	g_assert_cmpint(fu_device_get_metadata_integer(device, "nr-attach"), ==, 2);
	g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.4");

	/* reset the config back to defaults */
	ret = fu_engine_reset_config(engine, "fwupd", &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_engine_history_inherit(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autofree gchar *filename = NULL;
	g_autofree gchar *history_db = NULL;
	g_autoptr(FuCabinet) cabinet = NULL;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(FuPlugin) plugin = fu_plugin_new_from_gtype(fu_test_plugin_get_type(), self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(GInputStream) stream = NULL;
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* delete history */
	history_db = fu_path_build(FU_PATH_KIND_LOCALSTATEDIR_PKG, "pending.db", NULL);
	(void)g_unlink(history_db);

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* set up dummy plugin */
	ret = fu_plugin_reset_config_values(plugin, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_plugin_set_config_value(plugin, "NeedsActivation", "true", &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	fu_engine_add_plugin(engine, plugin);
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add a device so we can get upgrade it */
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.2");
	fu_device_set_id(device, "test_device");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_set_name(device, "Test Device");
	fu_device_set_plugin(device, "test");
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_set_created_usec(device, 1515338000ull * G_USEC_PER_SEC);
	fu_engine_add_device(engine, device);
	devices = fu_engine_get_devices(engine, &error);
	g_assert_no_error(error);
	g_assert_nonnull(devices);
	g_assert_cmpint(devices->len, ==, 1);
	g_assert_true(fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_REGISTERED));

	filename =
	    g_test_build_filename(G_TEST_BUILT, "tests", "missing-hwid", "noreqs-1.2.3.cab", NULL);
	stream = fu_input_stream_from_path(filename, &error);
	g_assert_no_error(error);
	g_assert_nonnull(stream);
	cabinet = fu_engine_build_cabinet_from_stream(engine, stream, &error);
	g_assert_no_error(error);
	g_assert_nonnull(cabinet);

	/* get component */
	component = fu_cabinet_get_component(cabinet, "com.hughski.test.firmware", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* install it */
	fu_release_set_device(release, device);
	ret = fu_release_load(release, cabinet, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_install_release(engine, release, progress, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* check the device requires an activation */
	g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION));
	g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.2");

	/* activate the device */
	fu_progress_reset(progress);
	ret = fu_engine_activate(engine, fu_device_get_id(device), progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* check the device no longer requires an activation */
	g_assert_false(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION));
	g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.3");

	/* emulate getting the flag for a fresh boot on old firmware */
	fu_progress_reset(progress);
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.2");
	ret = fu_engine_install_release(engine, release, progress, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_object_unref(engine);
	g_object_unref(device);
	engine = fu_engine_new(self->ctx);
	fu_engine_set_silo(engine, silo_empty);
	fu_engine_add_plugin(engine, plugin);
	device = fu_device_new(self->ctx);
	fu_device_add_private_flag(device, FU_DEVICE_PRIVATE_FLAG_INHERIT_ACTIVATION);
	fu_device_set_id(device, "test_device");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_set_name(device, "Test Device");
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.2");
	fu_engine_add_device(engine, device);
	g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION));

	/* emulate not getting the flag */
	g_object_unref(engine);
	g_object_unref(device);
	engine = fu_engine_new(self->ctx);
	fu_engine_set_silo(engine, silo_empty);
	fu_engine_add_plugin(engine, plugin);
	device = fu_device_new(self->ctx);
	fu_device_set_id(device, "test_device");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_set_name(device, "Test Device");
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.2");
	fu_engine_add_device(engine, device);
	g_assert_false(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION));
}

static void
fu_engine_install_needs_reboot(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autofree gchar *filename = NULL;
	g_autoptr(FuCabinet) cabinet = NULL;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(FuPlugin) plugin = fu_plugin_new_from_gtype(fu_test_plugin_get_type(), self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(GInputStream) stream = NULL;
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();
	g_autofree gchar *rundir = fu_path_from_kind(FU_PATH_KIND_RUNDIR);
	g_autofree gchar *reboot_file = NULL;

	/* create rundir */
	ret = fu_path_mkdir(rundir, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* set up dummy plugin */
	ret = fu_plugin_reset_config_values(plugin, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_plugin_set_config_value(plugin, "NeedsReboot", "true", &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	fu_engine_add_plugin(engine, plugin);
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add a device so we can get upgrade it */
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.2");
	fu_device_set_id(device, "test_device");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_set_name(device, "Test Device");
	fu_device_set_plugin(device, "test");
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_set_created_usec(device, 1515338000ull * G_USEC_PER_SEC);
	fu_engine_add_device(engine, device);
	devices = fu_engine_get_devices(engine, &error);
	g_assert_no_error(error);
	g_assert_nonnull(devices);
	g_assert_cmpint(devices->len, ==, 1);
	g_assert_true(fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_REGISTERED));

	filename =
	    g_test_build_filename(G_TEST_BUILT, "tests", "missing-hwid", "noreqs-1.2.3.cab", NULL);
	stream = fu_input_stream_from_path(filename, &error);
	g_assert_no_error(error);
	g_assert_nonnull(stream);
	cabinet = fu_engine_build_cabinet_from_stream(engine, stream, &error);
	g_assert_no_error(error);
	g_assert_nonnull(cabinet);

	/* get component */
	component = fu_cabinet_get_component(cabinet, "com.hughski.test.firmware", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* install it */
	fu_release_set_device(release, device);
	ret = fu_release_load(release, cabinet, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_install_release(engine, release, progress, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* check the device requires reboot */
	g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT));
	g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.2");

	reboot_file = g_build_filename(rundir, "reboot-required", NULL);
	g_assert_true(g_file_test(reboot_file, G_FILE_TEST_EXISTS));
}

typedef struct {
	guint request_cnt;
	FwupdStatus last_status;
} FuTestRequestHelper;

static void
fu_test_engine_status_changed_cb(FuProgress *progress, FwupdStatus status, gpointer user_data)
{
	FuTestRequestHelper *helper = (FuTestRequestHelper *)user_data;
	g_debug("status now %s", fwupd_status_to_string(status));
	helper->last_status = status;
}

static void
fu_test_engine_request_cb(FuEngine *engine, FwupdRequest *request, gpointer user_data)
{
	FuTestRequestHelper *helper = (FuTestRequestHelper *)user_data;
	g_assert_cmpint(fwupd_request_get_kind(request), ==, FWUPD_REQUEST_KIND_IMMEDIATE);
	g_assert_cmpstr(fwupd_request_get_id(request), ==, FWUPD_REQUEST_ID_REMOVE_REPLUG);
	g_assert_true(fwupd_request_has_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE));
	g_assert_nonnull(fwupd_request_get_message(request));
	g_assert_cmpint(helper->last_status, ==, FWUPD_STATUS_WAITING_FOR_USER);
	helper->request_cnt++;
}

static void
fu_engine_install_request(gconstpointer user_data)
{
	FuTestRequestHelper helper = {.request_cnt = 0, .last_status = FWUPD_STATUS_UNKNOWN};
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autofree gchar *filename = NULL;
	g_autoptr(FuCabinet) cabinet = NULL;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(FuPlugin) plugin = fu_plugin_new_from_gtype(fu_test_plugin_get_type(), self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(GInputStream) stream = NULL;
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* set up dummy plugin */
	ret = fu_plugin_reset_config_values(plugin, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_plugin_set_config_value(plugin, "RequestSupported", "true", &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	fu_engine_add_plugin(engine, plugin);
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add a device so we can get upgrade it */
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.2");
	fu_device_set_id(device, "test_device");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_set_name(device, "Test Device");
	fu_device_set_plugin(device, "test");
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_request_flag(device, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE);
	fu_device_set_created_usec(device, 1515338000ull * G_USEC_PER_SEC);
	fu_engine_add_device(engine, device);
	devices = fu_engine_get_devices(engine, &error);
	g_assert_no_error(error);
	g_assert_nonnull(devices);
	g_assert_cmpint(devices->len, ==, 1);
	g_assert_true(fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_REGISTERED));

	filename =
	    g_test_build_filename(G_TEST_BUILT, "tests", "missing-hwid", "noreqs-1.2.3.cab", NULL);
	stream = fu_input_stream_from_path(filename, &error);
	g_assert_no_error(error);
	g_assert_nonnull(stream);
	cabinet = fu_engine_build_cabinet_from_stream(engine, stream, &error);
	g_assert_no_error(error);
	g_assert_nonnull(cabinet);

	/* get component */
	component = fu_cabinet_get_component(cabinet, "com.hughski.test.firmware", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);

	/* install it */
	fu_release_set_device(release, device);
	ret = fu_release_load(release, cabinet, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpstr(fu_release_get_firmware_basename(release), ==, "firmware.bin");
	g_assert_cmpstr(fu_release_get_version(release), ==, "1.2.3");

	g_signal_connect(FU_ENGINE(engine),
			 "device-request",
			 G_CALLBACK(fu_test_engine_request_cb),
			 &helper);
	g_signal_connect(FU_PROGRESS(progress),
			 "status-changed",
			 G_CALLBACK(fu_test_engine_status_changed_cb),
			 &helper);

	ret = fu_engine_install_release(engine, release, progress, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpint(helper.request_cnt, ==, 1);
	g_assert_cmpint(helper.last_status, ==, FWUPD_STATUS_DEVICE_BUSY);
}

static void
fu_engine_history_error_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autofree gchar *checksum = NULL;
	g_autofree gchar *device_str_expected = NULL;
	g_autofree gchar *device_str = NULL;
	g_autofree gchar *filename = NULL;
	g_autoptr(FuCabinet) cabinet = NULL;
	g_autoptr(FuDevice) device2 = NULL;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuHistory) history = NULL;
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(FuPlugin) plugin = fu_plugin_new_from_gtype(fu_test_plugin_get_type(), self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error2 = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(GInputStream) stream = NULL;
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* set up dummy plugin */
	ret = fu_plugin_reset_config_values(plugin, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_plugin_set_config_value(plugin, "WriteSupported", "false", &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	fu_engine_add_plugin(engine, plugin);
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add a device so we can get upgrade it */
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "1.2.2");
	fu_device_set_id(device, "test_device");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_protocol(device, "com.acme");
	fu_device_set_name(device, "Test Device");
	fu_device_set_plugin(device, "test");
	fu_device_add_instance_id(device, "12345678-1234-1234-1234-123456789012");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_set_created_usec(device, 1515338000ull * G_USEC_PER_SEC);
	fu_engine_add_device(engine, device);
	devices = fu_engine_get_devices(engine, &error);
	g_assert_no_error(error);
	g_assert_nonnull(devices);
	g_assert_cmpint(devices->len, ==, 1);
	g_assert_true(fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_REGISTERED));

	/* install the wrong thing */
	filename =
	    g_test_build_filename(G_TEST_BUILT, "tests", "missing-hwid", "noreqs-1.2.3.cab", NULL);
	stream = fu_input_stream_from_path(filename, &error);
	g_assert_no_error(error);
	g_assert_nonnull(stream);
	cabinet = fu_engine_build_cabinet_from_stream(engine, stream, &error);
	g_assert_no_error(error);
	g_assert_nonnull(cabinet);
	component = fu_cabinet_get_component(cabinet, "com.hughski.test.firmware", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);
	fu_release_set_device(release, device);
	ret = fu_release_load(release, cabinet, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_engine_install_release(engine, release, progress, FWUPD_INSTALL_FLAG_NONE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_cmpstr(error->message,
			==,
			"failed to write-firmware: device was not in supported mode");
	g_assert_false(ret);

	/* check the history database */
	history = fu_history_new(self->ctx);
	device2 = fu_history_get_device_by_id(history, fu_device_get_id(device), &error2);
	g_assert_no_error(error2);
	g_assert_nonnull(device2);
	g_assert_cmpint(fu_device_get_update_state(device2), ==, FWUPD_UPDATE_STATE_FAILED);
	g_assert_cmpstr(fu_device_get_update_error(device2), ==, error->message);
	g_clear_error(&error);
	fu_device_set_modified_usec(device2, 1514338000ull * G_USEC_PER_SEC);
	g_hash_table_remove_all(fwupd_release_get_metadata(fu_device_get_release_default(device2)));
	device_str = fu_device_to_string(device2);
	checksum = fu_input_stream_compute_checksum(stream, G_CHECKSUM_SHA1, &error);
	g_assert_no_error(error);
	g_assert_nonnull(checksum);
	device_str_expected = g_strdup_printf(
	    "FuDevice:\n"
	    "  DeviceId:             894e8c17a29428b09d10cd90d1db74ea76fbcfe8\n"
	    "  Name:                 Test Device\n"
	    "  Guid:                 12345678-1234-1234-1234-123456789012\n"
	    "  Plugin:               test\n"
	    "  Flags:                updatable|historical|unsigned-payload\n"
	    "  Version:              1.2.2\n"
	    "  VersionFormat:        triplet\n"
	    "  Created:              2018-01-07 15:13:20\n"
	    "  Modified:             2017-12-27 01:26:40\n"
	    "  UpdateState:          failed\n"
	    "  UpdateError:          failed to write-firmware: device was not in supported mode\n"
	    "  FuRelease:\n"
	    "    AppstreamId:        com.hughski.test.firmware\n"
	    "    Version:            1.2.3\n"
	    "    Checksum:           SHA1(%s)\n"
	    "    Flags:              trusted-payload|trusted-metadata\n"
	    "  InstanceId[vi]:       12345678-1234-1234-1234-123456789012\n"
	    "  AcquiesceDelay:       50\n",
	    checksum);
	g_debug("%s", device_str);
	ret = g_strcmp0(device_str, device_str_expected) == 0;
	g_assert_true(ret);
}

static void
fu_test_device_list_count_cb(FuDeviceList *device_list, FuDevice *device, gpointer user_data)
{
	guint *cnt = (guint *)user_data;
	(*cnt)++;
}

static void
fu_device_list_no_auto_remove_children_func(gconstpointer user_data)
{
	g_autoptr(FuDevice) child = fu_device_new(NULL);
	g_autoptr(FuDevice) parent = fu_device_new(NULL);
	g_autoptr(FuDeviceList) device_list = fu_device_list_new();
	g_autoptr(GPtrArray) active1 = NULL;
	g_autoptr(GPtrArray) active2 = NULL;
	g_autoptr(GPtrArray) active3 = NULL;

	/* normal behavior, remove child with parent */
	fu_device_set_id(parent, "parent");
	fu_device_set_id(child, "child");
	fu_device_add_child(parent, child);
	fu_device_list_add(device_list, parent);
	fu_device_list_add(device_list, child);
	fu_device_list_remove(device_list, parent);
	active1 = fu_device_list_get_active(device_list);
	g_assert_cmpint(active1->len, ==, 0);

	/* new-style behavior, do not remove child */
	fu_device_add_private_flag(parent, FU_DEVICE_PRIVATE_FLAG_NO_AUTO_REMOVE_CHILDREN);
	fu_device_list_add(device_list, parent);
	fu_device_list_add(device_list, child);
	fu_device_list_remove(device_list, parent);
	active2 = fu_device_list_get_active(device_list);
	g_assert_cmpint(active2->len, ==, 1);
	fu_device_list_remove(device_list, child);
	active3 = fu_device_list_get_active(device_list);
	g_assert_cmpint(active3->len, ==, 0);
}

static void
fu_device_list_delay_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	g_autoptr(FuDevice) device1 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device2 = fu_device_new(self->ctx);
	g_autoptr(FuDeviceList) device_list = fu_device_list_new();
	guint added_cnt = 0;
	guint changed_cnt = 0;
	guint removed_cnt = 0;

	g_signal_connect(FU_DEVICE_LIST(device_list),
			 "added",
			 G_CALLBACK(fu_test_device_list_count_cb),
			 &added_cnt);
	g_signal_connect(FU_DEVICE_LIST(device_list),
			 "removed",
			 G_CALLBACK(fu_test_device_list_count_cb),
			 &removed_cnt);
	g_signal_connect(FU_DEVICE_LIST(device_list),
			 "changed",
			 G_CALLBACK(fu_test_device_list_count_cb),
			 &changed_cnt);

	/* add one device */
	fu_device_set_id(device1, "device1");
	fu_device_add_instance_id(device1, "foobar");
	fu_device_add_private_flag(device1, FU_DEVICE_PRIVATE_FLAG_DELAYED_REMOVAL);
	fu_device_set_remove_delay(device1, 100);
	fu_device_list_add(device_list, device1);
	g_assert_cmpint(added_cnt, ==, 1);
	g_assert_cmpint(removed_cnt, ==, 0);
	g_assert_cmpint(changed_cnt, ==, 0);

	/* add the same device again */
	fu_device_list_add(device_list, device1);
	g_assert_cmpint(added_cnt, ==, 1);
	g_assert_cmpint(removed_cnt, ==, 0);
	g_assert_cmpint(changed_cnt, ==, 1);

	/* add a device with the same ID */
	fu_device_set_id(device2, "device1");
	fu_device_add_private_flag(device2, FU_DEVICE_PRIVATE_FLAG_DELAYED_REMOVAL);
	fu_device_list_add(device_list, device2);
	fu_device_set_remove_delay(device2, 100);
	g_assert_cmpint(added_cnt, ==, 1);
	g_assert_cmpint(removed_cnt, ==, 0);
	g_assert_cmpint(changed_cnt, ==, 2);

	/* spin a bit */
	fu_test_loop_run_with_timeout(10);
	fu_test_loop_quit();

	/* verify only a changed event was generated */
	added_cnt = removed_cnt = changed_cnt = 0;
	fu_device_list_remove(device_list, device1);
	fu_device_list_add(device_list, device1);
	g_assert_cmpint(added_cnt, ==, 0);
	g_assert_cmpint(removed_cnt, ==, 0);
	g_assert_cmpint(changed_cnt, ==, 1);
}

typedef struct {
	FuDevice *device_new;
	FuDevice *device_old;
	FuDeviceList *device_list;
} FuDeviceListReplugHelper;

static gboolean
fu_device_list_remove_cb(gpointer user_data)
{
	FuDeviceListReplugHelper *helper = (FuDeviceListReplugHelper *)user_data;
	fu_device_list_remove(helper->device_list, helper->device_old);
	return FALSE;
}

static gboolean
fu_device_list_add_cb(gpointer user_data)
{
	FuDeviceListReplugHelper *helper = (FuDeviceListReplugHelper *)user_data;
	fu_device_list_add(helper->device_list, helper->device_new);
	return FALSE;
}

static void
fu_device_list_replug_auto_func(gconstpointer user_data)
{
	gboolean ret;
	g_autoptr(FuDevice) device1 = fu_device_new(NULL);
	g_autoptr(FuDevice) device2 = fu_device_new(NULL);
	g_autoptr(FuDevice) parent = fu_device_new(NULL);
	g_autoptr(FuDeviceList) device_list = fu_device_list_new();
	g_autoptr(GError) error = NULL;
	FuDeviceListReplugHelper helper;

	/* parent */
	fu_device_set_id(parent, "parent");

	/* fake child devices */
	fu_device_set_id(device1, "device1");
	fu_device_add_private_flag(device1, FU_DEVICE_PRIVATE_FLAG_REPLUG_MATCH_GUID);
	fu_device_set_physical_id(device1, "ID");
	fu_device_set_plugin(device1, "self-test");
	fu_device_set_remove_delay(device1, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
	fu_device_add_child(parent, device1);
	fu_device_set_id(device2, "device2");
	fu_device_add_private_flag(device2, FU_DEVICE_PRIVATE_FLAG_REPLUG_MATCH_GUID);
	fu_device_set_physical_id(device2, "ID"); /* matches */
	fu_device_set_plugin(device2, "self-test");
	fu_device_set_remove_delay(device2, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);

	/* not yet added */
	ret = fu_device_list_wait_for_replug(device_list, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add device */
	fu_device_list_add(device_list, device1);

	/* not waiting */
	ret = fu_device_list_wait_for_replug(device_list, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* waiting */
	helper.device_old = device1;
	helper.device_new = device2;
	helper.device_list = device_list;
	g_timeout_add(100, fu_device_list_remove_cb, &helper);
	g_timeout_add(200, fu_device_list_add_cb, &helper);
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
	ret = fu_device_list_wait_for_replug(device_list, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_false(fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG));

	/* check device2 now has parent too */
	g_assert_true(fu_device_get_parent_internal(device2) == parent);

	/* waiting, failed */
	fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
	ret = fu_device_list_wait_for_replug(device_list, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND);
	g_assert_false(ret);
	g_assert_false(fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG));
}

static void
fu_device_list_replug_user_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device1 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device2 = fu_device_new(self->ctx);
	g_autoptr(FuDeviceList) device_list = fu_device_list_new();
	g_autoptr(GError) error = NULL;
	FuDeviceListReplugHelper helper;

	/* fake devices */
	fu_device_set_id(device1, "device1");
	fu_device_set_name(device1, "device1");
	fu_device_add_private_flag(device1, FU_DEVICE_PRIVATE_FLAG_REPLUG_MATCH_GUID);
	fu_device_add_instance_id(device1, "foo");
	fu_device_add_instance_id(device1, "bar");
	fu_device_set_plugin(device1, "self-test");
	fu_device_set_remove_delay(device1, FU_DEVICE_REMOVE_DELAY_USER_REPLUG);
	fu_device_set_id(device2, "device2");
	fu_device_set_name(device2, "device2");
	fu_device_add_private_flag(device2, FU_DEVICE_PRIVATE_FLAG_REPLUG_MATCH_GUID);
	fu_device_add_instance_id(device2, "baz");
	fu_device_add_instance_id_full(device2,
				       "bar",
				       FU_DEVICE_INSTANCE_FLAG_COUNTERPART); /* matches */
	fu_device_set_plugin(device2, "self-test");
	fu_device_set_remove_delay(device2, FU_DEVICE_REMOVE_DELAY_USER_REPLUG);

	/* not yet added */
	ret = fu_device_list_wait_for_replug(device_list, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add device */
	fu_device_list_add(device_list, device1);

	/* add duplicate */
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
	fu_device_list_add(device_list, device1);
	g_assert_false(fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG));

	/* not waiting */
	ret = fu_device_list_wait_for_replug(device_list, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* waiting */
	helper.device_old = device1;
	helper.device_new = device2;
	helper.device_list = device_list;
	g_timeout_add(100, fu_device_list_remove_cb, &helper);
	g_timeout_add(200, fu_device_list_add_cb, &helper);
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
	ret = fu_device_list_wait_for_replug(device_list, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_false(fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG));

	/* should not be possible, but here we are */
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
	fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
	fu_device_list_add(device_list, device1);
	g_assert_false(fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG));
	g_assert_false(fu_device_has_flag(device2, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG));

	/* add back the old device */
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
	fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
	fu_device_list_remove(device_list, device2);
	fu_device_list_add(device_list, device1);
	g_assert_false(fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG));
	g_assert_false(fu_device_has_flag(device2, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG));
}

static void
fu_device_list_compatible_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	g_autoptr(FuDevice) device1 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device2 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device_old = NULL;
	g_autoptr(FuDeviceList) device_list = fu_device_list_new();
	g_autoptr(GPtrArray) devices_all = NULL;
	g_autoptr(GPtrArray) devices_active = NULL;
	FuDevice *device;
	guint added_cnt = 0;
	guint changed_cnt = 0;
	guint removed_cnt = 0;

	g_signal_connect(FU_DEVICE_LIST(device_list),
			 "added",
			 G_CALLBACK(fu_test_device_list_count_cb),
			 &added_cnt);
	g_signal_connect(FU_DEVICE_LIST(device_list),
			 "removed",
			 G_CALLBACK(fu_test_device_list_count_cb),
			 &removed_cnt);
	g_signal_connect(FU_DEVICE_LIST(device_list),
			 "changed",
			 G_CALLBACK(fu_test_device_list_count_cb),
			 &changed_cnt);

	/* add one device in runtime mode */
	fu_device_set_id(device1, "device1");
	fu_device_set_plugin(device1, "plugin-for-runtime");
	fu_device_build_vendor_id(device1, "USB", "0x20A0");
	fu_device_set_version_format(device1, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device1, "1.2.3");
	fu_device_add_private_flag(device1, FU_DEVICE_PRIVATE_FLAG_REPLUG_MATCH_GUID);
	fu_device_add_private_flag(device1, FU_DEVICE_PRIVATE_FLAG_DELAYED_REMOVAL);
	fu_device_add_instance_id(device1, "foobar");
	fu_device_add_instance_id_full(device1, "bootloader", FU_DEVICE_INSTANCE_FLAG_COUNTERPART);
	fu_device_set_remove_delay(device1, 100);
	fu_device_list_add(device_list, device1);
	g_assert_cmpint(added_cnt, ==, 1);
	g_assert_cmpint(removed_cnt, ==, 0);
	g_assert_cmpint(changed_cnt, ==, 0);

	/* add another device in bootloader mode */
	fu_device_set_id(device2, "device2");
	fu_device_set_plugin(device2, "plugin-for-bootloader");
	fu_device_add_instance_id(device2, "bootloader");
	fu_device_add_private_flag(device2, FU_DEVICE_PRIVATE_FLAG_REPLUG_MATCH_GUID);

	/* verify only a changed event was generated */
	added_cnt = removed_cnt = changed_cnt = 0;
	fu_device_list_remove(device_list, device1);
	fu_device_list_add(device_list, device2);
	g_assert_cmpint(added_cnt, ==, 0);
	g_assert_cmpint(removed_cnt, ==, 0);
	g_assert_cmpint(changed_cnt, ==, 1);

	/* device2 should inherit the vendor ID and version from device1 */
	g_assert_true(fu_device_has_vendor_id(device2, "USB:0x20A0"));
	g_assert_cmpstr(fu_device_get_version(device2), ==, "1.2.3");

	/* one device is active */
	devices_active = fu_device_list_get_active(device_list);
	g_assert_cmpint(devices_active->len, ==, 1);
	device = g_ptr_array_index(devices_active, 0);
	g_assert_cmpstr(fu_device_get_id(device), ==, "1a8d0d9a96ad3e67ba76cf3033623625dc6d6882");

	/* the list knows about both devices, list in order of active->old */
	devices_all = fu_device_list_get_all(device_list);
	g_assert_cmpint(devices_all->len, ==, 2);
	device = g_ptr_array_index(devices_all, 0);
	g_assert_cmpstr(fu_device_get_id(device), ==, "1a8d0d9a96ad3e67ba76cf3033623625dc6d6882");
	device = g_ptr_array_index(devices_all, 1);
	g_assert_cmpstr(fu_device_get_id(device), ==, "99249eb1bd9ef0b6e192b271a8cb6a3090cfec7a");

	/* verify we can get the old device from the new device */
	device_old = fu_device_list_get_old(device_list, device2);
	g_assert_true(device_old == device1);
}

static void
fu_device_list_remove_chain_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	g_autoptr(FuDeviceList) device_list = fu_device_list_new();
	g_autoptr(FuDevice) device_child = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device_parent = fu_device_new(self->ctx);

	guint added_cnt = 0;
	guint changed_cnt = 0;
	guint removed_cnt = 0;

	g_signal_connect(FU_DEVICE_LIST(device_list),
			 "added",
			 G_CALLBACK(fu_test_device_list_count_cb),
			 &added_cnt);
	g_signal_connect(FU_DEVICE_LIST(device_list),
			 "removed",
			 G_CALLBACK(fu_test_device_list_count_cb),
			 &removed_cnt);
	g_signal_connect(FU_DEVICE_LIST(device_list),
			 "changed",
			 G_CALLBACK(fu_test_device_list_count_cb),
			 &changed_cnt);

	/* add child */
	fu_device_set_id(device_child, "child");
	fu_device_add_instance_id(device_child, "child-GUID-1");
	fu_device_list_add(device_list, device_child);
	g_assert_cmpint(added_cnt, ==, 1);
	g_assert_cmpint(removed_cnt, ==, 0);
	g_assert_cmpint(changed_cnt, ==, 0);

	/* add parent */
	fu_device_set_id(device_parent, "parent");
	fu_device_add_instance_id(device_parent, "parent-GUID-1");
	fu_device_add_child(device_parent, device_child);
	fu_device_list_add(device_list, device_parent);
	g_assert_cmpint(added_cnt, ==, 2);
	g_assert_cmpint(removed_cnt, ==, 0);
	g_assert_cmpint(changed_cnt, ==, 0);

	/* make sure that removing the parent causes both to go; but the child to go first */
	fu_device_list_remove(device_list, device_parent);
	g_assert_cmpint(added_cnt, ==, 2);
	g_assert_cmpint(removed_cnt, ==, 2);
	g_assert_cmpint(changed_cnt, ==, 0);
}

static void
fu_device_list_explicit_order_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	g_autoptr(FuDevice) device_child = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device_root = fu_device_new(self->ctx);
	g_autoptr(FuDeviceList) device_list = fu_device_list_new();

	/* add both */
	fu_device_set_id(device_root, "device");
	fu_device_add_instance_id(device_root, "foobar");
	fu_device_set_id(device_child, "device-child");
	fu_device_add_instance_id(device_child, "baz");
	fu_device_add_child(device_root, device_child);
	fu_device_list_add(device_list, device_root);

	fu_device_add_private_flag(device_root, FU_DEVICE_PRIVATE_FLAG_EXPLICIT_ORDER);
	fu_device_list_depsolve_order(device_list, device_root);
	g_assert_cmpint(fu_device_get_order(device_root), ==, G_MAXINT);
	g_assert_cmpint(fu_device_get_order(device_child), ==, G_MAXINT);
}

static void
fu_device_list_explicit_order_post_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	g_autoptr(FuDevice) device_child = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device_root = fu_device_new(self->ctx);
	g_autoptr(FuDeviceList) device_list = fu_device_list_new();

	/* add both */
	fu_device_set_id(device_root, "device");
	fu_device_add_instance_id(device_root, "foobar");
	fu_device_set_id(device_child, "device-child");
	fu_device_add_instance_id(device_child, "baz");
	fu_device_add_child(device_root, device_child);
	fu_device_list_add(device_list, device_root);
	fu_device_list_add(device_list, device_child);

	fu_device_list_depsolve_order(device_list, device_root);
	g_assert_cmpint(fu_device_get_order(device_root), ==, 0);
	g_assert_cmpint(fu_device_get_order(device_child), ==, -1);

	fu_device_add_private_flag(device_root, FU_DEVICE_PRIVATE_FLAG_EXPLICIT_ORDER);
	g_assert_cmpint(fu_device_get_order(device_root), ==, G_MAXINT);
	g_assert_cmpint(fu_device_get_order(device_child), ==, G_MAXINT);
}

static void
fu_device_list_install_parent_first_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	g_autoptr(FuDevice) device_child = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device_root = fu_device_new(self->ctx);
	g_autoptr(FuDeviceList) device_list = fu_device_list_new();

	/* add both */
	fu_device_set_id(device_root, "device");
	fu_device_add_instance_id(device_root, "foobar");
	fu_device_add_private_flag(device_root, FU_DEVICE_PRIVATE_FLAG_INSTALL_PARENT_FIRST);
	fu_device_set_id(device_child, "device-child");
	fu_device_add_instance_id(device_child, "baz");
	fu_device_add_child(device_root, device_child);
	fu_device_list_add(device_list, device_root);
	fu_device_list_add(device_list, device_child);

	fu_device_list_depsolve_order(device_list, device_root);
	g_assert_cmpint(fu_device_get_order(device_root), <, fu_device_get_order(device_child));
}

static void
fu_device_list_install_parent_first_child_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	g_autoptr(FuDevice) device_child = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device_root = fu_device_new(self->ctx);
	g_autoptr(FuDeviceList) device_list = fu_device_list_new();

	/* add both */
	fu_device_set_id(device_root, "device");
	fu_device_add_instance_id(device_root, "foobar");
	fu_device_set_id(device_child, "device-child");
	fu_device_add_instance_id(device_child, "baz");
	fu_device_add_private_flag(device_child, FU_DEVICE_PRIVATE_FLAG_INSTALL_PARENT_FIRST);
	fu_device_add_child(device_root, device_child);
	fu_device_list_add(device_list, device_root);
	fu_device_list_add(device_list, device_child);

	fu_device_list_depsolve_order(device_list, device_root);
	g_assert_cmpint(fu_device_get_order(device_root), <, fu_device_get_order(device_child));
}

static void
fu_device_list_better_than_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device1 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device2 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device_best = NULL;
	g_autoptr(FuDevice) device_replug = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuPlugin) plugin1 = fu_plugin_new(self->ctx);
	g_autoptr(FuPlugin) plugin2 = fu_plugin_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;

	/* add a bad plugin */
	fu_plugin_set_name(plugin2, "plugin2");
	fu_engine_add_plugin(engine, plugin2);

	/* add a good plugin */
	fu_plugin_set_name(plugin1, "plugin1");
	fu_plugin_add_rule(plugin1, FU_PLUGIN_RULE_BETTER_THAN, "plugin2");
	fu_engine_add_plugin(engine, plugin1);

	/* load the daemon */
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add a higher priority device */
	fu_device_set_id(device1, "87ea5dfc8b8e384d848979496e706390b497e547");
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_instance_id(device1, "12345678-1234-1234-1234-123456789012");
	fu_device_add_protocol(device1, "com.acme");
	fu_device_set_remove_delay(device1, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
	fu_plugin_device_add(plugin1, device1);

	/* should be ignored */
	fu_device_set_id(device2, "87ea5dfc8b8e384d848979496e706390b497e547");
	fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_instance_id(device2, "12345678-1234-1234-1234-123456789012");
	fu_device_add_protocol(device2, "com.acme");
	fu_plugin_device_add(plugin2, device2);

	/* ensure we still have device1 */
	device_best =
	    fu_engine_get_device(engine, "87ea5dfc8b8e384d848979496e706390b497e547", &error);
	g_assert_no_error(error);
	g_assert_nonnull(device_best);
	g_assert_true(device_best == device1);

	/* should be replaced */
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
	fu_plugin_device_add(plugin2, device2);

	/* ensure we now have device2 */
	device_replug =
	    fu_engine_get_device(engine, "87ea5dfc8b8e384d848979496e706390b497e547", &error);
	g_assert_no_error(error);
	g_assert_nonnull(device_replug);
	g_assert_true(device_replug == device2);
}

static void
fu_device_list_counterpart_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	g_autoptr(FuDeviceList) device_list = fu_device_list_new();
	g_autoptr(FuDevice) device1 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device2 = fu_device_new(self->ctx);

	/* add and then remove runtime */
	fu_device_set_id(device1, "device-runtime");
	fu_device_add_instance_id(device1, "runtime"); /* 420dde7c-3102-5d8f-86bc-aaabd7920150 */
	fu_device_add_instance_id_full(device1, "bootloader", FU_DEVICE_INSTANCE_FLAG_COUNTERPART);
	fu_device_set_remove_delay(device1, 100);
	fu_device_list_add(device_list, device1);
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
	fu_device_list_remove(device_list, device1);
	g_assert_true(fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG));

	/* add bootloader */
	fu_device_set_id(device2, "device-bootloader");
	fu_device_add_instance_id(device2, "bootloader"); /* 015370aa-26f2-5daa-9661-a75bf4c1a913 */
	fu_device_add_private_flag(device2, FU_DEVICE_PRIVATE_FLAG_REPLUG_MATCH_GUID);
	fu_device_add_private_flag(device2, FU_DEVICE_PRIVATE_FLAG_ADD_COUNTERPART_GUIDS);
	fu_device_list_add(device_list, device2);

	/* should have matched the runtime */
	g_assert_false(fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG));

	/* should not have *visible* GUID of runtime */
	g_assert_false(fu_device_has_guid(device2, "runtime"));
	g_assert_false(
	    fu_device_has_instance_id(device2, "runtime", FU_DEVICE_INSTANCE_FLAG_VISIBLE));
}

static void
fu_device_list_equivalent_id_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	g_autoptr(FuDevice) device1 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device2 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device3 = NULL;
	g_autoptr(FuDevice) device4 = NULL;
	g_autoptr(FuDeviceList) device_list = fu_device_list_new();
	g_autoptr(GError) error = NULL;

	fu_device_set_id(device1, "8e9cb71aeca70d2faedb5b8aaa263f6175086b2e");
	fu_device_list_add(device_list, device1);

	fu_device_set_id(device2, "1a8d0d9a96ad3e67ba76cf3033623625dc6d6882");
	fu_device_set_equivalent_id(device2, "8e9cb71aeca70d2faedb5b8aaa263f6175086b2e");
	fu_device_set_priority(device2, 999);
	fu_device_list_add(device_list, device2);

	device3 = fu_device_list_get_by_id(device_list, "8e9c", &error);
	g_assert_no_error(error);
	g_assert_nonnull(device3);
	g_assert_cmpstr(fu_device_get_id(device3), ==, "1a8d0d9a96ad3e67ba76cf3033623625dc6d6882");

	/* two devices with the 'same' priority */
	fu_device_set_priority(device2, 0);
	device4 = fu_device_list_get_by_id(device_list, "8e9c", &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_null(device4);
}

static void
fu_device_list_unconnected_no_delay_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	g_autoptr(FuDeviceList) device_list = fu_device_list_new();
	g_autoptr(FuDevice) device1 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device2 = fu_device_new(self->ctx);

	fu_device_set_id(device1, "device1");
	fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_instance_id(device1, "foobar");
	fu_device_list_add(device_list, device1);
	g_assert_false(fu_device_has_private_flag(device1, FU_DEVICE_PRIVATE_FLAG_UNCONNECTED));

	/* remove */
	fu_device_list_remove(device_list, device1);
	g_assert_true(fu_device_has_private_flag(device1, FU_DEVICE_PRIVATE_FLAG_UNCONNECTED));

	/* add back exact same device, then remove */
	fu_device_list_add(device_list, device1);
	g_assert_false(fu_device_has_private_flag(device1, FU_DEVICE_PRIVATE_FLAG_UNCONNECTED));
	fu_device_list_remove(device_list, device1);
	g_assert_true(fu_device_has_private_flag(device1, FU_DEVICE_PRIVATE_FLAG_UNCONNECTED));

	/* add back device with same ID, then remove */
	fu_device_set_id(device2, "device1");
	fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_instance_id(device2, "foobar");
	fu_device_list_add(device_list, device2);
	g_assert_false(fu_device_has_private_flag(device2, FU_DEVICE_PRIVATE_FLAG_UNCONNECTED));
	fu_device_list_remove(device_list, device2);
	g_assert_true(fu_device_has_private_flag(device2, FU_DEVICE_PRIVATE_FLAG_UNCONNECTED));
}

static void
fu_device_list_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	g_autoptr(FuDeviceList) device_list = fu_device_list_new();
	g_autoptr(FuDevice) device1 = fu_device_new(self->ctx);
	g_autoptr(FuDevice) device2 = fu_device_new(self->ctx);
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(GPtrArray) devices2 = NULL;
	g_autoptr(GError) error = NULL;
	FuDevice *device;
	guint added_cnt = 0;
	guint changed_cnt = 0;
	guint removed_cnt = 0;

	g_signal_connect(FU_DEVICE_LIST(device_list),
			 "added",
			 G_CALLBACK(fu_test_device_list_count_cb),
			 &added_cnt);
	g_signal_connect(FU_DEVICE_LIST(device_list),
			 "removed",
			 G_CALLBACK(fu_test_device_list_count_cb),
			 &removed_cnt);
	g_signal_connect(FU_DEVICE_LIST(device_list),
			 "changed",
			 G_CALLBACK(fu_test_device_list_count_cb),
			 &changed_cnt);

	/* add both */
	fu_device_set_id(device1, "device1");
	fu_device_add_instance_id(device1, "foobar");
	fu_device_list_add(device_list, device1);
	fu_device_set_id(device2, "device2");
	fu_device_add_instance_id(device2, "baz");
	fu_device_list_add(device_list, device2);
	g_assert_cmpint(added_cnt, ==, 2);
	g_assert_cmpint(removed_cnt, ==, 0);
	g_assert_cmpint(changed_cnt, ==, 0);

	/* get all */
	devices = fu_device_list_get_all(device_list);
	g_assert_cmpint(devices->len, ==, 2);
	device = g_ptr_array_index(devices, 0);
	g_assert_cmpstr(fu_device_get_id(device), ==, "99249eb1bd9ef0b6e192b271a8cb6a3090cfec7a");

	/* find by ID */
	device = fu_device_list_get_by_id(device_list,
					  "99249eb1bd9ef0b6e192b271a8cb6a3090cfec7a",
					  &error);
	g_assert_no_error(error);
	g_assert_nonnull(device);
	g_assert_cmpstr(fu_device_get_id(device), ==, "99249eb1bd9ef0b6e192b271a8cb6a3090cfec7a");
	g_clear_object(&device);

	/* find by GUID */
	device =
	    fu_device_list_get_by_guid(device_list, "579a3b1c-d1db-5bdc-b6b9-e2c1b28d5b8a", &error);
	g_assert_no_error(error);
	g_assert_nonnull(device);
	g_assert_cmpstr(fu_device_get_id(device), ==, "1a8d0d9a96ad3e67ba76cf3033623625dc6d6882");
	g_clear_object(&device);

	/* find by missing GUID */
	device = fu_device_list_get_by_guid(device_list, "notfound", &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND);
	g_assert_null(device);

	/* remove device */
	added_cnt = removed_cnt = changed_cnt = 0;
	fu_device_list_remove(device_list, device1);
	g_assert_cmpint(added_cnt, ==, 0);
	g_assert_cmpint(removed_cnt, ==, 1);
	g_assert_cmpint(changed_cnt, ==, 0);
	devices2 = fu_device_list_get_all(device_list);
	g_assert_cmpint(devices2->len, ==, 1);
	device = g_ptr_array_index(devices2, 0);
	g_assert_cmpstr(fu_device_get_id(device), ==, "1a8d0d9a96ad3e67ba76cf3033623625dc6d6882");
}

static void
fu_plugin_list_func(gconstpointer user_data)
{
	GPtrArray *plugins;
	FuPlugin *plugin;
	g_autoptr(FuPluginList) plugin_list = fu_plugin_list_new();
	g_autoptr(FuPlugin) plugin1 = fu_plugin_new(NULL);
	g_autoptr(FuPlugin) plugin2 = fu_plugin_new(NULL);
	g_autoptr(GError) error = NULL;

	fu_plugin_set_name(plugin1, "plugin1");
	fu_plugin_set_name(plugin2, "plugin2");

	/* get all the plugins */
	fu_plugin_list_add(plugin_list, plugin1);
	fu_plugin_list_add(plugin_list, plugin2);
	plugins = fu_plugin_list_get_all(plugin_list);
	g_assert_cmpint(plugins->len, ==, 2);

	/* get a single plugin */
	plugin = fu_plugin_list_find_by_name(plugin_list, "plugin1", &error);
	g_assert_no_error(error);
	g_assert_nonnull(plugin);
	g_assert_cmpstr(fu_plugin_get_name(plugin), ==, "plugin1");

	/* does not exist */
	plugin = fu_plugin_list_find_by_name(plugin_list, "nope", &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND);
	g_assert_null(plugin);
}

static void
fu_plugin_list_depsolve_func(gconstpointer user_data)
{
	GPtrArray *plugins;
	FuPlugin *plugin;
	gboolean ret;
	g_autoptr(FuPluginList) plugin_list = fu_plugin_list_new();
	g_autoptr(FuPlugin) plugin1 = fu_plugin_new(NULL);
	g_autoptr(FuPlugin) plugin2 = fu_plugin_new(NULL);
	g_autoptr(GError) error = NULL;

	fu_plugin_set_name(plugin1, "plugin1");
	fu_plugin_set_name(plugin2, "plugin2");

	/* add rule then depsolve */
	fu_plugin_list_add(plugin_list, plugin1);
	fu_plugin_list_add(plugin_list, plugin2);
	fu_plugin_add_rule(plugin1, FU_PLUGIN_RULE_RUN_AFTER, "plugin2");
	ret = fu_plugin_list_depsolve(plugin_list, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	plugins = fu_plugin_list_get_all(plugin_list);
	g_assert_cmpint(plugins->len, ==, 2);
	plugin = g_ptr_array_index(plugins, 0);
	g_assert_cmpstr(fu_plugin_get_name(plugin), ==, "plugin2");
	g_assert_cmpint(fu_plugin_get_order(plugin), ==, 0);
	g_assert_false(fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED));

	/* add another rule, then re-depsolve */
	fu_plugin_add_rule(plugin1, FU_PLUGIN_RULE_CONFLICTS, "plugin2");
	ret = fu_plugin_list_depsolve(plugin_list, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	plugin = fu_plugin_list_find_by_name(plugin_list, "plugin1", &error);
	g_assert_no_error(error);
	g_assert_nonnull(plugin);
	g_assert_false(fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED));
	plugin = fu_plugin_list_find_by_name(plugin_list, "plugin2", &error);
	g_assert_no_error(error);
	g_assert_nonnull(plugin);
	g_assert_true(fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED));
}

static void
fu_history_migrate_v1_func(gconstpointer user_data)
{
	gboolean ret;
	g_autoptr(FuContext) ctx = fu_context_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(GFile) file_dst = NULL;
	g_autoptr(GFile) file_src = NULL;
	g_autoptr(FuDevice) device = NULL;
	g_autoptr(FuHistory) history = NULL;
	g_autofree gchar *filename = NULL;

	/* load old version */
	filename = g_test_build_filename(G_TEST_DIST, "tests", "history_v1.db", NULL);
	file_src = g_file_new_for_path(filename);
	file_dst = g_file_new_for_path("/tmp/fwupd-self-test/var/lib/fwupd/pending.db");
	ret = g_file_copy(file_src, file_dst, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* create, migrating as required */
	history = fu_history_new(ctx);
	g_assert_nonnull(history);

	/* get device */
	device = fu_history_get_device_by_id(history,
					     "2ba16d10df45823dd4494ff10a0bfccfef512c9d",
					     &error);
	g_assert_no_error(error);
	g_assert_nonnull(device);
	g_assert_cmpstr(fu_device_get_id(device), ==, "2ba16d10df45823dd4494ff10a0bfccfef512c9d");
}

static void
fu_history_migrate_v2_func(gconstpointer user_data)
{
	gboolean ret;
	g_autoptr(FuContext) ctx = fu_context_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(GFile) file_dst = NULL;
	g_autoptr(GFile) file_src = NULL;
	g_autoptr(FuDevice) device = NULL;
	g_autoptr(FuHistory) history = NULL;
	g_autofree gchar *filename = NULL;

	/* load old version */
	filename = g_test_build_filename(G_TEST_DIST, "tests", "history_v2.db", NULL);
	file_src = g_file_new_for_path(filename);
	file_dst = g_file_new_for_path("/tmp/fwupd-self-test/var/lib/fwupd/pending.db");
	ret = g_file_copy(file_src, file_dst, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* create, migrating as required */
	history = fu_history_new(ctx);
	g_assert_nonnull(history);

	/* get device */
	device = fu_history_get_device_by_id(history,
					     "2ba16d10df45823dd4494ff10a0bfccfef512c9d",
					     &error);
	g_assert_no_error(error);
	g_assert_nonnull(device);
	g_assert_cmpstr(fu_device_get_id(device), ==, "2ba16d10df45823dd4494ff10a0bfccfef512c9d");
}

static void
fu_test_plugin_device_added_cb(FuPlugin *plugin, FuDevice *device, gpointer user_data)
{
	FuDevice **dev = (FuDevice **)user_data;
	*dev = g_object_ref(device);
	fu_test_loop_quit();
}

static void
fu_test_plugin_device_register_cb(FuPlugin *plugin, FuDevice *device, gpointer user_data)
{
	/* fake being a daemon */
	fu_plugin_runner_device_register(plugin, device);
}

static void
fu_backend_usb_hotplug_cb(FuBackend *backend, FuDevice *device, gpointer user_data)
{
	guint *cnt = (guint *)user_data;
	(*cnt)++;
}

static void
fu_backend_usb_load_file(FuBackend *backend, const gchar *fn)
{
	gboolean ret;
	g_autoptr(GError) error = NULL;
	g_autoptr(JsonParser) parser = json_parser_new();

	ret = json_parser_load_from_file(parser, fn, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fwupd_codec_from_json(FWUPD_CODEC(backend), json_parser_get_root(parser), &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

/*
 * To generate the fwupd DS20 descriptor in the usb-devices.json file save fw-ds20.builder.xml:
 *
 *    <firmware gtype="FuUsbDeviceFwDs20">
 *      <idx>42</idx>   <!-- bVendorCode -->
 *      <size>32</size> <!-- wLength -->
 *    </firmware>
 *
 * Then run:
 *
 *    fwupdtool firmware-build fw-ds20.builder.xml fw-ds20.bin
 *    base64 fw-ds20.bin
 *
 * To generate the fake control transfer response, save fw-ds20.quirk:
 *
 *    [USB\VID_273F&PID_1004]
 *    Plugin = dfu
 *    Icon = computer
 *
 * Then run:
 *
 *    contrib/generate-ds20.py fw-ds20.quirk --bufsz 32
 */
static void
fu_backend_usb_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	guint cnt_added = 0;
	guint cnt_removed = 0;
	FuDevice *device_tmp;
	g_autofree gchar *usb_emulate_fn = NULL;
	g_autofree gchar *usb_emulate_fn2 = NULL;
	g_autofree gchar *usb_emulate_fn3 = NULL;
	g_autofree gchar *devicestr = NULL;
	g_autoptr(FuBackend) backend = fu_usb_backend_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(GPtrArray) possible_plugins = NULL;

	/* check there were events */
	g_object_set(backend, "device-gtype", FU_TYPE_USB_DEVICE, NULL);
	g_signal_connect(backend,
			 "device-added",
			 G_CALLBACK(fu_backend_usb_hotplug_cb),
			 &cnt_added);
	g_signal_connect(backend,
			 "device-removed",
			 G_CALLBACK(fu_backend_usb_hotplug_cb),
			 &cnt_removed);

	/* load the JSON into the backend */
	g_assert_cmpstr(fu_backend_get_name(backend), ==, "usb");
	g_assert_true(fu_backend_get_enabled(backend));
	ret = fu_backend_setup(backend, FU_BACKEND_SETUP_FLAG_NONE, progress, &error);
	g_assert_cmpint(cnt_added, ==, 0);
	g_assert_cmpint(cnt_removed, ==, 0);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpint(cnt_added, ==, 0);
	g_assert_cmpint(cnt_removed, ==, 0);
	ret = fu_backend_coldplug(backend, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpint(cnt_added, ==, 0);
	g_assert_cmpint(cnt_removed, ==, 0);
	usb_emulate_fn = g_test_build_filename(G_TEST_DIST, "tests", "usb-devices.json", NULL);
	g_assert_nonnull(usb_emulate_fn);
	fu_backend_usb_load_file(backend, usb_emulate_fn);
	g_assert_cmpint(cnt_added, ==, 1);
	g_assert_cmpint(cnt_removed, ==, 0);
	devices = fu_backend_get_devices(backend);
	g_assert_cmpint(devices->len, ==, 1);
	device_tmp = g_ptr_array_index(devices, 0);
	fu_device_set_context(device_tmp, self->ctx);
	ret = fu_device_probe(device_tmp, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_true(fu_device_has_flag(device_tmp, FWUPD_DEVICE_FLAG_EMULATED));

	/* for debugging */
	devicestr = fu_device_to_string(device_tmp);
	g_debug("%s", devicestr);

	/* check the fwupd DS20 descriptor was parsed */
	g_assert_true(fu_device_has_icon(device_tmp, "computer"));
	possible_plugins = fu_device_get_possible_plugins(device_tmp);
	g_assert_cmpint(possible_plugins->len, ==, 1);
	g_assert_cmpstr(g_ptr_array_index(possible_plugins, 0), ==, "dfu");

	/* load another device with the same VID:PID, and check that we did not get a replug */
	usb_emulate_fn2 =
	    g_test_build_filename(G_TEST_DIST, "tests", "usb-devices-replace.json", NULL);
	g_assert_nonnull(usb_emulate_fn2);
	fu_backend_usb_load_file(backend, usb_emulate_fn2);
	g_assert_cmpint(cnt_added, ==, 1);
	g_assert_cmpint(cnt_removed, ==, 0);

	/* load another device with a different VID:PID, and check that we *did* get a replug */
	usb_emulate_fn3 =
	    g_test_build_filename(G_TEST_DIST, "tests", "usb-devices-bootloader.json", NULL);
	g_assert_nonnull(usb_emulate_fn3);
	fu_backend_usb_load_file(backend, usb_emulate_fn3);
	g_assert_cmpint(cnt_added, ==, 2);
	g_assert_cmpint(cnt_removed, ==, 1);
}

static void
fu_backend_usb_invalid_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	FuDevice *device_tmp;
	g_autofree gchar *usb_emulate_fn = NULL;
	g_autoptr(FuBackend) backend = fu_usb_backend_new(self->ctx);
	g_autoptr(FuDeviceLocker) locker = NULL;
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(JsonParser) parser = json_parser_new();

#ifndef SUPPORTED_BUILD
	g_test_expect_message("FuUsbDevice",
			      G_LOG_LEVEL_WARNING,
			      "*invalid platform version 0x0000000a, expected >= 0x00010805*");
	g_test_expect_message("FuUsbDevice",
			      G_LOG_LEVEL_WARNING,
			      "failed to parse * BOS descriptor: *invalid UUID*");
#endif

	/* load the JSON into the backend */
	g_object_set(backend, "device-gtype", FU_TYPE_USB_DEVICE, NULL);
	usb_emulate_fn =
	    g_test_build_filename(G_TEST_DIST, "tests", "usb-devices-invalid.json", NULL);
	ret = json_parser_load_from_file(parser, usb_emulate_fn, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_backend_setup(backend, FU_BACKEND_SETUP_FLAG_NONE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fwupd_codec_from_json(FWUPD_CODEC(backend), json_parser_get_root(parser), &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_backend_coldplug(backend, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	devices = fu_backend_get_devices(backend);
	g_assert_cmpint(devices->len, ==, 1);
	device_tmp = g_ptr_array_index(devices, 0);
	fu_device_set_context(device_tmp, self->ctx);

	locker = fu_device_locker_new(device_tmp, &error);
	g_assert_no_error(error);
	g_assert_nonnull(locker);

	/* check the device was processed correctly by FuUsbDevice */
	g_assert_cmpstr(fu_device_get_name(device_tmp), ==, "ColorHug2");
	g_assert_true(fu_device_has_instance_id(device_tmp,
						"USB\\VID_273F&PID_1004",
						FU_DEVICE_INSTANCE_FLAG_VISIBLE));
	g_assert_true(fu_device_has_vendor_id(device_tmp, "USB:0x273F"));

	/* check the fwupd DS20 descriptor was *not* parsed */
	g_assert_false(fu_device_has_icon(device_tmp, "computer"));
}

static void
fu_plugin_module_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	GError *error = NULL;
	gboolean ret;
	g_autoptr(FuDevice) device = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuPlugin) plugin = fu_plugin_new_from_gtype(fu_test_plugin_get_type(), self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* create a fake device */
	ret = fu_plugin_reset_config_values(plugin, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_plugin_set_config_value(plugin, "RegistrationSupported", "true", &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_plugin_runner_startup(plugin, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_signal_connect(FU_PLUGIN(plugin),
			 "device-added",
			 G_CALLBACK(fu_test_plugin_device_added_cb),
			 &device);
	g_signal_connect(FU_PLUGIN(plugin),
			 "device-register",
			 G_CALLBACK(fu_test_plugin_device_register_cb),
			 &device);
	ret = fu_plugin_runner_coldplug(plugin, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* check we did the right thing */
	g_assert_nonnull(device);
	g_assert_cmpstr(fu_device_get_id(device), ==, "08d460be0f1f9f128413f816022a6439e0078018");
	g_assert_cmpstr(fu_device_get_version_lowest(device), ==, "1.2.0");
	g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.2");
	g_assert_cmpstr(fu_device_get_version_bootloader(device), ==, "0.1.2");
	g_assert_cmpstr(fu_device_get_guid_default(device),
			==,
			"b585990a-003e-5270-89d5-3705a17f9a43");
	g_assert_cmpstr(fu_device_get_name(device), ==, "Integrated Webcam™");
	g_signal_handlers_disconnect_by_data(plugin, &device);
}

static void
fu_history_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	GPtrArray *checksums;
	gboolean ret;
	FuDevice *device;
	FuRelease *release;
	g_autoptr(FuDevice) device_found = NULL;
	g_autoptr(FuHistory) history = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(GPtrArray) approved_firmware = NULL;
	g_autofree gchar *dirname = NULL;
	g_autofree gchar *filename = NULL;

	/* create */
	history = fu_history_new(self->ctx);
	g_assert_nonnull(history);

	/* delete the database */
	dirname = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG);
	if (!g_file_test(dirname, G_FILE_TEST_IS_DIR))
		return;
	filename = g_build_filename(dirname, "pending.db", NULL);
	(void)g_unlink(filename);

	/* add a device */
	device = fu_device_new(self->ctx);
	fu_device_set_id(device, "self-test");
	fu_device_set_name(device, "ColorHug"),
	    fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_version(device, "3.0.1"),
	    fu_device_set_update_state(device, FWUPD_UPDATE_STATE_FAILED);
	fu_device_set_update_error(device, "word");
	fu_device_add_instance_id(device, "827edddd-9bb6-5632-889f-2c01255503da");
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INTERNAL);
	fu_device_set_created_usec(device, 1514338000ull * G_USEC_PER_SEC);
	fu_device_set_modified_usec(device, 1514338999ull * G_USEC_PER_SEC);
	release = fu_release_new();
	fu_release_set_filename(release, "/var/lib/dave.cap"),
	    fu_release_add_checksum(release, "abcdef");
	fu_release_set_version(release, "3.0.2");
	fu_release_add_metadata_item(release, "FwupdVersion", VERSION);
	ret = fu_history_add_device(history, device, release, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_object_unref(release);

	/* ensure database was created */
	g_assert_true(g_file_test(filename, G_FILE_TEST_EXISTS));

	g_object_unref(device);

	/* get device */
	device = fu_history_get_device_by_id(history,
					     "2ba16d10df45823dd4494ff10a0bfccfef512c9d",
					     &error);
	g_assert_no_error(error);
	g_assert_nonnull(device);
	g_assert_cmpstr(fu_device_get_id(device), ==, "2ba16d10df45823dd4494ff10a0bfccfef512c9d");
	g_assert_cmpstr(fu_device_get_name(device), ==, "ColorHug");
	g_assert_cmpstr(fu_device_get_version(device), ==, "3.0.1");
	g_assert_cmpint(fu_device_get_update_state(device), ==, FWUPD_UPDATE_STATE_FAILED);
	g_assert_cmpstr(fu_device_get_update_error(device), ==, "word");
	g_assert_cmpstr(fu_device_get_guid_default(device),
			==,
			"827edddd-9bb6-5632-889f-2c01255503da");
	g_assert_cmpint(fu_device_get_flags(device),
			==,
			FWUPD_DEVICE_FLAG_INTERNAL | FWUPD_DEVICE_FLAG_HISTORICAL);
	g_assert_cmpint(fu_device_get_created_usec(device), ==, 1514338000ull * G_USEC_PER_SEC);
	g_assert_cmpint(fu_device_get_modified_usec(device), ==, 1514338999ull * G_USEC_PER_SEC);
	release = FU_RELEASE(fu_device_get_release_default(device));
	g_assert_nonnull(release);
	g_assert_cmpstr(fu_release_get_version(release), ==, "3.0.2");
	g_assert_cmpstr(fu_release_get_filename(release), ==, "/var/lib/dave.cap");
	g_assert_cmpstr(fu_release_get_metadata_item(release, "FwupdVersion"), ==, VERSION);
	checksums = fu_release_get_checksums(release);
	g_assert_nonnull(checksums);
	g_assert_cmpint(checksums->len, ==, 1);
	g_assert_cmpstr(fwupd_checksum_get_by_kind(checksums, G_CHECKSUM_SHA1), ==, "abcdef");
	ret = fu_history_add_device(history, device, release, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* get device that does not exist */
	device_found = fu_history_get_device_by_id(history, "XXXXXXXXXXXXX", &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND);
	g_assert_null(device_found);
	g_clear_error(&error);

	/* get device that does exist */
	device_found = fu_history_get_device_by_id(history,
						   "2ba16d10df45823dd4494ff10a0bfccfef512c9d",
						   &error);
	g_assert_no_error(error);
	g_assert_nonnull(device_found);
	g_object_unref(device_found);

	/* remove device */
	ret = fu_history_remove_device(history, device, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_object_unref(device);

	/* get device that does not exist */
	device_found = fu_history_get_device_by_id(history,
						   "2ba16d10df45823dd4494ff10a0bfccfef512c9d",
						   &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND);
	g_assert_null(device_found);
	g_clear_error(&error);

	/* approved firmware */
	ret = fu_history_clear_approved_firmware(history, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_history_add_approved_firmware(history, "foo", &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_history_add_approved_firmware(history, "bar", &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	approved_firmware = fu_history_get_approved_firmware(history, &error);
	g_assert_no_error(error);
	g_assert_nonnull(approved_firmware);
	g_assert_cmpint(approved_firmware->len, ==, 2);
	g_assert_cmpstr(g_ptr_array_index(approved_firmware, 0), ==, "foo");
	g_assert_cmpstr(g_ptr_array_index(approved_firmware, 1), ==, "bar");

	/* emulation-tag */
	ret = fu_history_add_emulation_tag(history, "id", &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_history_has_emulation_tag(history, "id", &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_history_has_emulation_tag(history, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_history_remove_emulation_tag(history, "id", &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_history_remove_emulation_tag(history, "id", &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_history_has_emulation_tag(history, "id", &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND);
	g_assert_false(ret);
}

static GBytes *
fu_test_build_cab(gboolean compressed, ...)
{
	gboolean ret;
	va_list args;
	g_autoptr(FuCabFirmware) cabinet = fu_cab_firmware_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(GBytes) cabinet_blob = NULL;

	fu_cab_firmware_set_compressed(cabinet, compressed);

	/* add each file */
	va_start(args, compressed);
	do {
		const gchar *fn;
		const gchar *text;
		g_autoptr(FuCabImage) img = fu_cab_image_new();
		g_autoptr(GBytes) blob = NULL;

		/* get filename */
		fn = va_arg(args, const gchar *);
		if (fn == NULL)
			break;

		/* get contents */
		text = va_arg(args, const gchar *);
		if (text == NULL)
			break;
		g_debug("creating %s with %s", fn, text);

		/* add a GCabFile to the cabinet */
		blob = g_bytes_new_static(text, strlen(text));
		fu_firmware_set_id(FU_FIRMWARE(img), fn);
		fu_firmware_set_bytes(FU_FIRMWARE(img), blob);
		ret = fu_firmware_add_image(FU_FIRMWARE(cabinet), FU_FIRMWARE(img), &error);
		g_assert_no_error(error);
		g_assert_true(ret);
	} while (TRUE);
	va_end(args);

	/* write the archive to a blob */
	cabinet_blob = fu_firmware_write(FU_FIRMWARE(cabinet), &error);
	g_assert_no_error(error);
	g_assert_nonnull(cabinet_blob);
	return g_steal_pointer(&cabinet_blob);
}

static void
fu_test_plugin_composite_device_added_cb(FuPlugin *plugin, FuDevice *device, gpointer user_data)
{
	GPtrArray *devices = (GPtrArray *)user_data;
	g_ptr_array_add(devices, g_object_ref(device));
}

static gint
fu_plugin_composite_release_sort_cb(gconstpointer a, gconstpointer b)
{
	FuRelease *release1 = *((FuRelease **)a);
	FuRelease *release2 = *((FuRelease **)b);
	FuDevice *device1 = fu_release_get_device(release1);
	FuDevice *device2 = fu_release_get_device(release2);
	if (fu_device_get_order(device1) < fu_device_get_order(device2))
		return 1;
	if (fu_device_get_order(device1) > fu_device_get_order(device2))
		return -1;
	return 0;
}

static void
fu_plugin_composite_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	FuDevice *dev_tmp;
	GError *error = NULL;
	gboolean ret;
	g_autoptr(FuCabinet) cabinet = fu_cabinet_new();
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuPlugin) plugin = fu_plugin_new_from_gtype(fu_test_plugin_get_type(), self->ctx);
	g_autoptr(GBytes) blob = NULL;
	g_autoptr(GPtrArray) components = NULL;
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(GPtrArray) releases =
	    g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(XbSilo) silo_empty = xb_silo_new();

	/* no metadata in daemon */
	fu_engine_set_silo(engine, silo_empty);

	/* create CAB file */
	blob = fu_test_build_cab(
	    FALSE,
	    "acme.metainfo.xml",
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.example.firmware</id>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">b585990a-003e-5270-89d5-3705a17f9a43</firmware>\n"
	    "  </provides>\n"
	    "  <releases>\n"
	    "    <release version=\"1.2.3\"/>\n"
	    "  </releases>\n"
	    "</component>",
	    "acme.module1.metainfo.xml",
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.example.firmware.module1</id>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">7fddead7-12b5-4fb9-9fa0-6d30305df755</firmware>\n"
	    "  </provides>\n"
	    "  <releases>\n"
	    "    <release version=\"2\"/>\n"
	    "  </releases>\n"
	    "  <custom>\n"
	    "    <value key=\"LVFS::VersionFormat\">plain</value>\n"
	    "  </custom>\n"
	    "</component>",
	    "acme.module2.metainfo.xml",
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.example.firmware.module2</id>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">b8fe6b45-8702-4bcd-8120-ef236caac76f</firmware>\n"
	    "  </provides>\n"
	    "  <releases>\n"
	    "    <release version=\"11\"/>\n"
	    "  </releases>\n"
	    "  <custom>\n"
	    "    <value key=\"LVFS::VersionFormat\">plain</value>\n"
	    "  </custom>\n"
	    "</component>",
	    "firmware.bin",
	    "world",
	    NULL);
	ret = fu_firmware_parse_bytes(FU_FIRMWARE(cabinet),
				      blob,
				      0x0,
				      FU_FIRMWARE_PARSE_FLAG_CACHE_BLOB,
				      &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	components = fu_cabinet_get_components(cabinet, &error);
	g_assert_no_error(error);
	g_assert_nonnull(components);
	g_assert_cmpint(components->len, ==, 3);

	/* set up dummy plugin */
	ret = fu_plugin_reset_config_values(plugin, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_plugin_set_config_value(plugin, "CompositeChild", "true", &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	fu_engine_add_plugin(engine, plugin);

	ret = fu_plugin_runner_startup(plugin, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
	g_signal_connect(FU_PLUGIN(plugin),
			 "device-added",
			 G_CALLBACK(fu_test_plugin_composite_device_added_cb),
			 devices);

	ret = fu_plugin_runner_coldplug(plugin, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* check we found all composite devices  */
	g_assert_cmpint(devices->len, ==, 3);
	for (guint i = 0; i < devices->len; i++) {
		FuDevice *device = g_ptr_array_index(devices, i);
		fu_engine_add_device(engine, device);
		if (g_strcmp0(fu_device_get_id(device),
			      "08d460be0f1f9f128413f816022a6439e0078018") == 0) {
			g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.2");
		} else if (g_strcmp0(fu_device_get_id(device),
				     "c0a0a4aa6480ac28eea1ce164fbb466ca934e1ff") == 0) {
			g_assert_cmpstr(fu_device_get_version(device), ==, "1");
			g_assert_nonnull(fu_device_get_parent_internal(device));
		} else if (g_strcmp0(fu_device_get_id(device),
				     "bf455e9f371d2608d1cb67660fd2b335d3f6ef73") == 0) {
			g_assert_cmpstr(fu_device_get_version(device), ==, "10");
			g_assert_nonnull(fu_device_get_parent_internal(device));
		}
	}

	/* produce install tasks */
	for (guint i = 0; i < components->len; i++) {
		XbNode *component = g_ptr_array_index(components, i);

		/* do any devices pass the requirements */
		for (guint j = 0; j < devices->len; j++) {
			FuDevice *device = g_ptr_array_index(devices, j);
			g_autoptr(FuRelease) release = fu_release_new();
			g_autoptr(GError) error_local = NULL;

			/* is this component valid for the device */
			fu_release_set_device(release, device);
			fu_release_set_request(release, request);
			if (!fu_release_load(release,
					     cabinet,
					     component,
					     NULL,
					     FWUPD_INSTALL_FLAG_NONE,
					     &error_local)) {
				g_debug("requirement on %s:%s failed: %s",
					fu_device_get_id(device),
					xb_node_query_text(component, "id", NULL),
					error_local->message);
				continue;
			}

			g_ptr_array_add(releases, g_steal_pointer(&release));
		}
	}
	g_assert_cmpint(releases->len, ==, 3);

	/* sort these by version, forcing fu_engine_install_releases() to sort by device order */
	g_ptr_array_sort(releases, fu_plugin_composite_release_sort_cb);
	dev_tmp = fu_release_get_device(FU_RELEASE(g_ptr_array_index(releases, 0)));
	g_assert_cmpstr(fu_device_get_logical_id(dev_tmp), ==, "child1");
	dev_tmp = fu_release_get_device(FU_RELEASE(g_ptr_array_index(releases, 1)));
	g_assert_cmpstr(fu_device_get_logical_id(dev_tmp), ==, "child2");
	dev_tmp = fu_release_get_device(FU_RELEASE(g_ptr_array_index(releases, 2)));
	g_assert_cmpstr(fu_device_get_logical_id(dev_tmp), ==, NULL);

	/* install the cab */
	ret = fu_engine_install_releases(engine,
					 request,
					 releases,
					 cabinet,
					 progress,
					 FWUPD_INSTALL_FLAG_NONE,
					 &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* verify we installed the parent first */
	dev_tmp = fu_release_get_device(FU_RELEASE(g_ptr_array_index(releases, 0)));
	g_assert_cmpstr(fu_device_get_logical_id(dev_tmp), ==, NULL);
	dev_tmp = fu_release_get_device(FU_RELEASE(g_ptr_array_index(releases, 1)));
	g_assert_cmpstr(fu_device_get_logical_id(dev_tmp), ==, "child2");
	dev_tmp = fu_release_get_device(FU_RELEASE(g_ptr_array_index(releases, 2)));
	g_assert_cmpstr(fu_device_get_logical_id(dev_tmp), ==, "child1");

	/* verify everything upgraded */
	for (guint i = 0; i < devices->len; i++) {
		FuDevice *device = g_ptr_array_index(devices, i);
		const gchar *metadata;
		if (g_strcmp0(fu_device_get_id(device),
			      "08d460be0f1f9f128413f816022a6439e0078018") == 0) {
			g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.3");
		} else if (g_strcmp0(fu_device_get_id(device),
				     "c0a0a4aa6480ac28eea1ce164fbb466ca934e1ff") == 0) {
			g_assert_cmpstr(fu_device_get_version(device), ==, "2");
		} else if (g_strcmp0(fu_device_get_id(device),
				     "bf455e9f371d2608d1cb67660fd2b335d3f6ef73") == 0) {
			g_assert_cmpstr(fu_device_get_version(device), ==, "11");
		}

		/* verify prepare and cleanup ran on all devices */
		metadata = fu_device_get_metadata(device, "frimbulator");
		g_assert_cmpstr(metadata, ==, "1");
		metadata = fu_device_get_metadata(device, "frombulator");
		g_assert_cmpstr(metadata, ==, "1");
	}
}

static void
fu_plugin_composite_multistep_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);
	g_autoptr(FuPlugin) plugin = fu_plugin_new_from_gtype(fu_test_plugin_get_type(), self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(GPtrArray) devices = NULL;
	g_autoptr(GPtrArray) releases = NULL;
	g_autoptr(XbBuilder) builder = xb_builder_new();
	g_autoptr(XbBuilderSource) source = xb_builder_source_new();
	g_autoptr(XbSilo) silo = NULL;

	/* load engine to get FuConfig set up */
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add the fake metadata */
	ret = xb_builder_source_load_xml(
	    source,
	    "<?xml version=\"1.0\"?>\n"
	    "<components>\n"
	    "<component type=\"firmware\">\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">b585990a-003e-5270-89d5-3705a17f9a43</firmware>\n"
	    "  </provides>\n"
	    "  <custom>\n"
	    "    <value key=\"LVFS::VersionFormat\">triplet</value>\n"
	    "    <value key=\"LVFS::UpdateProtocol\">com.acme.test</value>\n"
	    "  </custom>\n"
	    "  <releases>\n"
	    "    <release id=\"1\" version=\"1.2.3\">\n"
	    "      <checksum type=\"sha1\" target=\"content\">aaa</checksum>\n"
	    "      <artifacts>\n"
	    "        <artifact type=\"binary\">\n"
	    "          <location>file://filename.cab</location>\n"
	    "          <checksum type=\"sha1\">ccc</checksum>\n"
	    "        </artifact>\n"
	    "      </artifacts>\n"
	    "    </release>\n"
	    "    <release id=\"1\" version=\"1.2.4\">\n"
	    "      <checksum type=\"sha1\" target=\"content\">bbb</checksum>\n"
	    "      <artifacts>\n"
	    "        <artifact type=\"binary\">\n"
	    "          <location>file://filename.cab</location>\n"
	    "          <checksum type=\"sha1\">ccc</checksum>\n"
	    "        </artifact>\n"
	    "      </artifacts>\n"
	    "    </release>\n"
	    "  </releases>\n"
	    "</component>\n"
	    "</components>",
	    XB_BUILDER_SOURCE_FLAG_NONE,
	    &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	xb_builder_import_source(builder, source);
	silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	fu_engine_set_silo(engine, silo);

	/* set up dummy plugin */
	ret = fu_plugin_reset_config_values(plugin, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	fu_engine_add_plugin(engine, plugin);
	ret = fu_plugin_runner_startup(plugin, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
	g_signal_connect(FU_PLUGIN(plugin),
			 "device-added",
			 G_CALLBACK(fu_test_plugin_composite_device_added_cb),
			 devices);
	ret = fu_plugin_runner_coldplug(plugin, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add all the found devices  */
	g_assert_cmpint(devices->len, ==, 1);
	for (guint i = 0; i < devices->len; i++) {
		FuDevice *device_tmp = g_ptr_array_index(devices, i);
		fu_engine_add_device(engine, device_tmp);
	}

	/* check we did not dedupe the composite cab */
	device = fu_engine_get_device(engine, "08d460be0f1f9f128413f816022a6439e0078018", &error);
	g_assert_no_error(error);
	g_assert_nonnull(device);
	releases = fu_engine_get_releases(engine,
					  request,
					  "08d460be0f1f9f128413f816022a6439e0078018",
					  &error);
	g_assert_no_error(error);
	g_assert_nonnull(releases);
	g_assert_cmpint(releases->len, ==, 2);
}

static void
fu_plugin_engine_get_results_appstream_id_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	FwupdRelease *release_default;
	g_autoptr(FuDevice) device_tmp = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuHistory) history = fu_history_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(FuRelease) release = fu_release_new();
	g_autoptr(FwupdDevice) device = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(XbBuilder) builder = xb_builder_new();
	g_autoptr(XbBuilderSource) source = xb_builder_source_new();
	g_autoptr(XbSilo) silo = NULL;

	/* load engine to get FuConfig set up */
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add the fake metadata */
	ret = xb_builder_source_load_xml(
	    source,
	    "<?xml version=\"1.0\"?>\n"
	    "<components>\n"
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.WRONGDEVICE.firmware</id>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">00000000-0000-0000-0000-000000000000</firmware>\n"
	    "  </provides>\n"
	    "  <custom>\n"
	    "    <value key=\"LVFS::VersionFormat\">triplet</value>\n"
	    "    <value key=\"LVFS::UpdateProtocol\">com.acme.test</value>\n"
	    "  </custom>\n"
	    "  <releases>\n"
	    "    <release version=\"1.2.3\">\n"
	    "      <checksum type=\"sha1\" target=\"content\">aaa</checksum>\n"
	    "      <artifacts>\n"
	    "        <artifact type=\"binary\">\n"
	    "        <checksum type=\"sha1\">7c211433f02071597741e6ff5a8ea34789abbf43</checksum>\n"
	    "        </artifact>\n"
	    "      </artifacts>\n"
	    "    </release>\n"
	    "  </releases>\n"
	    "</component>\n"
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.example.firmware</id>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">b585990a-003e-5270-89d5-3705a17f9a43</firmware>\n"
	    "  </provides>\n"
	    "  <custom>\n"
	    "    <value key=\"LVFS::VersionFormat\">triplet</value>\n"
	    "    <value key=\"LVFS::UpdateProtocol\">com.acme.test</value>\n"
	    "  </custom>\n"
	    "  <releases>\n"
	    "    <release version=\"1.2.3\">\n"
	    "      <checksum type=\"sha1\" target=\"content\">aaa</checksum>\n"
	    "      <artifacts>\n"
	    "        <artifact type=\"binary\">\n"
	    "        <checksum type=\"sha1\">7c211433f02071597741e6ff5a8ea34789abbf43</checksum>\n"
	    "        </artifact>\n"
	    "      </artifacts>\n"
	    "    </release>\n"
	    "  </releases>\n"
	    "</component>\n"
	    "</components>",
	    XB_BUILDER_SOURCE_FLAG_NONE,
	    &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	xb_builder_import_source(builder, source);
	silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	fu_engine_set_silo(engine, silo);

	/* add a dummy device  */
	fu_device_set_id(device_tmp, "08d460be0f1f9f128413f816022a6439e0078018");
	fu_engine_add_device(engine, device_tmp);
	fu_release_set_appstream_id(release, "com.acme.example.firmware");
	fu_release_add_checksum(release, "7c211433f02071597741e6ff5a8ea34789abbf43");
	fu_device_add_release(device_tmp, FWUPD_RELEASE(release));
	fu_device_set_update_state(device_tmp, FWUPD_UPDATE_STATE_SUCCESS);
	ret = fu_history_add_device(history, device_tmp, release, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* check we got the correct component */
	device = fu_engine_get_results(engine, "08d460be0f1f9f128413f816022a6439e0078018", &error);
	g_assert_no_error(error);
	g_assert_nonnull(device);
	release_default = fu_device_get_release_default(device);
	g_assert_nonnull(release_default);
	g_assert_cmpstr(fwupd_release_get_appstream_id(release_default),
			==,
			"com.acme.example.firmware");
}

static void
fu_security_attr_func(gconstpointer user_data)
{
	gboolean ret;
	g_autofree gchar *json1 = NULL;
	g_autofree gchar *json2 = NULL;
	g_autoptr(FuSecurityAttrs) attrs1 = fu_security_attrs_new();
	g_autoptr(FuSecurityAttrs) attrs2 = fu_security_attrs_new();
	g_autoptr(FwupdSecurityAttr) attr1 = fwupd_security_attr_new("org.fwupd.hsi.foo");
	g_autoptr(FwupdSecurityAttr) attr2 = fwupd_security_attr_new("org.fwupd.hsi.bar");
	g_autoptr(GError) error = NULL;

	fwupd_security_attr_set_plugin(attr1, "foo");
	fwupd_security_attr_set_created(attr1, 0);
	fwupd_security_attr_set_level(attr1, 1);
	fwupd_security_attr_set_plugin(attr2, "bar");
	fwupd_security_attr_set_created(attr2, 0);
	fwupd_security_attr_set_level(attr2, 2);
	fu_security_attrs_append(attrs1, attr1);
	fu_security_attrs_append(attrs1, attr2);

	json1 = fwupd_codec_to_json_string(FWUPD_CODEC(attrs1), FWUPD_CODEC_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_nonnull(json1);
	ret = g_strcmp0(json1,
			"{\n"
			"  \"SecurityAttributes\" : [\n"
			"    {\n"
			"      \"AppstreamId\" : \"org.fwupd.hsi.foo\",\n"
			"      \"HsiLevel\" : 1,\n"
			"      \"Plugin\" : \"foo\",\n"
			"      \"Uri\" : "
			"\"https://fwupd.github.io/libfwupdplugin/hsi.html#org.fwupd.hsi.foo\"\n"
			"    },\n"
			"    {\n"
			"      \"AppstreamId\" : \"org.fwupd.hsi.bar\",\n"
			"      \"HsiLevel\" : 2,\n"
			"      \"Plugin\" : \"bar\",\n"
			"      \"Uri\" : "
			"\"https://fwupd.github.io/libfwupdplugin/hsi.html#org.fwupd.hsi.bar\"\n"
			"    }\n"
			"  ]\n"
			"}") == 0;
	g_assert_true(ret);

	ret = fwupd_codec_from_json_string(FWUPD_CODEC(attrs2), json1, &error);
	if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) {
		g_test_skip(error->message);
		return;
	}
	g_assert_no_error(error);
	g_assert_true(ret);

	json2 = fwupd_codec_to_json_string(FWUPD_CODEC(attrs2), FWUPD_CODEC_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_nonnull(json2);
	ret = g_strcmp0(json2, json1) == 0;
	g_assert_true(ret);
}

static void
fu_common_cabinet_func(void)
{
	gboolean ret;
	g_autofree gchar *filename = NULL;
	g_autoptr(FuCabinet) cabinet = fu_cabinet_new();
	g_autoptr(FuFirmware) img1 = NULL;
	g_autoptr(FuFirmware) img2 = NULL;
	g_autoptr(GBytes) blob = NULL;
	g_autoptr(GBytes) jcat_blob1 = g_bytes_new_static("hello", 6);
	g_autoptr(GBytes) jcat_blob2 = g_bytes_new_static("hellX", 6);
	g_autoptr(GError) error = NULL;
	g_autoptr(GInputStream) stream = NULL;

	filename = g_test_build_filename(G_TEST_BUILT,
					 "tests",
					 "multiple-rels",
					 "multiple-rels-1.2.4.cab",
					 NULL);
	stream = fu_input_stream_from_path(filename, &error);
	g_assert_no_error(error);
	g_assert_nonnull(stream);
	ret = fu_firmware_parse_stream(FU_FIRMWARE(cabinet),
				       stream,
				       0x0,
				       FU_FIRMWARE_PARSE_FLAG_CACHE_STREAM,
				       &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* add */
	ret = fu_cabinet_add_file(cabinet, "firmware.jcat", jcat_blob1, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* replace */
	ret = fu_cabinet_add_file(cabinet, "firmware.jcat", jcat_blob2, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* get data */
	img1 = fu_firmware_get_image_by_id(FU_FIRMWARE(cabinet), "firmware.jcat", &error);
	g_assert_no_error(error);
	g_assert_nonnull(img1);
	blob = fu_firmware_get_bytes(FU_FIRMWARE(img1), &error);
	g_assert_no_error(error);
	g_assert_nonnull(blob);
	g_assert_cmpstr(g_bytes_get_data(blob, NULL), ==, "hellX");

	/* get data that does not exist */
	img2 = fu_firmware_get_image_by_id(FU_FIRMWARE(cabinet), "foo.jcat", &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND);
	g_assert_null(img2);
}

static void
fu_memcpy_func(gconstpointer user_data)
{
	const guint8 src[] = {'a', 'b', 'c', 'd', 'e'};
	gboolean ret;
	guint8 dst[4] = {0};
	g_autoptr(GError) error = NULL;

	/* copy entire buffer */
	ret = fu_memcpy_safe(dst, sizeof(dst), 0x0, src, sizeof(src), 0x0, 4, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpint(memcmp(src, dst, 4), ==, 0);

	/* copy first char */
	ret = fu_memcpy_safe(dst, sizeof(dst), 0x0, src, sizeof(src), 0x0, 1, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpint(dst[0], ==, 'a');

	/* copy last char */
	ret = fu_memcpy_safe(dst, sizeof(dst), 0x0, src, sizeof(src), 0x4, 1, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpint(dst[0], ==, 'e');

	/* copy nothing */
	ret = fu_memcpy_safe(dst, sizeof(dst), 0x0, src, sizeof(src), 0x0, 0, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* write past the end of dst */
	ret = fu_memcpy_safe(dst, sizeof(dst), 0x0, src, sizeof(src), 0x0, 5, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE);
	g_assert_false(ret);
	g_clear_error(&error);

	/* write past the end of dst with offset */
	ret = fu_memcpy_safe(dst, sizeof(dst), 0x1, src, sizeof(src), 0x0, 4, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE);
	g_assert_false(ret);
	g_clear_error(&error);

	/* read past the end of dst */
	ret = fu_memcpy_safe(dst, sizeof(dst), 0x0, src, sizeof(src), 0x0, 6, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_READ);
	g_assert_false(ret);
	g_clear_error(&error);

	/* read past the end of src with offset */
	ret = fu_memcpy_safe(dst, sizeof(dst), 0x0, src, sizeof(src), 0x4, 4, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_READ);
	g_assert_false(ret);
	g_clear_error(&error);
}

static void
fu_console_func(gconstpointer user_data)
{
	g_autoptr(FuConsole) console = fu_console_new();

	fu_console_set_status_length(console, 20);
	fu_console_set_percentage_length(console, 50);

	g_print("\n");
	for (guint i = 0; i < 100; i++) {
		fu_console_set_progress(console, FWUPD_STATUS_DECOMPRESSING, i);
		g_usleep(10000);
	}
	fu_console_set_progress(console, FWUPD_STATUS_IDLE, 0);
	for (guint i = 0; i < 100; i++) {
		guint pc = (i > 25 && i < 75) ? 0 : i;
		fu_console_set_progress(console, FWUPD_STATUS_LOADING, pc);
		g_usleep(10000);
	}
	fu_console_set_progress(console, FWUPD_STATUS_IDLE, 0);

	for (guint i = 0; i < 5000; i++) {
		fu_console_set_progress(console, FWUPD_STATUS_LOADING, 0);
		g_usleep(1000);
	}
	fu_console_set_progress(console, FWUPD_STATUS_IDLE, 0);
}

static gint
fu_release_compare_func_cb(gconstpointer a, gconstpointer b)
{
	FuRelease *release1 = *((FuRelease **)a);
	FuRelease *release2 = *((FuRelease **)b);
	return fu_release_compare(release1, release2);
}

static void
fu_release_compare_func(gconstpointer user_data)
{
	g_autoptr(GPtrArray) releases = g_ptr_array_new();
	g_autoptr(FuDevice) device1 = fu_device_new(NULL);
	g_autoptr(FuDevice) device2 = fu_device_new(NULL);
	g_autoptr(FuDevice) device3 = fu_device_new(NULL);
	g_autoptr(FuRelease) release1 = fu_release_new();
	g_autoptr(FuRelease) release2 = fu_release_new();
	g_autoptr(FuRelease) release3 = fu_release_new();

	fu_device_set_order(device1, 33);
	fu_release_set_device(release1, device1);
	fu_release_set_priority(release1, 0);
	fu_release_set_branch(release1, "1");

	fu_device_set_order(device2, 11);
	fu_release_set_device(release2, device2);
	fu_release_set_priority(release2, 0);
	fu_release_set_branch(release2, "2");

	fu_device_set_order(device3, 11);
	fu_release_set_device(release3, device3);
	fu_release_set_priority(release3, 99);
	fu_release_set_branch(release3, "3");

	g_ptr_array_add(releases, release1);
	g_ptr_array_add(releases, release2);
	g_ptr_array_add(releases, release3);

	/* order the install tasks */
	g_ptr_array_sort(releases, fu_release_compare_func_cb);
	g_assert_cmpint(releases->len, ==, 3);
	g_assert_cmpstr(fu_release_get_branch(g_ptr_array_index(releases, 0)), ==, "3");
	g_assert_cmpstr(fu_release_get_branch(g_ptr_array_index(releases, 1)), ==, "2");
	g_assert_cmpstr(fu_release_get_branch(g_ptr_array_index(releases, 2)), ==, "1");
}

static void
fu_release_uri_scheme_func(void)
{
	struct {
		const gchar *in;
		const gchar *op;
	} strs[] = {{"https://foo.bar/baz", "https"},
		    {"HTTP://FOO.BAR/BAZ", "http"},
		    {"ftp://", "ftp"},
		    {"ftp:", "ftp"},
		    {"foobarbaz", NULL},
		    {"", NULL},
		    {NULL, NULL}};
	for (guint i = 0; strs[i].in != NULL; i++) {
		g_autofree gchar *tmp = fu_release_uri_get_scheme(strs[i].in);
		g_assert_cmpstr(tmp, ==, strs[i].op);
	}
}

static void
fu_release_trusted_report_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	FwupdRelease *rel;
	gboolean ret;
	g_autofree gchar *filename = NULL;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FwupdRemote) remote = fwupd_remote_new();
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(GFile) file = NULL;
	g_autoptr(XbBuilder) builder = xb_builder_new();
	g_autoptr(XbBuilderNode) custom = xb_builder_node_new("custom");
	g_autoptr(XbBuilderSource) source = xb_builder_source_new();
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(GPtrArray) releases = NULL;
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);

	/* add fake LVFS remote */
	fwupd_remote_set_id(remote, "lvfs");
	fu_engine_add_remote(engine, remote);

	/* load engine to get FuConfig set up */
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* metadata with vendor id=123 */
	filename = g_test_build_filename(G_TEST_DIST, "tests", "metadata-report1.xml", NULL);
	file = g_file_new_for_path(filename);
	ret = xb_builder_source_load_file(source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	xb_builder_node_insert_text(custom, "value", "lvfs", "key", "fwupd::RemoteId", NULL);
	xb_builder_source_set_info(source, custom);
	xb_builder_import_source(builder, source);
	silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	fu_engine_set_silo(engine, silo);

	/* add a dummy device */
	fu_device_set_id(device, "dummy");
	fu_device_set_version(device, "1.2.2");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_protocol(device, "com.acme");
	fu_device_add_instance_id(device, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e");
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_engine_add_device(engine, device);

	/* ensure we set this as trusted */
	releases = fu_engine_get_releases_for_device(engine, request, device, &error);
	g_assert_no_error(error);
	g_assert_nonnull(releases);
	g_assert_cmpint(releases->len, ==, 1);
	rel = g_ptr_array_index(releases, 0);
	g_assert_true(fwupd_release_has_flag(rel, FWUPD_RELEASE_FLAG_TRUSTED_REPORT));
}

static void
fu_release_trusted_report_oem_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	FwupdRelease *rel;
	gboolean ret;
	g_autofree gchar *filename = NULL;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(GFile) file = NULL;
	g_autoptr(XbBuilder) builder = xb_builder_new();
	g_autoptr(XbBuilderSource) source = xb_builder_source_new();
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(GPtrArray) releases = NULL;
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);

	/* load engine to get FuConfig set up */
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* metadata with FromOEM */
	filename = g_test_build_filename(G_TEST_DIST, "tests", "metadata-report2.xml", NULL);
	file = g_file_new_for_path(filename);
	ret = xb_builder_source_load_file(source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	xb_builder_import_source(builder, source);
	silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	fu_engine_set_silo(engine, silo);

	/* add a dummy device */
	fu_device_set_id(device, "dummy");
	fu_device_set_version(device, "1.2.2");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_protocol(device, "com.acme");
	fu_device_add_instance_id(device, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e");
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_engine_add_device(engine, device);

	/* ensure we set this as trusted */
	releases = fu_engine_get_releases_for_device(engine, request, device, &error);
	g_assert_no_error(error);
	g_assert_nonnull(releases);
	g_assert_cmpint(releases->len, ==, 1);
	rel = g_ptr_array_index(releases, 0);
	g_assert_true(fwupd_release_has_flag(rel, FWUPD_RELEASE_FLAG_TRUSTED_REPORT));
}

static void
fu_release_no_trusted_report_upgrade_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	FwupdRelease *rel;
	gboolean ret;
	g_autofree gchar *filename = NULL;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(GFile) file = NULL;
	g_autoptr(XbBuilder) builder = xb_builder_new();
	g_autoptr(XbBuilderSource) source = xb_builder_source_new();
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(GPtrArray) releases = NULL;
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);

	/* load engine to get FuConfig set up */
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* metadata with FromOEM, but *NOT* an upgrade */
	filename = g_test_build_filename(G_TEST_DIST, "tests", "metadata-report4.xml", NULL);
	file = g_file_new_for_path(filename);
	ret = xb_builder_source_load_file(source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	xb_builder_import_source(builder, source);
	silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	fu_engine_set_silo(engine, silo);

	/* add a dummy device */
	fu_device_set_id(device, "dummy");
	fu_device_set_version(device, "1.2.3");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_protocol(device, "com.acme");
	fu_device_add_instance_id(device, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e");
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_engine_add_device(engine, device);

	/* ensure we set this as trusted */
	releases = fu_engine_get_releases_for_device(engine, request, device, &error);
	g_assert_no_error(error);
	g_assert_nonnull(releases);
	g_assert_cmpint(releases->len, ==, 1);
	rel = g_ptr_array_index(releases, 0);
	g_assert_false(fwupd_release_has_flag(rel, FWUPD_RELEASE_FLAG_TRUSTED_REPORT));
}

static void
fu_release_no_trusted_report_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	FwupdRelease *rel;
	gboolean ret;
	g_autofree gchar *filename = NULL;
	g_autoptr(FuDevice) device = fu_device_new(self->ctx);
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;
	g_autoptr(GFile) file = NULL;
	g_autoptr(XbBuilder) builder = xb_builder_new();
	g_autoptr(XbBuilderSource) source = xb_builder_source_new();
	g_autoptr(XbSilo) silo = NULL;
	g_autoptr(GPtrArray) releases = NULL;
	g_autoptr(FuEngineRequest) request = fu_engine_request_new(NULL);

	/* load engine to get FuConfig set up */
	ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* metadata without OEM or valid VendorId as per tests/fwupd.conf */
	filename = g_test_build_filename(G_TEST_DIST, "tests", "metadata-report3.xml", NULL);
	file = g_file_new_for_path(filename);
	ret = xb_builder_source_load_file(source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	xb_builder_import_source(builder, source);
	silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error);
	g_assert_no_error(error);
	g_assert_nonnull(silo);
	fu_engine_set_silo(engine, silo);

	/* add a dummy device */
	fu_device_set_id(device, "dummy");
	fu_device_set_version(device, "1.2.2");
	fu_device_build_vendor_id_u16(device, "USB", 0xFFFF);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_protocol(device, "com.acme");
	fu_device_add_instance_id(device, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e");
	fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET);
	fu_engine_add_device(engine, device);

	/* ensure trusted reports flag is not set */
	releases = fu_engine_get_releases_for_device(engine, request, device, &error);
	g_assert_no_error(error);
	g_assert_nonnull(releases);
	g_assert_cmpint(releases->len, ==, 1);
	rel = g_ptr_array_index(releases, 0);
	g_assert_false(fwupd_release_has_flag(rel, FWUPD_RELEASE_FLAG_TRUSTED_REPORT));
}

static void
fu_common_store_cab_func(void)
{
	gboolean ret;
	GBytes *blob_tmp;
	g_autoptr(FuCabinet) cabinet = fu_cabinet_new();
	g_autoptr(GBytes) blob = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbNode) csum = NULL;
	g_autoptr(XbNode) rel = NULL;
	g_autoptr(XbNode) req = NULL;
	g_autoptr(XbQuery) query = NULL;

	/* create silo */
	blob = fu_test_build_cab(
	    FALSE,
	    "acme.metainfo.xml",
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.example.firmware</id>\n"
	    "  <name>ACME Firmware</name>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">ae56e3fb-6528-5bc4-8b03-012f124075d7</firmware>\n"
	    "  </provides>\n"
	    "  <releases>\n"
	    "    <release version=\"1.2.3\" date=\"2017-09-06\">\n"
	    "      <size type=\"installed\">5</size>\n"
	    "      <checksum filename=\"firmware.dfu\" target=\"content\" "
	    "type=\"sha1\">7c211433f02071597741e6ff5a8ea34789abbf43</checksum>\n"
	    "      <description><p>We fixed things</p></description>\n"
	    "    </release>\n"
	    "  </releases>\n"
	    "  <requires>\n"
	    "    <id compare=\"ge\" version=\"1.0.1\">org.freedesktop.fwupd</id>\n"
	    "  </requires>\n"
	    "</component>",
	    "firmware.dfu",
	    "world",
	    "firmware.dfu.asc",
	    "signature",
	    NULL);
	ret = fu_firmware_parse_bytes(FU_FIRMWARE(cabinet),
				      blob,
				      0x0,
				      FU_FIRMWARE_PARSE_FLAG_CACHE_BLOB,
				      &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* verify */
	component = fu_cabinet_get_component(cabinet, "com.acme.example.firmware", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);
	query = xb_query_new_full(xb_node_get_silo(component),
				  "releases/release",
				  XB_QUERY_FLAG_FORCE_NODE_CACHE,
				  &error);
	g_assert_no_error(error);
	g_assert_nonnull(query);
	rel = xb_node_query_first_full(component, query, &error);
	g_assert_no_error(error);
	g_assert_nonnull(rel);
	g_assert_cmpstr(xb_node_get_attr(rel, "version"), ==, "1.2.3");
	csum = xb_node_query_first(rel, "checksum[@target='content']", &error);
	g_assert_nonnull(csum);
	g_assert_cmpstr(xb_node_get_text(csum), ==, "7c211433f02071597741e6ff5a8ea34789abbf43");
	blob_tmp = xb_node_get_data(rel, "fwupd::FirmwareBasename");
	g_assert_nonnull(blob_tmp);
	req = xb_node_query_first(component, "requires/id", &error);
	g_assert_no_error(error);
	g_assert_nonnull(req);
}

static void
fu_common_store_cab_artifact_func(void)
{
	gboolean ret;
	g_autoptr(GBytes) blob1 = NULL;
	g_autoptr(GBytes) blob2 = NULL;
	g_autoptr(GBytes) blob3 = NULL;
	g_autoptr(GBytes) blob4 = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(FuCabinet) cabinet1 = fu_cabinet_new();
	g_autoptr(FuCabinet) cabinet2 = fu_cabinet_new();
	g_autoptr(FuCabinet) cabinet3 = fu_cabinet_new();
	g_autoptr(FuCabinet) cabinet4 = fu_cabinet_new();

	/* create silo (sha256, using artifacts object) */
	blob1 = fu_test_build_cab(
	    FALSE,
	    "acme.metainfo.xml",
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.example.firmware</id>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">b585990a-003e-5270-89d5-3705a17f9a43</firmware>\n"
	    "  </provides>\n"
	    "  <releases>\n"
	    "    <release version=\"1.2.3\" date=\"2017-09-06\">\n"
	    "      <artifacts>\n"
	    "        <artifact type=\"source\">\n"
	    "          <filename>firmware.dfu</filename>\n"
	    "          <checksum "
	    "type=\"sha256\">486EA46224D1BB4FB680F34F7C9AD96A8F24EC88BE73EA8E5A6C65260E9CB8A7</"
	    "checksum>\n"
	    "        </artifact>\n"
	    "      </artifacts>\n"
	    "    </release>\n"
	    "  </releases>\n"
	    "</component>",
	    "firmware.dfu",
	    "world",
	    "firmware.dfu.asc",
	    "signature",
	    NULL);
	ret = fu_firmware_parse_bytes(FU_FIRMWARE(cabinet1),
				      blob1,
				      0x0,
				      FU_FIRMWARE_PARSE_FLAG_CACHE_BLOB,
				      &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* create silo (sha1, using artifacts object; mixed case) */
	blob2 = fu_test_build_cab(
	    FALSE,
	    "acme.metainfo.xml",
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.example.firmware</id>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">b585990a-003e-5270-89d5-3705a17f9a43</firmware>\n"
	    "  </provides>\n"
	    "  <releases>\n"
	    "    <release version=\"1.2.3\" date=\"2017-09-06\">\n"
	    "      <artifacts>\n"
	    "        <artifact type=\"source\">\n"
	    "          <filename>firmware.dfu</filename>\n"
	    "          <checksum "
	    "type=\"sha1\">7c211433f02071597741e6ff5a8ea34789abbF43</"
	    "checksum>\n"
	    "        </artifact>\n"
	    "      </artifacts>\n"
	    "    </release>\n"
	    "  </releases>\n"
	    "</component>",
	    "firmware.dfu",
	    "world",
	    "firmware.dfu.asc",
	    "signature",
	    NULL);
	ret = fu_firmware_parse_bytes(FU_FIRMWARE(cabinet2),
				      blob2,
				      0x0,
				      FU_FIRMWARE_PARSE_FLAG_CACHE_BLOB,
				      &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* create silo (sha512, using artifacts object; lower case) */
	blob3 = fu_test_build_cab(
	    FALSE,
	    "acme.metainfo.xml",
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.example.firmware</id>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">b585990a-003e-5270-89d5-3705a17f9a43</firmware>\n"
	    "  </provides>\n"
	    "  <releases>\n"
	    "    <release version=\"1.2.3\" date=\"2017-09-06\">\n"
	    "      <artifacts>\n"
	    "        <artifact type=\"source\">\n"
	    "          <filename>firmware.dfu</filename>\n"
	    "          <checksum "
	    "type=\"sha512\">"
	    "11853df40f4b2b919d3815f64792e58d08663767a494bcbb38c0b2389d9140bbb170281b"
	    "4a847be7757bde12c9cd0054ce3652d0ad3a1a0c92babb69798246ee</"
	    "checksum>\n"
	    "        </artifact>\n"
	    "      </artifacts>\n"
	    "    </release>\n"
	    "  </releases>\n"
	    "</component>",
	    "firmware.dfu",
	    "world",
	    "firmware.dfu.asc",
	    "signature",
	    NULL);
	ret = fu_firmware_parse_bytes(FU_FIRMWARE(cabinet3),
				      blob3,
				      0x0,
				      FU_FIRMWARE_PARSE_FLAG_CACHE_BLOB,
				      &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* create silo (legacy release object) */
	blob4 = fu_test_build_cab(
	    FALSE,
	    "acme.metainfo.xml",
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.example.firmware</id>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">b585990a-003e-5270-89d5-3705a17f9a43</firmware>\n"
	    "  </provides>\n"
	    "  <releases>\n"
	    "    <release version=\"1.2.3\" date=\"2017-09-06\">\n"
	    "        <checksum "
	    "target=\"content\" "
	    "filename=\"firmware.dfu\">"
	    "486EA46224D1BB4FB680F34F7C9AD96A8F24EC88BE73EA8E5A6C65260E9CB8A7</"
	    "checksum>\n"
	    "    </release>\n"
	    "  </releases>\n"
	    "</component>",
	    "firmware.dfu",
	    "world",
	    "firmware.dfu.asc",
	    "signature",
	    NULL);
	ret = fu_firmware_parse_bytes(FU_FIRMWARE(cabinet4),
				      blob4,
				      0x0,
				      FU_FIRMWARE_PARSE_FLAG_CACHE_BLOB,
				      &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_common_store_cab_unsigned_func(void)
{
	GBytes *blob_tmp;
	gboolean ret;
	g_autoptr(FuCabinet) cabinet = fu_cabinet_new();
	g_autoptr(GBytes) blob = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbNode) csum = NULL;
	g_autoptr(XbNode) rel = NULL;
	g_autoptr(XbQuery) query = NULL;

	/* create silo */
	blob = fu_test_build_cab(
	    FALSE,
	    "acme.metainfo.xml",
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.example.firmware</id>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">b585990a-003e-5270-89d5-3705a17f9a43</firmware>\n"
	    "  </provides>\n"
	    "  <releases>\n"
	    "    <release version=\"1.2.3\"/>\n"
	    "  </releases>\n"
	    "</component>",
	    "firmware.bin",
	    "world",
	    NULL);
	ret = fu_firmware_parse_bytes(FU_FIRMWARE(cabinet),
				      blob,
				      0x0,
				      FU_FIRMWARE_PARSE_FLAG_CACHE_BLOB,
				      &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* verify */
	component = fu_cabinet_get_component(cabinet, "com.acme.example.firmware", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);
	query = xb_query_new_full(xb_node_get_silo(component),
				  "releases/release",
				  XB_QUERY_FLAG_FORCE_NODE_CACHE,
				  &error);
	g_assert_no_error(error);
	g_assert_nonnull(query);
	rel = xb_node_query_first_full(component, query, &error);
	g_assert_no_error(error);
	g_assert_nonnull(rel);
	g_assert_cmpstr(xb_node_get_attr(rel, "version"), ==, "1.2.3");
	csum = xb_node_query_first(rel, "checksum[@target='content']", &error);
	g_assert_null(csum);
	blob_tmp = xb_node_get_data(rel, "fwupd::FirmwareBasename");
	g_assert_nonnull(blob_tmp);
}

static void
fu_common_store_cab_sha256_func(void)
{
	gboolean ret;
	g_autoptr(FuCabinet) cabinet = fu_cabinet_new();
	g_autoptr(GBytes) blob = NULL;
	g_autoptr(GError) error = NULL;

	/* create silo */
	blob = fu_test_build_cab(
	    FALSE,
	    "acme.metainfo.xml",
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.example.firmware</id>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">b585990a-003e-5270-89d5-3705a17f9a43</firmware>\n"
	    "  </provides>\n"
	    "  <releases>\n"
	    "    <release version=\"1.2.3\" date=\"2017-09-06\">\n"
	    "      <checksum target=\"content\" "
	    "type=\"sha256\">486ea46224d1bb4fb680f34f7c9ad96a8f24ec88be73ea8e5a6c65260e9cb8a7</"
	    "checksum>\n"
	    "    </release>\n"
	    "  </releases>\n"
	    "</component>",
	    "firmware.bin",
	    "world",
	    NULL);
	ret = fu_firmware_parse_bytes(FU_FIRMWARE(cabinet),
				      blob,
				      0x0,
				      FU_FIRMWARE_PARSE_FLAG_CACHE_BLOB,
				      &error);
	g_assert_no_error(error);
	g_assert_true(ret);
}

static void
fu_common_store_cab_folder_func(void)
{
	GBytes *blob_tmp;
	gboolean ret;
	g_autoptr(FuCabinet) cabinet = fu_cabinet_new();
	g_autoptr(GBytes) blob = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(XbNode) component = NULL;
	g_autoptr(XbNode) rel = NULL;
	g_autoptr(XbQuery) query = NULL;

	/* create silo */
	blob = fu_test_build_cab(
	    FALSE,
	    "lvfs\\acme.metainfo.xml",
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.example.firmware</id>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">b585990a-003e-5270-89d5-3705a17f9a43</firmware>\n"
	    "  </provides>\n"
	    "  <releases>\n"
	    "    <release version=\"1.2.3\"/>\n"
	    "  </releases>\n"
	    "</component>",
	    "lvfs\\firmware.bin",
	    "world",
	    NULL);
	ret = fu_firmware_parse_bytes(FU_FIRMWARE(cabinet),
				      blob,
				      0x0,
				      FU_FIRMWARE_PARSE_FLAG_CACHE_BLOB,
				      &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* verify */
	component = fu_cabinet_get_component(cabinet, "com.acme.example.firmware", &error);
	g_assert_no_error(error);
	g_assert_nonnull(component);
	query = xb_query_new_full(xb_node_get_silo(component),
				  "releases/release",
				  XB_QUERY_FLAG_FORCE_NODE_CACHE,
				  &error);
	g_assert_no_error(error);
	g_assert_nonnull(query);
	rel = xb_node_query_first_full(component, query, &error);
	g_assert_no_error(error);
	g_assert_nonnull(rel);
	g_assert_cmpstr(xb_node_get_attr(rel, "version"), ==, "1.2.3");
	blob_tmp = xb_node_get_data(rel, "fwupd::FirmwareBasename");
	g_assert_nonnull(blob_tmp);
}

static void
fu_common_store_cab_error_no_metadata_func(void)
{
	gboolean ret;
	g_autoptr(FuCabinet) cabinet = fu_cabinet_new();
	g_autoptr(GBytes) blob = NULL;
	g_autoptr(GError) error = NULL;

	blob = fu_test_build_cab(FALSE, "foo.txt", "hello", "bar.txt", "world", NULL);
	ret = fu_firmware_parse_bytes(FU_FIRMWARE(cabinet),
				      blob,
				      0x0,
				      FU_FIRMWARE_PARSE_FLAG_NONE,
				      &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE);
	g_assert_false(ret);
}

static void
fu_common_store_cab_error_wrong_size_func(void)
{
	gboolean ret;
	g_autoptr(FuCabinet) cabinet = fu_cabinet_new();
	g_autoptr(GBytes) blob = NULL;
	g_autoptr(GError) error = NULL;

	blob = fu_test_build_cab(
	    FALSE,
	    "acme.metainfo.xml",
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.example.firmware</id>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">b585990a-003e-5270-89d5-3705a17f9a43</firmware>\n"
	    "  </provides>\n"
	    "  <releases>\n"
	    "    <release version=\"1.2.3\">\n"
	    "      <size type=\"installed\">7004701</size>\n"
	    "      <checksum filename=\"firmware.bin\" target=\"content\" "
	    "type=\"sha1\">deadbeef</checksum>\n"
	    "    </release>\n"
	    "  </releases>\n"
	    "</component>",
	    "firmware.bin",
	    "world",
	    NULL);
	ret = fu_firmware_parse_bytes(FU_FIRMWARE(cabinet),
				      blob,
				      0x0,
				      FU_FIRMWARE_PARSE_FLAG_NONE,
				      &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE);
	g_assert_false(ret);
}

static void
fu_common_store_cab_error_missing_file_func(void)
{
	gboolean ret;
	g_autoptr(FuCabinet) cabinet = fu_cabinet_new();
	g_autoptr(GBytes) blob = NULL;
	g_autoptr(GError) error = NULL;

	blob = fu_test_build_cab(
	    FALSE,
	    "acme.metainfo.xml",
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.example.firmware</id>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">b585990a-003e-5270-89d5-3705a17f9a43</firmware>\n"
	    "  </provides>\n"
	    "  <releases>\n"
	    "    <release version=\"1.2.3\">\n"
	    "      <checksum filename=\"firmware.dfu\" target=\"content\"/>\n"
	    "    </release>\n"
	    "  </releases>\n"
	    "</component>",
	    "firmware.bin",
	    "world",
	    NULL);
	ret = fu_firmware_parse_bytes(FU_FIRMWARE(cabinet),
				      blob,
				      0x0,
				      FU_FIRMWARE_PARSE_FLAG_NONE,
				      &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE);
	g_assert_false(ret);
}

static void
fu_common_store_cab_error_size_func(void)
{
	gboolean ret;
	g_autoptr(FuCabinet) cabinet = fu_cabinet_new();
	g_autoptr(GBytes) blob = NULL;
	g_autoptr(GError) error = NULL;

	blob = fu_test_build_cab(
	    FALSE,
	    "acme.metainfo.xml",
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.example.firmware</id>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">b585990a-003e-5270-89d5-3705a17f9a43</firmware>\n"
	    "  </provides>\n"
	    "  <releases>\n"
	    "    <release version=\"1.2.3\"/>\n"
	    "  </releases>\n"
	    "</component>",
	    "firmware.bin",
	    "world",
	    NULL);
	fu_firmware_set_size_max(FU_FIRMWARE(cabinet), 123);
	ret = fu_firmware_parse_bytes(FU_FIRMWARE(cabinet),
				      blob,
				      0x0,
				      FU_FIRMWARE_PARSE_FLAG_NONE,
				      &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE);
	g_assert_false(ret);
}

static void
fu_common_store_cab_error_wrong_checksum_func(void)
{
	gboolean ret;
	g_autoptr(FuCabinet) cabinet = fu_cabinet_new();
	g_autoptr(GBytes) blob = NULL;
	g_autoptr(GError) error = NULL;

	blob = fu_test_build_cab(
	    FALSE,
	    "acme.metainfo.xml",
	    "<component type=\"firmware\">\n"
	    "  <id>com.acme.example.firmware</id>\n"
	    "  <provides>\n"
	    "    <firmware type=\"flashed\">b585990a-003e-5270-89d5-3705a17f9a43</firmware>\n"
	    "  </provides>\n"
	    "  <releases>\n"
	    "    <release version=\"1.2.3\">\n"
	    "      <checksum filename=\"firmware.bin\" target=\"content\" "
	    "type=\"sha1\">deadbeef</checksum>\n"
	    "    </release>\n"
	    "  </releases>\n"
	    "</component>",
	    "firmware.bin",
	    "world",
	    NULL);
	ret = fu_firmware_parse_bytes(FU_FIRMWARE(cabinet),
				      blob,
				      0x0,
				      FU_FIRMWARE_PARSE_FLAG_NONE,
				      &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE);
	g_assert_false(ret);
}

static void
fu_engine_modify_bios_settings_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	const gchar *current;
	FwupdBiosSetting *attr1;
	FwupdBiosSetting *attr2;
	FwupdBiosSetting *attr3;
	FwupdBiosSetting *attr4;
	g_autofree gchar *test_dir = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(GError) error = NULL;
	g_autoptr(FuBiosSettings) attrs = NULL;
	g_autoptr(GPtrArray) items = NULL;
	g_autoptr(GHashTable) bios_settings =
	    g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);

#ifdef _WIN32
	g_test_skip("BIOS settings not supported on Windows");
	return;
#endif

	/* load contrived attributes */
	test_dir = g_test_build_filename(G_TEST_DIST, "tests", "bios-attrs", NULL);
	(void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", test_dir, TRUE);

	ret = fu_context_reload_bios_settings(fu_engine_get_context(engine), &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	attrs = fu_context_get_bios_settings(fu_engine_get_context(engine));
	items = fu_bios_settings_get_all(attrs);
	g_assert_cmpint(items->len, ==, 4);

	/* enumeration */
	attr1 = fu_context_get_bios_setting(fu_engine_get_context(engine),
					    "com.fwupd-internal.Absolute");
	g_assert_nonnull(attr1);

	current = fwupd_bios_setting_get_current_value(attr1);
	g_assert_nonnull(current);

	g_hash_table_insert(bios_settings, g_strdup("Absolute"), g_strdup("Disabled"));
	ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO);
	g_assert_false(ret);
	g_clear_error(&error);

	g_hash_table_remove_all(bios_settings);
	g_hash_table_insert(bios_settings, g_strdup("Absolute"), g_strdup("Enabled"));
	ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	g_hash_table_remove_all(bios_settings);
	g_hash_table_insert(bios_settings, g_strdup("Absolute"), g_strdup("off"));
	ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	g_hash_table_remove_all(bios_settings);
	g_hash_table_insert(bios_settings, g_strdup("Absolute"), g_strdup("FOO"));
	ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error);
	g_assert_false(ret);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_clear_error(&error);

	/* use BiosSettingId instead */
	g_hash_table_remove_all(bios_settings);
	g_hash_table_insert(bios_settings, g_strdup("com.fwupd-internal.Absolute"), g_strdup("on"));
	ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	g_hash_table_remove_all(bios_settings);
	g_hash_table_insert(bios_settings,
			    g_strdup("com.fwupd-internal.Absolute"),
			    g_strdup("off"));
	ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* string */
	attr2 =
	    fu_context_get_bios_setting(fu_engine_get_context(engine), "com.fwupd-internal.Asset");
	g_assert_nonnull(attr2);

	current = fwupd_bios_setting_get_current_value(attr2);
	g_assert_nonnull(current);

	g_hash_table_remove_all(bios_settings);
	g_hash_table_insert(bios_settings, g_strdup("Asset"), g_strdup("0"));
	ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	g_hash_table_remove_all(bios_settings);
	g_hash_table_insert(bios_settings, g_strdup("Asset"), g_strdup("1"));
	ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	g_hash_table_remove_all(bios_settings);
	g_hash_table_insert(
	    bios_settings,
	    g_strdup("Absolute"),
	    g_strdup("1234567891123456789112345678911234567891123456789112345678911111"));
	ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error);
	g_assert_false(ret);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_clear_error(&error);

	/* integer */
	attr3 = fu_context_get_bios_setting(fu_engine_get_context(engine),
					    "com.fwupd-internal.CustomChargeStop");
	g_assert_nonnull(attr3);

	current = fwupd_bios_setting_get_current_value(attr3);
	g_assert_nonnull(current);

	g_hash_table_remove_all(bios_settings);
	g_hash_table_insert(bios_settings, g_strdup("CustomChargeStop"), g_strdup("75"));
	ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error);
	g_assert_true(ret);

	g_hash_table_remove_all(bios_settings);
	g_hash_table_insert(bios_settings, g_strdup("CustomChargeStop"), g_strdup("110"));
	ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error);
	g_assert_false(ret);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_clear_error(&error);

	g_hash_table_remove_all(bios_settings);
	g_hash_table_insert(bios_settings, g_strdup("CustomChargeStop"), g_strdup("1"));
	ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error);
	g_assert_false(ret);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_clear_error(&error);

	/* force it to read only */
	g_hash_table_remove_all(bios_settings);
	g_hash_table_insert(bios_settings, g_strdup("CustomChargeStop"), g_strdup("70"));
	ret = fu_engine_modify_bios_settings(engine, bios_settings, TRUE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* read only */
	attr4 = fu_context_get_bios_setting(fu_engine_get_context(engine),
					    "com.fwupd-internal.pending_reboot");
	g_assert_nonnull(attr4);

	current = fwupd_bios_setting_get_current_value(attr4);
	g_assert_nonnull(current);

	g_hash_table_remove_all(bios_settings);
	g_hash_table_insert(bios_settings, g_strdup("pending_reboot"), g_strdup("foo"));
	ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error);
	g_assert_false(ret);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_clear_error(&error);

	g_hash_table_remove_all(bios_settings);
	g_hash_table_insert(bios_settings, g_strdup("CustomChargeStop"), g_strdup("80"));
	ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error);
	g_assert_false(ret);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_clear_error(&error);
}

GFileInputStream *
_g_local_file_input_stream_new(int fd);

static void
fu_unix_seekable_input_stream_func(void)
{
#ifdef HAVE_GIO_UNIX
	gssize ret;
	gint fd;
	guint8 buf[6] = {0};
	g_autofree gchar *fn = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(GInputStream) stream = NULL;

	fn = g_test_build_filename(G_TEST_DIST, "tests", "metadata.xml", NULL);
	g_assert_nonnull(fn);
	fd = g_open(fn, O_RDONLY, 0);
	g_assert_cmpint(fd, >=, 0);

	stream = fu_unix_seekable_input_stream_new(fd, TRUE);
	g_assert_nonnull(stream);

	/* first chuck */
	ret = g_input_stream_read(stream, buf, sizeof(buf) - 1, NULL, &error);
	g_assert_no_error(error);
	g_assert_cmpint(ret, ==, 5);
	g_assert_cmpstr((const gchar *)buf, ==, "<?xml");

	/* second chuck */
	ret = g_input_stream_read(stream, buf, sizeof(buf) - 1, NULL, &error);
	g_assert_no_error(error);
	g_assert_cmpint(ret, ==, 5);
	g_assert_cmpstr((const gchar *)buf, ==, " vers");

	/* first chuck, again */
	ret = g_seekable_seek(G_SEEKABLE(stream), 0, G_SEEK_SET, NULL, &error);
	g_assert_no_error(error);
	g_assert_cmpint(ret, ==, 1);
	ret = g_input_stream_read(stream, buf, sizeof(buf) - 1, NULL, &error);
	g_assert_no_error(error);
	g_assert_cmpint(ret, ==, 5);
	g_assert_cmpstr((const gchar *)buf, ==, "<?xml");
#else
	g_test_skip("No gio-unix-2.0 support, skipping");
#endif
}

static void
fu_remote_download_func(void)
{
	gboolean ret;
	g_autofree gchar *fn = NULL;
	g_autofree gchar *directory = NULL;
	g_autofree gchar *expected_metadata = NULL;
	g_autofree gchar *expected_signature = NULL;
	g_autoptr(FwupdRemote) remote = NULL;
	g_autoptr(GError) error = NULL;

	remote = fwupd_remote_new();
	directory = g_build_filename(FWUPD_LOCALSTATEDIR, "lib", "fwupd", "remotes2.d", NULL);
	expected_metadata = g_build_filename(FWUPD_LOCALSTATEDIR,
					     "lib",
					     "fwupd",
					     "remotes2.d",
					     "lvfs-testing",
					     "firmware.xml.gz",
					     NULL);
	expected_signature = g_strdup_printf("%s.jcat", expected_metadata);
	fwupd_remote_set_remotes_dir(remote, directory);
	fn = g_test_build_filename(G_TEST_DIST, "tests", "remotes2.d", "lvfs-testing.conf", NULL);
	ret = fu_remote_load_from_filename(remote, fn, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fwupd_remote_setup(remote, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpint(fwupd_remote_get_kind(remote), ==, FWUPD_REMOTE_KIND_DOWNLOAD);
	g_assert_cmpint(fwupd_remote_get_priority(remote), ==, 0);
	g_assert_false(fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED));
	g_assert_nonnull(fwupd_remote_get_metadata_uri(remote));
	g_assert_nonnull(fwupd_remote_get_metadata_uri_sig(remote));
	g_assert_cmpstr(fwupd_remote_get_title(remote),
			==,
			"Linux Vendor Firmware Service (testing)");
	g_assert_cmpstr(fwupd_remote_get_report_uri(remote),
			==,
			"https://fwupd.org/lvfs/firmware/report");
	g_assert_cmpstr(fwupd_remote_get_filename_cache(remote), ==, expected_metadata);
	g_assert_cmpstr(fwupd_remote_get_filename_cache_sig(remote), ==, expected_signature);
}

/* verify we used the FirmwareBaseURI just for firmware */
static void
fu_remote_baseuri_func(void)
{
	gboolean ret;
	g_autofree gchar *firmware_uri = NULL;
	g_autofree gchar *fn = NULL;
	g_autoptr(FwupdRemote) remote = NULL;
	g_autofree gchar *directory = NULL;
	g_autoptr(GError) error = NULL;

	remote = fwupd_remote_new();
	directory = g_build_filename(FWUPD_LOCALSTATEDIR, "lib", "fwupd", "remotes2.d", NULL);
	fwupd_remote_set_remotes_dir(remote, directory);
	fn = g_test_build_filename(G_TEST_DIST, "tests", "firmware-base-uri.conf", NULL);
	ret = fu_remote_load_from_filename(remote, fn, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpint(fwupd_remote_get_kind(remote), ==, FWUPD_REMOTE_KIND_DOWNLOAD);
	g_assert_cmpint(fwupd_remote_get_priority(remote), ==, 0);
	g_assert_true(fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED));
	g_assert_cmpstr(fwupd_remote_get_firmware_base_uri(remote), ==, "https://my.fancy.cdn/");
	g_assert_cmpstr(fwupd_remote_get_agreement(remote), ==, NULL);
	g_assert_cmpstr(fwupd_remote_get_checksum(remote), ==, NULL);
	g_assert_cmpstr(fwupd_remote_get_metadata_uri(remote),
			==,
			"https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz");
	g_assert_cmpstr(fwupd_remote_get_metadata_uri_sig(remote),
			==,
			"https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz.jcat");
	firmware_uri =
	    fwupd_remote_build_firmware_uri(remote, "http://bbc.co.uk/firmware.cab", &error);
	g_assert_no_error(error);
	g_assert_cmpstr(firmware_uri, ==, "https://my.fancy.cdn/firmware.cab");
}

static void
fu_remote_auth_func(void)
{
	gboolean ret;
	gchar **order;
	g_autofree gchar *fn = NULL;
	g_autofree gchar *remotes_dir = NULL;
	g_autofree gchar *json = NULL;
	g_autoptr(FwupdRemote) remote = fwupd_remote_new();
	g_autoptr(FwupdRemote) remote2 = fwupd_remote_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(GVariant) data = NULL;

	remotes_dir = g_test_build_filename(G_TEST_BUILT, "tests", NULL);
	fwupd_remote_set_remotes_dir(remote, remotes_dir);

	fn = g_test_build_filename(G_TEST_DIST, "tests", "auth.conf", NULL);
	ret = fu_remote_load_from_filename(remote, fn, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpstr(fwupd_remote_get_username(remote), ==, "user");
	g_assert_cmpstr(fwupd_remote_get_password(remote), ==, "pass");
	g_assert_cmpstr(fwupd_remote_get_report_uri(remote),
			==,
			"https://fwupd.org/lvfs/firmware/report");
	g_assert_false(fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED));
	g_assert_false(fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS));
	g_assert_true(fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS));

	g_assert_true(
	    g_str_has_suffix(fwupd_remote_get_filename_source(remote), "tests/auth.conf"));
	g_assert_true(g_str_has_suffix(fwupd_remote_get_remotes_dir(remote), "/src/tests"));
	g_assert_cmpint(fwupd_remote_get_age(remote), >, 1000000);

	ret = fwupd_remote_setup(remote, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	order = fwupd_remote_get_order_before(remote);
	g_assert_nonnull(order);
	g_assert_cmpint(g_strv_length(order), ==, 1);
	g_assert_cmpstr(order[0], ==, "before");
	order = fwupd_remote_get_order_after(remote);
	g_assert_nonnull(order);
	g_assert_cmpint(g_strv_length(order), ==, 1);
	g_assert_cmpstr(order[0], ==, "after");

	/* to/from GVariant */
	fwupd_remote_set_priority(remote, 999);
	data = fwupd_codec_to_variant(FWUPD_CODEC(remote), FWUPD_CODEC_FLAG_NONE);
	ret = fwupd_codec_from_variant(FWUPD_CODEC(remote2), data, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpstr(fwupd_remote_get_username(remote2), ==, "user");
	g_assert_cmpint(fwupd_remote_get_priority(remote2), ==, 999);

	/* jcat-tool is not a hard dep, and the tests create an empty file if unfound */
	ret = fwupd_remote_load_signature(remote,
					  fwupd_remote_get_filename_cache_sig(remote),
					  &error);
	if (!ret) {
		if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_READ)) {
			g_test_skip("no jcat-tool, so skipping test");
			return;
		}
	}
	g_assert_no_error(error);
	g_assert_true(ret);

	/* to JSON */
	fwupd_remote_set_filename_source(remote2, NULL);
	fwupd_remote_set_checksum_sig(
	    remote2,
	    "dd1b4fd2a59bb0e4d9ea760c658ac3cf9336c7b6729357bab443485b5cf071b2");
	fwupd_remote_set_filename_cache(remote2, "./libfwupd/tests/auth/firmware.xml.gz");
	json = fwupd_codec_to_json_string(FWUPD_CODEC(remote2), FWUPD_CODEC_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_nonnull(json);
	ret =
	    g_strcmp0(
		json,
		"{\n"
		"  \"Id\" : \"auth\",\n"
		"  \"Kind\" : \"download\",\n"
		"  \"ReportUri\" : \"https://fwupd.org/lvfs/firmware/report\",\n"
		"  \"MetadataUri\" : \"https://cdn.fwupd.org/downloads/firmware.xml.gz\",\n"
		"  \"MetadataUriSig\" : \"https://cdn.fwupd.org/downloads/firmware.xml.gz.jcat\",\n"
		"  \"FirmwareBaseUri\" : \"https://my.fancy.cdn/\",\n"
		"  \"Username\" : \"user\",\n"
		"  \"Password\" : \"pass\",\n"
		"  \"ChecksumSig\" : "
		"\"dd1b4fd2a59bb0e4d9ea760c658ac3cf9336c7b6729357bab443485b5cf071b2\",\n"
		"  \"FilenameCache\" : \"./libfwupd/tests/auth/firmware.xml.gz\",\n"
		"  \"FilenameCacheSig\" : \"./libfwupd/tests/auth/firmware.xml.gz.jcat\",\n"
		"  \"Flags\" : 9,\n"
		"  \"Enabled\" : true,\n"
		"  \"ApprovalRequired\" : false,\n"
		"  \"AutomaticReports\" : false,\n"
		"  \"AutomaticSecurityReports\" : true,\n"
		"  \"Priority\" : 999,\n"
		"  \"Mtime\" : 0,\n"
		"  \"RefreshInterval\" : 86400\n"
		"}") == 0;
	g_assert_true(ret);
}

static void
fu_remote_duplicate_func(void)
{
	gboolean ret;
	g_autofree gchar *fn2 = NULL;
	g_autofree gchar *fn = NULL;
	g_autoptr(FwupdRemote) remote = fwupd_remote_new();
	g_autoptr(GError) error = NULL;

	fn = g_test_build_filename(G_TEST_DIST, "tests", "stable.conf", NULL);
	ret = fu_remote_load_from_filename(remote, fn, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	fn2 = g_test_build_filename(G_TEST_DIST, "tests", "disabled.conf", NULL);
	ret = fu_remote_load_from_filename(remote, fn2, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fwupd_remote_setup(remote, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fwupd_remote_setup(remote, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_false(fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED));
	g_assert_cmpstr(fwupd_remote_get_username(remote), ==, NULL);
	g_assert_cmpstr(fwupd_remote_get_password(remote), ==, "");
	g_assert_cmpstr(fwupd_remote_get_filename_cache(remote),
			==,
			"/tmp/fwupd-self-test/stable.xml");
}

/* verify we used the metadata path for firmware */
static void
fu_remote_nopath_func(void)
{
	gboolean ret;
	g_autofree gchar *firmware_uri = NULL;
	g_autofree gchar *fn = NULL;
	g_autoptr(FwupdRemote) remote = NULL;
	g_autoptr(GError) error = NULL;
	g_autofree gchar *directory = NULL;

	remote = fwupd_remote_new();
	directory = g_build_filename(FWUPD_LOCALSTATEDIR, "lib", "fwupd", "remotes2.d", NULL);
	fwupd_remote_set_remotes_dir(remote, directory);
	fn = g_test_build_filename(G_TEST_DIST, "tests", "firmware-nopath.conf", NULL);
	ret = fu_remote_load_from_filename(remote, fn, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpint(fwupd_remote_get_kind(remote), ==, FWUPD_REMOTE_KIND_DOWNLOAD);
	g_assert_cmpint(fwupd_remote_get_priority(remote), ==, 0);
	g_assert_true(fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED));
	g_assert_cmpstr(fwupd_remote_get_checksum(remote), ==, NULL);
	g_assert_cmpstr(fwupd_remote_get_metadata_uri(remote),
			==,
			"https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz");
	g_assert_cmpstr(fwupd_remote_get_metadata_uri_sig(remote),
			==,
			"https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz.jcat");
	firmware_uri = fwupd_remote_build_firmware_uri(remote, "firmware.cab", &error);
	g_assert_no_error(error);
	g_assert_cmpstr(firmware_uri,
			==,
			"https://s3.amazonaws.com/lvfsbucket/downloads/firmware.cab");
}

static void
fu_remote_local_func(void)
{
	gboolean ret;
	g_autofree gchar *fn = NULL;
	g_autofree gchar *json = NULL;
	g_autoptr(FwupdRemote) remote = NULL;
	g_autoptr(FwupdRemote) remote2 = fwupd_remote_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(GVariant) data = NULL;

	remote = fwupd_remote_new();
	fn = g_test_build_filename(G_TEST_DIST, "tests", "dell-esrt.conf", NULL);
	ret = fu_remote_load_from_filename(remote, fn, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpint(fwupd_remote_get_kind(remote), ==, FWUPD_REMOTE_KIND_LOCAL);
	g_assert_true(fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED));
	g_assert_null(fwupd_remote_get_metadata_uri(remote));
	g_assert_null(fwupd_remote_get_metadata_uri_sig(remote));
	g_assert_null(fwupd_remote_get_report_uri(remote));
	g_assert_cmpstr(fwupd_remote_get_title(remote),
			==,
			"Enable UEFI capsule updates on Dell systems");
	g_assert_cmpstr(fwupd_remote_get_filename_cache(remote),
			==,
			"@datadir@/fwupd/remotes.d/dell-esrt/firmware.xml");
	g_assert_cmpstr(fwupd_remote_get_filename_cache_sig(remote), ==, NULL);
	g_assert_cmpstr(fwupd_remote_get_checksum(remote), ==, NULL);

	/* to/from GVariant */
	data = fwupd_codec_to_variant(FWUPD_CODEC(remote), FWUPD_CODEC_FLAG_NONE);
	ret = fwupd_codec_from_variant(FWUPD_CODEC(remote2), data, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_null(fwupd_remote_get_metadata_uri(remote));

	/* to JSON */
	fwupd_remote_set_filename_source(remote2, NULL);
	json = fwupd_codec_to_json_string(FWUPD_CODEC(remote2), FWUPD_CODEC_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_nonnull(json);
	ret = g_strcmp0(
		  json,
		  "{\n"
		  "  \"Id\" : \"dell-esrt\",\n"
		  "  \"Kind\" : \"local\",\n"
		  "  \"Title\" : \"Enable UEFI capsule updates on Dell systems\",\n"
		  "  \"FilenameCache\" : \"@datadir@/fwupd/remotes.d/dell-esrt/firmware.xml\",\n"
		  "  \"Flags\" : 1,\n"
		  "  \"Enabled\" : true,\n"
		  "  \"ApprovalRequired\" : false,\n"
		  "  \"AutomaticReports\" : false,\n"
		  "  \"AutomaticSecurityReports\" : false,\n"
		  "  \"Priority\" : 0,\n"
		  "  \"Mtime\" : 0,\n"
		  "  \"RefreshInterval\" : 0\n"
		  "}") == 0;
	g_assert_true(ret);
}

static void
fu_remote_list_repair_func(void)
{
	gboolean ret;
	g_autoptr(FuRemoteList) remote_list = fu_remote_list_new();
	g_autoptr(FwupdRemote) remote1 = NULL;
	g_autoptr(FwupdRemote) remote2 = NULL;
	g_autoptr(FwupdRemote) remote3 = NULL;
	g_autoptr(GError) error = NULL;

	fu_remote_list_set_lvfs_metadata_format(remote_list, "zst");
	ret = fu_remote_list_load(remote_list, FU_REMOTE_LIST_LOAD_FLAG_FIX_METADATA_URI, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* check .gz converted to .zst */
	remote1 = fu_remote_list_get_by_id(remote_list, "legacy-lvfs", &error);
	g_assert_no_error(error);
	g_assert_nonnull(remote1);
	g_assert_cmpstr(fwupd_remote_get_metadata_uri(remote1),
			==,
			"http://localhost/stable.xml.zst");

	/* check .xz converted to .zst */
	remote2 = fu_remote_list_get_by_id(remote_list, "legacy-lvfs-xz", &error);
	g_assert_no_error(error);
	g_assert_nonnull(remote2);
	g_assert_cmpstr(fwupd_remote_get_metadata_uri(remote2),
			==,
			"http://localhost/stable.xml.zst");

	/* check non-LVFS remote NOT .gz converted to .xz */
	remote3 = fu_remote_list_get_by_id(remote_list, "legacy", &error);
	g_assert_no_error(error);
	g_assert_nonnull(remote3);
	g_assert_cmpstr(fwupd_remote_get_metadata_uri(remote3),
			==,
			"http://localhost/stable.xml.gz");
}

static void
fu_config_migrate_1_9_func(void)
{
	const gchar *fake_localconf_fn = "/tmp/fwupd-self-test/var/etc/fwupd/fwupd.conf";
	const gchar *fake_sysconf_fn = "/tmp/fwupd-self-test/fwupd/fwupd.conf";
	gboolean ret;
	g_autoptr(FuConfig) config = FU_CONFIG(fu_engine_config_new());
	g_autoptr(GError) error = NULL;

	/* ensure empty tree */
	fu_self_test_mkroot();

	g_unsetenv("CONFIGURATION_DIRECTORY");
	(void)g_setenv("FWUPD_SYSCONFDIR", "/tmp/fwupd-self-test", TRUE);

	ret = fu_path_mkdir_parent(fake_sysconf_fn, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = g_file_set_contents(fake_sysconf_fn,
				  "# use `man 5 fwupd.conf` for documentation\n"
				  "[fwupd]\n"
				  "DisabledPlugins=test;test_ble\n"
				  "OnlyTrusted=true\n"
				  "AllowEmulation=false\n",
				  -1,
				  &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	ret = fu_config_load(config, FU_CONFIG_LOAD_FLAG_NONE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* ensure that all keys migrated */
	ret = g_file_test(fake_localconf_fn, G_FILE_TEST_EXISTS);
	g_assert_false(ret);
}

static void
fu_config_set_plugin_defaults(FuConfig *config)
{
	/* these are correct for v2.0.0 */
	fu_config_set_default(config, "msr", "MinimumSmeKernelVersion", "5.18.0");
	fu_config_set_default(config, "redfish", "CACheck", "false");
	fu_config_set_default(config, "redfish", "IpmiDisableCreateUser", "false");
	fu_config_set_default(config, "redfish", "ManagerResetTimeout", "1800"); /* seconds */
	fu_config_set_default(config, "redfish", "Password", NULL);
	fu_config_set_default(config, "redfish", "Uri", NULL);
	fu_config_set_default(config, "redfish", "Username", NULL);
	fu_config_set_default(config, "redfish", "UserUri", NULL);
	fu_config_set_default(config, "thunderbolt", "DelayedActivation", "false");
	fu_config_set_default(config, "thunderbolt", "MinimumKernelVersion", "4.13.0");
	fu_config_set_default(config, "uefi-capsule", "DisableCapsuleUpdateOnDisk", "false");
	fu_config_set_default(config, "uefi-capsule", "DisableShimForSecureBoot", "false");
	fu_config_set_default(config, "uefi-capsule", "EnableEfiDebugging", "false");
	fu_config_set_default(config, "uefi-capsule", "EnableGrubChainLoad", "false");
	fu_config_set_default(config, "uefi-capsule", "OverrideESPMountPoint", NULL);
	fu_config_set_default(config, "uefi-capsule", "RebootCleanup", "true");
	fu_config_set_default(config, "uefi-capsule", "RequireESPFreeSpace", "0");
	fu_config_set_default(config, "uefi-capsule", "ScreenWidth", "0");
	fu_config_set_default(config, "uefi-capsule", "ScreenHeight", "0");
}

static void
fu_config_migrate_1_7_func(void)
{
	const gchar *sysconfdir = "/tmp/fwupd-self-test/conf-migration-1.7/var/etc";
	gboolean ret;
	const gchar *fn_merge[] = {"daemon.conf",
				   "msr.conf",
				   "redfish.conf",
				   "thunderbolt.conf",
				   "uefi_capsule.conf",
				   NULL};
	g_autofree gchar *localconf_data = NULL;
	g_autofree gchar *fn_mut = NULL;
	g_autofree gchar *testdatadir = NULL;
	g_autoptr(FuConfig) config = FU_CONFIG(fu_engine_config_new());
	g_autoptr(GError) error = NULL;

	/* ensure empty tree */
	fu_self_test_mkroot();

	/* source directory and data */
	testdatadir = g_test_build_filename(G_TEST_DIST, "tests", "conf-migration-1.7", NULL);
	if (!g_file_test(testdatadir, G_FILE_TEST_EXISTS)) {
		g_test_skip("missing fwupd 1.7.x migration test data");
		return;
	}

	/* working directory */
	(void)g_setenv("FWUPD_SYSCONFDIR", sysconfdir, TRUE);
	g_unsetenv("CONFIGURATION_DIRECTORY");

	fn_mut = g_build_filename(sysconfdir, "fwupd", "fwupd.conf", NULL);
	g_assert_nonnull(fn_mut);
	ret = fu_path_mkdir_parent(fn_mut, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* copy all files to working directory */
	for (guint i = 0; fn_merge[i] != NULL; i++) {
		g_autofree gchar *source =
		    g_build_filename(testdatadir, "fwupd", fn_merge[i], NULL);
		g_autofree gchar *target = g_build_filename(sysconfdir, "fwupd", fn_merge[i], NULL);
		fu_test_copy_file(source, target);
	}

	/* we don't want to run all the plugins just to get the _init() defaults */
	fu_config_set_plugin_defaults(config);
	ret = fu_config_load(config, FU_CONFIG_LOAD_FLAG_MIGRATE_FILES, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* make sure all migrated files were renamed */
	for (guint i = 0; fn_merge[i] != NULL; i++) {
		g_autofree gchar *old = g_build_filename(sysconfdir, "fwupd", fn_merge[i], NULL);
		g_autofree gchar *new = g_strdup_printf("%s.old", old);
		ret = g_file_test(old, G_FILE_TEST_EXISTS);
		g_assert_false(ret);
		ret = g_file_test(new, G_FILE_TEST_EXISTS);
		g_assert_true(ret);
	}

	/* ensure all default keys migrated */
	ret = g_file_get_contents(fn_mut, &localconf_data, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpstr(localconf_data, ==, "");
}

static void
fu_engine_integrity_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	g_autofree gchar *str = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(GHashTable) integrity = NULL;

	integrity = fu_engine_integrity_new(self->ctx, &error);
	g_assert_no_error(error);
	g_assert_nonnull(integrity);
	str = fu_engine_integrity_to_string(integrity);
	g_debug("%s", str);
}

static void
fu_engine_report_metadata_func(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(GError) error = NULL;
	g_autoptr(GHashTable) metadata = NULL;
	g_autoptr(GList) keys = NULL;
	const gchar *keys_exist[] = {
	    "BatteryThreshold",
	    "CompileVersion(org.freedesktop.fwupd)",
	    "CpuArchitecture",
	    "DistroId",
	    "FwupdSupported",
	    "RuntimeVersion(org.freedesktop.fwupd)",
	    "SELinux",
	};

	/* check report metadata */
	metadata = fu_engine_get_report_metadata(engine, &error);
	g_assert_no_error(error);
	g_assert_nonnull(metadata);

	keys = g_list_sort(g_hash_table_get_keys(metadata), (GCompareFunc)g_strcmp0);
	for (GList *l = keys; l != NULL; l = l->next) {
		const gchar *key = l->data;
		const gchar *value = g_hash_table_lookup(metadata, key);
		g_debug("%s=%s", key, value);
	}
	for (guint i = 0; i < G_N_ELEMENTS(keys_exist); i++) {
		const gchar *value = g_hash_table_lookup(metadata, keys_exist[i]);
		if (value == NULL)
			g_warning("no %s in metadata", keys_exist[i]);
	}
}

static void
fu_engine_error_array_func(void)
{
	g_autoptr(GPtrArray) arr1 = g_ptr_array_new_with_free_func((GDestroyNotify)g_error_free);
	g_autoptr(GPtrArray) arr2 = g_ptr_array_new_with_free_func((GDestroyNotify)g_error_free);
	g_autoptr(GPtrArray) arr3 = g_ptr_array_new_with_free_func((GDestroyNotify)g_error_free);
	g_autoptr(GPtrArray) arr4 = g_ptr_array_new_with_free_func((GDestroyNotify)g_error_free);
	g_autoptr(GError) error1 = NULL;
	g_autoptr(GError) error2 = NULL;
	g_autoptr(GError) error3 = NULL;
	g_autoptr(GError) error4 = NULL;

	/* fallback */
	error1 = fu_engine_error_array_get_best(arr1);
	g_assert_error(error1, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND);

	/* get the most important single error */
	g_ptr_array_add(arr2, g_error_new_literal(FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, ""));
	error2 = fu_engine_error_array_get_best(arr2);
	g_assert_error(error2, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND);

	/* version same */
	g_ptr_array_add(arr3, g_error_new_literal(FWUPD_ERROR, FWUPD_ERROR_VERSION_SAME, ""));
	g_ptr_array_add(arr3, g_error_new_literal(FWUPD_ERROR, FWUPD_ERROR_VERSION_SAME, ""));
	g_ptr_array_add(arr3, g_error_new_literal(FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, ""));
	error3 = fu_engine_error_array_get_best(arr3);
	g_assert_error(error3, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO);

	/* already have newer versions */
	g_ptr_array_add(arr4, g_error_new_literal(FWUPD_ERROR, FWUPD_ERROR_VERSION_NEWER, ""));
	g_ptr_array_add(arr4, g_error_new_literal(FWUPD_ERROR, FWUPD_ERROR_VERSION_NEWER, ""));
	error4 = fu_engine_error_array_get_best(arr4);
	g_assert_error(error4, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO);
}

static void
fu_engine_machine_hash_func(void)
{
	gsize sz = 0;
	g_autofree gchar *buf = NULL;
	g_autofree gchar *mhash1 = NULL;
	g_autofree gchar *mhash2 = NULL;
	g_autoptr(GError) error = NULL;

	if (!g_file_test("/etc/machine-id", G_FILE_TEST_EXISTS)) {
		g_test_skip("Missing /etc/machine-id");
		return;
	}
	if (!g_file_get_contents("/etc/machine-id", &buf, &sz, &error)) {
		g_test_skip("/etc/machine-id is unreadable");
		return;
	}

	if (sz == 0) {
		g_test_skip("Empty /etc/machine-id");
		return;
	}

	mhash1 = fu_engine_build_machine_id("salt1", &error);
	g_assert_no_error(error);
	g_assert_cmpstr(mhash1, !=, NULL);
	mhash2 = fu_engine_build_machine_id("salt2", &error);
	g_assert_no_error(error);
	g_assert_cmpstr(mhash2, !=, NULL);
	g_assert_cmpstr(mhash2, !=, mhash1);
}

static void
fu_test_engine_fake_hidraw(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autofree gchar *value2 = NULL;
	g_autoptr(FuDevice) device = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(FuUdevDevice) udev_device2 = NULL;
	g_autoptr(FuUdevDevice) udev_device3 = NULL;
	g_autoptr(GError) error = NULL;

	/* non-linux */
	if (!fu_context_has_backend(self->ctx, "udev")) {
		g_test_skip("no Udev backend");
		return;
	}

	/* load engine and check the device was found */
	fu_engine_add_plugin_filter(engine, "pixart_rf");
	ret = fu_engine_load(engine,
			     FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS |
				 FU_ENGINE_LOAD_FLAG_READONLY,
			     progress,
			     &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* hidraw -> pixart_rf */
	device = fu_engine_get_device(engine, "6acd27f1feb25ba3b604063de4c13b604776b2f5", &error);
	g_assert_no_error(error);
	g_assert_nonnull(device);
	g_assert_cmpstr(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), ==, "hidraw");
	g_assert_cmpstr(fu_udev_device_get_devtype(FU_UDEV_DEVICE(device)), ==, NULL);
	g_assert_cmpstr(fu_udev_device_get_driver(FU_UDEV_DEVICE(device)), ==, NULL);
	g_assert_cmpint(fu_device_get_vid(device), ==, 0x093a);
	g_assert_cmpint(fu_device_get_pid(device), ==, 0x2862);
	g_assert_cmpstr(fu_device_get_plugin(device), ==, "pixart_rf");
	g_assert_cmpstr(fu_device_get_name(device), ==, "PIXART Pixart dual-mode mouse");
	g_assert_cmpstr(fu_device_get_physical_id(device), ==, "usb-0000:00:14.0-1/input1");
	g_assert_cmpstr(fu_device_get_logical_id(device), ==, NULL);

	/* check can read random files */
	value2 = fu_udev_device_read_sysfs(FU_UDEV_DEVICE(device),
					   "dev",
					   FU_UDEV_DEVICE_ATTR_READ_TIMEOUT_DEFAULT,
					   &error);
	g_assert_no_error(error);
	g_assert_cmpstr(value2, ==, "241:1");

	/* get child, both specified */
	udev_device2 = FU_UDEV_DEVICE(
	    fu_device_get_backend_parent_with_subsystem(device, "usb:usb_interface", &error));
	g_assert_no_error(error);
	g_assert_nonnull(udev_device2);
	g_assert_cmpstr(fu_udev_device_get_subsystem(udev_device2), ==, "usb");

	/* get child, initially unprobed */
	udev_device3 =
	    FU_UDEV_DEVICE(fu_device_get_backend_parent_with_subsystem(device, "usb", &error));
	g_assert_no_error(error);
	g_assert_nonnull(udev_device3);
	g_assert_cmpstr(fu_udev_device_get_subsystem(udev_device3), ==, "usb");
	g_assert_cmpstr(fu_udev_device_get_driver(udev_device3), ==, "usb");
}

static void
fu_test_engine_fake_usb(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;

	/* non-linux */
	if (!fu_context_has_backend(self->ctx, "udev")) {
		g_test_skip("no Udev backend");
		return;
	}

	/* load engine and check the device was found */
	fu_engine_add_plugin_filter(engine, "hughski_colorhug");
	ret = fu_engine_load(engine,
			     FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS |
				 FU_ENGINE_LOAD_FLAG_READONLY,
			     progress,
			     &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* USB -> colorhug */
	device = fu_engine_get_device(engine, "d787669ee4a103fe0b361fe31c10ea037c72f27c", &error);
	g_assert_no_error(error);
	g_assert_nonnull(device);
	g_assert_cmpstr(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), ==, "usb");
	g_assert_cmpstr(fu_udev_device_get_devtype(FU_UDEV_DEVICE(device)), ==, "usb_device");
	g_assert_cmpstr(fu_udev_device_get_driver(FU_UDEV_DEVICE(device)), ==, "usb");
	g_assert_cmpint(fu_device_get_vid(device), ==, 0x093a);
	g_assert_cmpint(fu_device_get_pid(device), ==, 0x2862);
	g_assert_cmpstr(fu_device_get_plugin(device), ==, "hughski_colorhug");
	g_assert_cmpstr(fu_device_get_physical_id(device), ==, "1-1");
	g_assert_cmpstr(fu_device_get_logical_id(device), ==, NULL);
}

static void
fu_test_engine_fake_v4l(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;

	/* non-linux */
	if (!fu_context_has_backend(self->ctx, "udev")) {
		g_test_skip("no Udev backend");
		return;
	}

	/* load engine and check the device was found */
	fu_engine_add_plugin_filter(engine, "logitech_tap");
	ret = fu_engine_load(engine,
			     FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS |
				 FU_ENGINE_LOAD_FLAG_READONLY,
			     progress,
			     &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* no -Dplugin_logitech_tap=enabled */
	if (fu_engine_get_plugin_by_name(engine, "logitech_tap", &error) == NULL) {
		g_test_skip(error->message);
		return;
	}

	/* v4l -> logitech_tap */
	device = fu_engine_get_device(engine, "d787669ee4a103fe0b361fe31c10ea037c72f27c", &error);
	g_assert_no_error(error);
	g_assert_nonnull(device);
	g_assert_cmpstr(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), ==, "video4linux");
	g_assert_cmpstr(fu_udev_device_get_devtype(FU_UDEV_DEVICE(device)), ==, NULL);
	g_assert_cmpstr(fu_udev_device_get_driver(FU_UDEV_DEVICE(device)), ==, NULL);
	g_assert_cmpint(fu_device_get_vid(device), ==, 0x093A);
	g_assert_cmpint(fu_device_get_pid(device), ==, 0x2862);
	g_assert_cmpint(fu_v4l_device_get_index(FU_V4L_DEVICE(device)), ==, 0);
	g_assert_cmpint(fu_v4l_device_get_caps(FU_V4L_DEVICE(device)), ==, FU_V4L_CAP_NONE);
	g_assert_cmpstr(fu_device_get_name(device), ==, "Integrated Camera: Integrated C");
	g_assert_cmpstr(fu_device_get_plugin(device), ==, "logitech_tap");
}

static void
fu_test_engine_fake_nvme(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;

	/* non-linux */
	if (!fu_context_has_backend(self->ctx, "udev")) {
		g_test_skip("no Udev backend");
		return;
	}

	/* load engine and check the device was found */
	fu_engine_add_plugin_filter(engine, "nvme");
	ret = fu_engine_load(engine,
			     FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS |
				 FU_ENGINE_LOAD_FLAG_READONLY | FU_ENGINE_LOAD_FLAG_NO_CACHE,
			     progress,
			     &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* no linux/nvme_ioctl.h */
	if (fu_engine_get_plugin_by_name(engine, "nvme", &error) == NULL) {
		g_test_skip(error->message);
		return;
	}

	/* NVMe -> nvme */
	device = fu_engine_get_device(engine, "4c263c95f596030b430d65dc934f6722bcee5720", &error);
	g_assert_no_error(error);
	g_assert_nonnull(device);
	g_assert_cmpstr(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), ==, "nvme");
	g_assert_cmpstr(fu_udev_device_get_devtype(FU_UDEV_DEVICE(device)), ==, NULL);
	g_assert_cmpint(fu_udev_device_get_number(FU_UDEV_DEVICE(device)), ==, 1);
	g_assert_cmpstr(fu_udev_device_get_driver(FU_UDEV_DEVICE(device)), ==, NULL);
	g_assert_cmpstr(fu_udev_device_get_device_file(FU_UDEV_DEVICE(device)), ==, "/dev/nvme1");
	g_assert_cmpint(fu_device_get_vid(device), ==, 0x1179);
	g_assert_cmpint(fu_device_get_pid(device), ==, 0x010F);
	g_assert_true(fu_device_has_vendor_id(device, "PCI:0x1179"));
	g_assert_cmpstr(fu_device_get_vendor(device), ==, NULL);
	g_assert_cmpstr(fu_device_get_plugin(device), ==, "nvme");
	g_assert_cmpstr(fu_device_get_physical_id(device), ==, "PCI_SLOT_NAME=0000:00:1b.0");
	g_assert_cmpstr(fu_device_get_logical_id(device), ==, NULL);
}

static void
fu_test_engine_fake_serio(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;

	/* non-linux */
	if (!fu_context_has_backend(self->ctx, "udev")) {
		g_test_skip("no Udev backend");
		return;
	}

	/* load engine and check the device was found */
	fu_engine_add_plugin_filter(engine, "synaptics_rmi");
	ret = fu_engine_load(engine,
			     FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS |
				 FU_ENGINE_LOAD_FLAG_READONLY,
			     progress,
			     &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* no gnutls */
	if (fu_engine_get_plugin_by_name(engine, "synaptics_rmi", &error) == NULL) {
		g_test_skip(error->message);
		return;
	}

	/* serio */
	device = fu_engine_get_device(engine, "d8419b7614e50c6fb6162b5dca34df5236a62a8d", &error);
	g_assert_no_error(error);
	g_assert_nonnull(device);
	g_assert_cmpstr(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), ==, "serio");
	g_assert_cmpstr(fu_udev_device_get_devtype(FU_UDEV_DEVICE(device)), ==, NULL);
	g_assert_cmpstr(fu_udev_device_get_driver(FU_UDEV_DEVICE(device)), ==, "psmouse");
	g_assert_cmpstr(fu_udev_device_get_device_file(FU_UDEV_DEVICE(device)), ==, NULL);
	g_assert_cmpint(fu_device_get_vid(device), ==, 0x0);
	g_assert_cmpint(fu_device_get_pid(device), ==, 0x0);
	g_assert_cmpstr(fu_device_get_name(device), ==, "TouchStyk");
	g_assert_cmpstr(fu_device_get_plugin(device), ==, "synaptics_rmi");
	g_assert_cmpstr(fu_device_get_physical_id(device),
			==,
			"DEVPATH=/devices/platform/i8042/serio1");
	g_assert_cmpstr(fu_device_get_logical_id(device), ==, NULL);
	g_assert_true(fu_device_has_instance_id(device,
						"SERIO\\FWID_LEN0305-PNP0F13",
						FU_DEVICE_INSTANCE_FLAG_VISIBLE));
}

static void
fu_test_engine_fake_tpm(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;

	/* non-linux */
	if (!fu_context_has_backend(self->ctx, "udev")) {
		g_test_skip("no Udev backend");
		return;
	}

	if (g_getenv("TPM2TOOLS_TCTI") != NULL) {
		g_test_skip("Using software TPM, skipping fake TPM test");
		return;
	}

	/* load engine and check the device was found */
	fu_engine_add_plugin_filter(engine, "tpm");
	ret = fu_engine_load(engine,
			     FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS |
				 FU_ENGINE_LOAD_FLAG_READONLY,
			     progress,
			     &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* no tss2-esys */
	if (fu_engine_get_plugin_by_name(engine, "tpm", &error) == NULL) {
		g_test_skip(error->message);
		return;
	}

	/* tpm */
	device = fu_engine_get_device(engine, "1d8d50a4dbc65618f5c399c2ae827b632b3ccc11", &error);
	g_assert_no_error(error);
	g_assert_nonnull(device);
	g_assert_cmpstr(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), ==, "tpm");
	g_assert_cmpstr(fu_udev_device_get_devtype(FU_UDEV_DEVICE(device)), ==, NULL);
	g_assert_cmpstr(fu_udev_device_get_driver(FU_UDEV_DEVICE(device)), ==, NULL);
	g_assert_cmpstr(fu_udev_device_get_device_file(FU_UDEV_DEVICE(device)), ==, "/dev/tpm0");
	g_assert_cmpint(fu_device_get_vid(device), ==, 0x0);
	g_assert_cmpint(fu_device_get_pid(device), ==, 0x0);
	g_assert_cmpstr(fu_device_get_plugin(device), ==, "tpm");
	g_assert_cmpstr(fu_device_get_physical_id(device), ==, "DEVNAME=tpm0");
	g_assert_cmpstr(fu_device_get_logical_id(device), ==, NULL);
}

static void
fu_test_engine_fake_block(gconstpointer user_data)
{
	FuTest *self = (FuTest *)user_data;
	gboolean ret;
	g_autoptr(FuDevice) device = NULL;
	g_autoptr(FuEngine) engine = fu_engine_new(self->ctx);
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(GError) error = NULL;

	/* non-linux */
	if (!fu_context_has_backend(self->ctx, "udev")) {
		g_test_skip("no Udev backend");
		return;
	}

	/* load engine and check the device was found */
	fu_engine_add_plugin_filter(engine, "scsi");
	ret = fu_engine_load(engine,
			     FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS |
				 FU_ENGINE_LOAD_FLAG_READONLY,
			     progress,
			     &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* no Udev */
	if (fu_engine_get_plugin_by_name(engine, "scsi", &error) == NULL) {
		g_test_skip(error->message);
		return;
	}

	/* block */
	device = fu_engine_get_device(engine, "7772d9fe9419e3ea564216e12913a16e233378a6", &error);
	g_assert_no_error(error);
	g_assert_nonnull(device);
	g_assert_cmpstr(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), ==, "block");
	g_assert_cmpstr(fu_udev_device_get_devtype(FU_UDEV_DEVICE(device)), ==, "disk");
	g_assert_cmpstr(fu_udev_device_get_driver(FU_UDEV_DEVICE(device)), ==, NULL);
	g_assert_cmpstr(fu_udev_device_get_device_file(FU_UDEV_DEVICE(device)), ==, "/dev/sde");
	g_assert_cmpstr(fu_device_get_plugin(device), ==, "scsi");
	g_assert_cmpstr(fu_device_get_vendor(device), ==, "IBM-ESXS");
}

int
main(int argc, char **argv)
{
	gboolean ret;
	g_autofree gchar *sysfsdir = NULL;
	g_autofree gchar *testdatadir = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
	g_autoptr(FuTest) self = g_new0(FuTest, 1);

	(void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE);
	g_test_init(&argc, &argv, NULL);

	/* only critical and error are fatal */
	g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
	(void)g_setenv("G_MESSAGES_DEBUG", "all", TRUE);
	testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL);
	(void)g_setenv("FWUPD_DATADIR", testdatadir, TRUE);
	(void)g_setenv("FWUPD_LIBDIR_PKG", testdatadir, TRUE);
	(void)g_setenv("FWUPD_SYSCONFDIR", testdatadir, TRUE);
	(void)g_setenv("FWUPD_SYSFSFWDIR", testdatadir, TRUE);
	(void)g_setenv("CONFIGURATION_DIRECTORY", testdatadir, TRUE);
	(void)g_setenv("FWUPD_LOCALSTATEDIR", "/tmp/fwupd-self-test/var", TRUE);
	(void)g_setenv("FWUPD_RUNDIR", "/tmp/fwupd-self-test/run", TRUE);
	(void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", testdatadir, TRUE);
	(void)g_setenv("FWUPD_PROCFS", testdatadir, TRUE);
	sysfsdir = g_test_build_filename(G_TEST_DIST, "tests", "sys", NULL);
	(void)g_setenv("FWUPD_SYSFSDIR", sysfsdir, TRUE);
	(void)g_setenv("FWUPD_SELF_TEST", "1", TRUE);
	(void)g_setenv("FWUPD_MACHINE_ID", "test", TRUE);
	(void)g_setenv("FWUPD_EFIVARS", "dummy", TRUE);

	/* ensure empty tree */
	fu_self_test_mkroot();

	/* do not save silo */
	self->ctx = fu_context_new();
	fu_context_add_flag(self->ctx, FU_CONTEXT_FLAG_NO_IDLE_SOURCES);
	ret = fu_context_load_quirks(self->ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* build a plausible EFI system */
	ret = fu_efivars_set_secure_boot(fu_context_get_efivars(self->ctx), TRUE, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	ret = fu_efivars_set_boot_current(fu_context_get_efivars(self->ctx), 0x0000, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* do not allow mounting disks in the self tests */
	fu_context_add_flag(self->ctx, FU_CONTEXT_FLAG_INHIBIT_VOLUME_MOUNT);

	/* load dummy hwids */
	ret = fu_context_load_hwinfo(self->ctx, progress, FU_CONTEXT_HWID_FLAG_LOAD_CONFIG, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	/* tests go here */
	if (g_test_slow())
		g_test_add_data_func("/fwupd/console", self, fu_console_func);
	g_test_add_func("/fwupd/idle", fu_idle_func);
	g_test_add_func("/fwupd/util", fu_util_func);
	g_test_add_func("/fwupd/client-list", fu_client_list_func);
	g_test_add_func("/fwupd/remote{download}", fu_remote_download_func);
	g_test_add_func("/fwupd/remote{base-uri}", fu_remote_baseuri_func);
	g_test_add_func("/fwupd/remote{no-path}", fu_remote_nopath_func);
	g_test_add_func("/fwupd/remote{local}", fu_remote_local_func);
	g_test_add_func("/fwupd/remote{duplicate}", fu_remote_duplicate_func);
	g_test_add_func("/fwupd/remote{auth}", fu_remote_auth_func);
	g_test_add_func("/fwupd/remote-list{repair}", fu_remote_list_repair_func);
	g_test_add_func("/fwupd/unix-seekable-input-stream", fu_unix_seekable_input_stream_func);
	g_test_add_data_func("/fwupd/backend{usb}", self, fu_backend_usb_func);
	g_test_add_data_func("/fwupd/backend{usb-invalid}", self, fu_backend_usb_invalid_func);
	g_test_add_data_func("/fwupd/plugin{module}", self, fu_plugin_module_func);
	g_test_add_data_func("/fwupd/memcpy", self, fu_memcpy_func);
	g_test_add_func("/fwupd/cabinet", fu_common_cabinet_func);
	g_test_add_data_func("/fwupd/security-attr", self, fu_security_attr_func);
	g_test_add_data_func("/fwupd/device-list", self, fu_device_list_func);
	g_test_add_data_func("/fwupd/device-list{unconnected-no-delay}",
			     self,
			     fu_device_list_unconnected_no_delay_func);
	g_test_add_data_func("/fwupd/device-list{equivalent-id}",
			     self,
			     fu_device_list_equivalent_id_func);
	g_test_add_data_func("/fwupd/device-list{delay}", self, fu_device_list_delay_func);
	g_test_add_data_func("/fwupd/device-list{explicit-order}",
			     self,
			     fu_device_list_explicit_order_func);
	g_test_add_data_func("/fwupd/device-list{explicit-order-post}",
			     self,
			     fu_device_list_explicit_order_post_func);
	g_test_add_data_func("/fwupd/device-list{install-parent-first}",
			     self,
			     fu_device_list_install_parent_first_func);
	g_test_add_data_func("/fwupd/device-list{install-parent-first-child}",
			     self,
			     fu_device_list_install_parent_first_child_func);
	g_test_add_data_func("/fwupd/device-list{no-auto-remove-children}",
			     self,
			     fu_device_list_no_auto_remove_children_func);
	g_test_add_data_func("/fwupd/device-list{compatible}",
			     self,
			     fu_device_list_compatible_func);
	g_test_add_data_func("/fwupd/device-list{remove-chain}",
			     self,
			     fu_device_list_remove_chain_func);
	g_test_add_data_func("/fwupd/device-list{counterpart}",
			     self,
			     fu_device_list_counterpart_func);
	g_test_add_data_func("/fwupd/device-list{better-than}",
			     self,
			     fu_device_list_better_than_func);
	g_test_add_data_func("/fwupd/release{compare}", self, fu_release_compare_func);
	g_test_add_func("/fwupd/release{uri-scheme}", fu_release_uri_scheme_func);
	g_test_add_data_func("/fwupd/release{trusted-report}",
			     self,
			     fu_release_trusted_report_func);
	g_test_add_data_func("/fwupd/release{trusted-report-oem}",
			     self,
			     fu_release_trusted_report_oem_func);
	g_test_add_data_func("/fwupd/release{no-trusted-report-upgrade}",
			     self,
			     fu_release_no_trusted_report_upgrade_func);
	g_test_add_data_func("/fwupd/release{no-trusted-report}",
			     self,
			     fu_release_no_trusted_report_func);
	g_test_add_data_func("/fwupd/engine{get-details-added}",
			     self,
			     fu_engine_get_details_added_func);
	g_test_add_data_func("/fwupd/engine{get-details-missing}",
			     self,
			     fu_engine_get_details_missing_func);
	g_test_add_data_func("/fwupd/engine{device-unlock}", self, fu_engine_device_unlock_func);
	g_test_add_data_func("/fwupd/engine{device-equivalent}",
			     self,
			     fu_engine_device_equivalent_func);
	g_test_add_data_func("/fwupd/engine{device-md-set-flags}",
			     self,
			     fu_engine_device_md_set_flags_func);
	g_test_add_data_func("/fwupd/engine{device-md-checksum-set-version}",
			     self,
			     fu_engine_device_md_checksum_set_version_func);
	g_test_add_data_func("/fwupd/engine{device-md-checksum-set-version-wrong-proto}",
			     self,
			     fu_engine_device_md_checksum_set_version_wrong_proto_func);
	g_test_add_data_func("/fwupd/engine{multiple-releases}",
			     self,
			     fu_engine_multiple_rels_func);
	g_test_add_data_func("/fwupd/engine{install-loop-restart}",
			     self,
			     fu_engine_install_loop_restart_func);
	g_test_add_data_func("/fwupd/engine{install-request}", self, fu_engine_install_request);
	g_test_add_data_func("/fwupd/engine{history-success}", self, fu_engine_history_func);
	g_test_add_data_func("/fwupd/engine{history-verfmt}", self, fu_engine_history_verfmt_func);
	g_test_add_data_func("/fwupd/engine{history-modify}", self, fu_engine_history_modify_func);
	g_test_add_data_func("/fwupd/engine{history-error}", self, fu_engine_history_error_func);
	g_test_add_data_func("/fwupd/engine{fake-hidraw}", self, fu_test_engine_fake_hidraw);
	g_test_add_data_func("/fwupd/engine{fake-usb}", self, fu_test_engine_fake_usb);
	g_test_add_data_func("/fwupd/engine{fake-serio}", self, fu_test_engine_fake_serio);
	g_test_add_data_func("/fwupd/engine{fake-nvme}", self, fu_test_engine_fake_nvme);
	g_test_add_data_func("/fwupd/engine{fake-block}", self, fu_test_engine_fake_block);
	g_test_add_data_func("/fwupd/engine{fake-tpm}", self, fu_test_engine_fake_tpm);
	g_test_add_data_func("/fwupd/engine{fake-v4l}", self, fu_test_engine_fake_v4l);
	if (g_test_slow()) {
		g_test_add_data_func("/fwupd/device-list{replug-auto}",
				     self,
				     fu_device_list_replug_auto_func);
	}
	g_test_add_data_func("/fwupd/device-list{replug-user}",
			     self,
			     fu_device_list_replug_user_func);
	g_test_add_func("/fwupd/engine{machine-hash}", fu_engine_machine_hash_func);
	g_test_add_func("/fwupd/engine{error-array}", fu_engine_error_array_func);
	g_test_add_data_func("/fwupd/engine{report-metadata}",
			     self,
			     fu_engine_report_metadata_func);
	g_test_add_data_func("/fwupd/engine{integrity}", self, fu_engine_integrity_func);
	g_test_add_data_func("/fwupd/engine{require-hwid}", self, fu_engine_require_hwid_func);
	g_test_add_data_func("/fwupd/engine{requires-reboot}",
			     self,
			     fu_engine_install_needs_reboot);
	g_test_add_data_func("/fwupd/engine{history-inherit}", self, fu_engine_history_inherit);
	g_test_add_data_func("/fwupd/engine{history-convert-version}",
			     self,
			     fu_engine_history_convert_version_func);
	g_test_add_data_func("/fwupd/engine{partial-hash}", self, fu_engine_partial_hash_func);
	g_test_add_data_func("/fwupd/engine{downgrade}", self, fu_engine_downgrade_func);
	g_test_add_data_func("/fwupd/engine{md-verfmt}", self, fu_engine_md_verfmt_func);
	g_test_add_data_func("/fwupd/engine{requirements-success}",
			     self,
			     fu_engine_requirements_func);
	g_test_add_data_func("/fwupd/engine{requirements-soft}",
			     self,
			     fu_engine_requirements_soft_func);
	g_test_add_data_func("/fwupd/engine{requirements-missing}",
			     self,
			     fu_engine_requirements_missing_func);
	g_test_add_data_func("/fwupd/engine{requirements-client-fail}",
			     self,
			     fu_engine_requirements_client_fail_func);
	g_test_add_data_func("/fwupd/engine{requirements-client-invalid}",
			     self,
			     fu_engine_requirements_client_invalid_func);
	g_test_add_data_func("/fwupd/engine{requirements-client-pass}",
			     self,
			     fu_engine_requirements_client_pass_func);
	g_test_add_data_func("/fwupd/engine{requirements-not-hardware}",
			     self,
			     fu_engine_requirements_not_hardware_func);
	g_test_add_data_func("/fwupd/engine{requirements-phased}",
			     self,
			     fu_engine_requirements_phased_func);
	g_test_add_data_func("/fwupd/engine{requirements-phased-old-fwpud}",
			     self,
			     fu_engine_requirements_phased_old_fwupd_func);
	g_test_add_data_func("/fwupd/engine{requirements-version-require}",
			     self,
			     fu_engine_requirements_version_require_func);
	g_test_add_data_func("/fwupd/engine{requirements-version-lowest}",
			     self,
			     fu_engine_requirements_version_lowest_func);
	g_test_add_data_func("/fwupd/engine{requirements-parent-device}",
			     self,
			     fu_engine_requirements_parent_device_func);
	g_test_add_data_func("/fwupd/engine{requirements-child-device}",
			     self,
			     fu_engine_requirements_child_device_func);
	g_test_add_data_func("/fwupd/engine{requirements_protocol_check_func}",
			     self,
			     fu_engine_requirements_protocol_check_func);
	g_test_add_data_func("/fwupd/engine{requirements-not-child}",
			     self,
			     fu_engine_requirements_child_func);
	g_test_add_data_func("/fwupd/engine{requirements-not-child-fail}",
			     self,
			     fu_engine_requirements_child_fail_func);
	g_test_add_data_func("/fwupd/engine{requirements-unsupported}",
			     self,
			     fu_engine_requirements_unsupported_func);
	g_test_add_data_func("/fwupd/engine{requirements-device}",
			     self,
			     fu_engine_requirements_device_func);
	g_test_add_data_func("/fwupd/engine{requirements-device-plain}",
			     self,
			     fu_engine_requirements_device_plain_func);
	g_test_add_data_func("/fwupd/engine{requirements-version-format}",
			     self,
			     fu_engine_requirements_version_format_func);
	g_test_add_data_func("/fwupd/engine{requirements-only-upgrade}",
			     self,
			     fu_engine_requirements_only_upgrade_func);
	g_test_add_data_func("/fwupd/engine{requirements-only-upgrade-reinstall}",
			     self,
			     fu_engine_requirements_only_upgrade_reinstall_func);
	g_test_add_data_func("/fwupd/engine{device-auto-parent-id}",
			     self,
			     fu_engine_device_parent_id_func);
	g_test_add_data_func("/fwupd/engine{device-auto-parent-guid}",
			     self,
			     fu_engine_device_parent_guid_func);
	g_test_add_data_func("/fwupd/engine{install-duration}",
			     self,
			     fu_engine_install_duration_func);
	g_test_add_data_func("/fwupd/engine{get-results-appstream-id}",
			     self,
			     fu_plugin_engine_get_results_appstream_id_func);
	g_test_add_data_func("/fwupd/engine{release-dedupe}", self, fu_engine_release_dedupe_func);
	g_test_add_data_func("/fwupd/engine{generate-md}", self, fu_engine_generate_md_func);
	g_test_add_data_func("/fwupd/engine{requirements-other-device}",
			     self,
			     fu_engine_requirements_other_device_func);
	g_test_add_data_func("/fwupd/engine{requirements-sibling-device}",
			     self,
			     fu_engine_requirements_sibling_device_func);
	g_test_add_data_func("/fwupd/engine{requirements-vercmp-glob}",
			     self,
			     fu_engine_requirements_vercmp_glob_func);
	g_test_add_data_func("/fwupd/engine{requirements-vercmp-glob-fallback}",
			     self,
			     fu_engine_requirements_vercmp_glob_fallback_func);
	g_test_add_data_func("/fwupd/engine{plugin-gtypes}", self, fu_engine_plugin_gtypes_func);
	g_test_add_data_func("/fwupd/plugin/mutable",
			     self,
			     fu_engine_test_plugin_mutable_enumeration);
	g_test_add_data_func("/fwupd/plugin{composite}", self, fu_plugin_composite_func);
	g_test_add_data_func("/fwupd/plugin{composite-multistep}",
			     self,
			     fu_plugin_composite_multistep_func);
	g_test_add_data_func("/fwupd/history", self, fu_history_func);
	g_test_add_data_func("/fwupd/history{migrate-v1}", self, fu_history_migrate_v1_func);
	g_test_add_data_func("/fwupd/history{migrate-v2}", self, fu_history_migrate_v2_func);
	g_test_add_data_func("/fwupd/plugin-list", self, fu_plugin_list_func);
	g_test_add_data_func("/fwupd/plugin-list{depsolve}", self, fu_plugin_list_depsolve_func);
	g_test_add_func("/fwupd/common{cab-success}", fu_common_store_cab_func);
	g_test_add_func("/fwupd/common{cab-success-artifact}", fu_common_store_cab_artifact_func);
	g_test_add_func("/fwupd/common{cab-success-unsigned}", fu_common_store_cab_unsigned_func);
	g_test_add_func("/fwupd/common{cab-success-folder}", fu_common_store_cab_folder_func);
	g_test_add_func("/fwupd/common{cab-success-sha256}", fu_common_store_cab_sha256_func);
	g_test_add_func("/fwupd/common{cab-error-no-metadata}",
			fu_common_store_cab_error_no_metadata_func);
	g_test_add_func("/fwupd/common{cab-error-wrong-size}",
			fu_common_store_cab_error_wrong_size_func);
	g_test_add_func("/fwupd/common{cab-error-wrong-checksum}",
			fu_common_store_cab_error_wrong_checksum_func);
	g_test_add_func("/fwupd/common{cab-error-missing-file}",
			fu_common_store_cab_error_missing_file_func);
	g_test_add_func("/fwupd/common{cab-error-size}", fu_common_store_cab_error_size_func);
	g_test_add_data_func("/fwupd/write-bios-attrs", self, fu_engine_modify_bios_settings_func);

	/* these need to be last as they overwrite stuff in the mkroot */
	g_test_add_func("/fwupd/config_migrate_1_7", fu_config_migrate_1_7_func);
	g_test_add_func("/fwupd/config_migrate_1_9", fu_config_migrate_1_9_func);
	return g_test_run();
}
