/*
 *
 *  Embedded Linux library
 *
 *  Copyright (C) 2011-2014  Intel Corporation. All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>

#include <ell/ell.h>
#include "ell/dbus-private.h"

#ifndef WAIT_ANY
#define WAIT_ANY (-1) /* Any process */
#endif

#define TEST_BUS_ADDRESS "unix:path=/tmp/ell-test-bus"
#define ROOT_PATH "/ell/test"

static pid_t dbus_daemon_pid = -1;

static bool start_dbus_daemon(void)
{
	char *prg_argv[5];
	char *prg_envp[1];
	pid_t pid;

	prg_argv[0] = "/usr/bin/dbus-daemon";
	prg_argv[1] = "--nopidfile";
	prg_argv[2] = "--nofork";
	prg_argv[3] = "--config-file=" UNITDIR "dbus.conf";
	prg_argv[4] = NULL;

	prg_envp[0] = NULL;

	l_info("launching dbus-daemon");

	pid = fork();
	if (pid < 0) {
		l_error("failed to fork new process");
		return false;
	}

	if (pid == 0) {
		execve(prg_argv[0], prg_argv, prg_envp);
		exit(EXIT_SUCCESS);
	}

	l_info("dbus-daemon process %d created", pid);

	dbus_daemon_pid = pid;

	return true;
}

static void signal_handler(uint32_t signo, void *user_data)
{
	switch (signo) {
	case SIGINT:
	case SIGTERM:
		l_info("Terminate");
		l_main_quit();
		break;
	}
}

static void sigchld_handler(void *user_data)
{
	while (1) {
		pid_t pid;
		int status;

		pid = waitpid(WAIT_ANY, &status, WNOHANG);
		if (pid < 0 || pid == 0)
			break;

		l_info("process %d terminated with status=%d\n", pid, status);

		if (pid == dbus_daemon_pid) {
			dbus_daemon_pid = -1;
			l_main_quit();
		}
	}
}

static struct l_dbus *dbus;

struct dbus_test {
	const char *name;
	void (*start)(struct l_dbus *dbus, void *);
	void *data;
};

static bool success;
static struct l_queue *tests;
static const struct l_queue_entry *current;

static void test_add(const char *name,
			void (*start)(struct l_dbus *dbus, void *),
			void *test_data)
{
	struct dbus_test *test = l_new(struct dbus_test, 1);

	test->name = name;
	test->start = start;
	test->data = test_data;

	if (!tests)
		tests = l_queue_new();

	l_queue_push_tail(tests, test);
}

static void test_next()
{
	struct dbus_test *test;

	if (current)
		current = current->next;
	else
		current = l_queue_get_entries(tests);

	if (!current) {
		success = true;
		l_main_quit();
		return;
	}

	test = current->data;

	l_info("TEST: %s", test->name);

	test->start(dbus, test->data);
}

#define test_assert(cond)	\
	do {	\
		if (!(cond)) {	\
			l_info("TEST FAILED in %s at %s:%i: %s",	\
				__func__, __FILE__, __LINE__,	\
				L_STRINGIFY(cond));	\
			l_main_quit();	\
			return;	\
		}	\
	} while (0)

static void request_name_callback(struct l_dbus *dbus, bool success,
					bool queued, void *user_data)
{
	l_info("request name result=%s",
		success ? (queued ? "queued" : "success") : "failed");

	test_next();
}

static void ready_callback(void *user_data)
{
	l_info("ready");

	l_dbus_name_acquire(dbus, "org.test", false, false, false,
				request_name_callback, NULL);
}

static void disconnect_callback(void *user_data)
{
	l_info("Disconnected from DBus");
	l_main_quit();
}

static bool test_string_getter(struct l_dbus *dbus,
				struct l_dbus_message *message,
				struct l_dbus_message_builder *builder,
				void *user_data)
{
	return l_dbus_message_builder_append_basic(builder, 's', "foo");
}

static bool setter_called;
static bool int_optional;

static struct l_dbus_message *test_string_setter(struct l_dbus *dbus,
					struct l_dbus_message *message,
					struct l_dbus_message_iter *new_value,
					l_dbus_property_complete_cb_t complete,
					void *user_data)
{
	const char *strvalue;

	if (!l_dbus_message_iter_get_variant(new_value, "s", &strvalue))
		goto done;

	if (strcmp(strvalue, "bar"))
		goto done;

	setter_called = true;

done:
	complete(dbus, message, NULL);

	return NULL;
}

static bool test_int_getter(struct l_dbus *dbus,
				struct l_dbus_message *message,
				struct l_dbus_message_builder *builder,
				void *user_data)
{
	uint32_t u;

	if (int_optional)
		return false;

	u = 5;

	return l_dbus_message_builder_append_basic(builder, 'u', &u);
}

static struct l_dbus_message *test_int_setter(struct l_dbus *dbus,
					struct l_dbus_message *message,
					struct l_dbus_message_iter *new_value,
					l_dbus_property_complete_cb_t complete,
					void *user_data)
{
	uint32_t u;

	if (!l_dbus_message_iter_get_variant(new_value, "u", &u))
		goto done;

	if (u != 42)
		goto done;

	setter_called = true;

done:
	return l_dbus_message_new_method_return(message);
}

static struct l_dbus_message *test_error_setter(struct l_dbus *dbus,
					struct l_dbus_message *message,
					struct l_dbus_message_iter *new_value,
					l_dbus_property_complete_cb_t complete,
					void *user_data)
{
	setter_called = true;

	return l_dbus_message_new_error(message, "org.test.Error", "Error");
}

static bool test_path_getter(struct l_dbus *dbus,
				struct l_dbus_message *message,
				struct l_dbus_message_builder *builder,
				void *user_data)
{
	return l_dbus_message_builder_append_basic(builder, 'o', "/foo/bar");
}

static void setup_test_interface(struct l_dbus_interface *interface)
{
	l_dbus_interface_property(interface, "String",
					L_DBUS_PROPERTY_FLAG_AUTO_EMIT, "s",
					test_string_getter, test_string_setter);
	l_dbus_interface_property(interface, "Integer",
					L_DBUS_PROPERTY_FLAG_AUTO_EMIT, "u",
					test_int_getter, test_int_setter);
	l_dbus_interface_property(interface, "Readonly", 0, "s",
					test_string_getter, NULL);
	l_dbus_interface_property(interface, "SetError", 0, "s",
					test_string_getter, test_error_setter);
	l_dbus_interface_property(interface, "Path", 0, "o",
					test_path_getter, NULL);
}

static void validate_properties(struct l_dbus_message_iter *dict)
{
	struct l_dbus_message_iter variant;
	const char *name, *strval;
	uint32_t intval;

	test_assert(l_dbus_message_iter_next_entry(dict, &name, &variant));
	test_assert(!strcmp(name, "String"));
	test_assert(l_dbus_message_iter_get_variant(&variant, "s", &strval));
	test_assert(!strcmp(strval, "foo"));

	if (!int_optional) {
		test_assert(l_dbus_message_iter_next_entry(dict, &name,
								&variant));
		test_assert(!strcmp(name, "Integer"));
		test_assert(l_dbus_message_iter_get_variant(&variant, "u",
								&intval));
		test_assert(intval == 5);
	}

	test_assert(l_dbus_message_iter_next_entry(dict, &name, &variant));
	test_assert(!strcmp(name, "Readonly"));
	test_assert(l_dbus_message_iter_get_variant(&variant, "s", &strval));
	test_assert(!strcmp(strval, "foo"));

	test_assert(l_dbus_message_iter_next_entry(dict, &name, &variant));
	test_assert(!strcmp(name, "SetError"));
	test_assert(l_dbus_message_iter_get_variant(&variant, "s", &strval));
	test_assert(!strcmp(strval, "foo"));

	test_assert(l_dbus_message_iter_next_entry(dict, &name, &variant));
	test_assert(!strcmp(name, "Path"));
	test_assert(l_dbus_message_iter_get_variant(&variant, "o", &strval));
	test_assert(!strcmp(strval, "/foo/bar"));

	test_assert(!l_dbus_message_iter_next_entry(dict, &name, &variant));
}

static void get_properties_callback(struct l_dbus_message *message,
					void *user_data)
{
	struct l_dbus_message_iter dict;

	test_assert(!l_dbus_message_get_error(message, NULL, NULL));
	test_assert(l_dbus_message_get_arguments(message, "a{sv}", &dict));

	validate_properties(&dict);

	test_next();
}

static void test_old_get(struct l_dbus *dbus, void *test_data)
{
	struct l_dbus_message *call =
		l_dbus_message_new_method_call(dbus, "org.test",
						ROOT_PATH"/test",
						"org.test", "GetProperties");

	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, ""));

	test_assert(l_dbus_send_with_reply(dbus, call, get_properties_callback,
						NULL, NULL));
}

static void test_old_optional_get(struct l_dbus *dbus, void *test_data)
{
	struct l_dbus_message *call =
		l_dbus_message_new_method_call(dbus, "org.test",
						ROOT_PATH"/test",
						"org.test", "GetProperties");

	int_optional = true;

	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, ""));

	test_assert(l_dbus_send_with_reply(dbus, call, get_properties_callback,
						NULL, NULL));
}
static void set_invalid_callback(struct l_dbus_message *message,
					void *user_data)
{
	test_assert(l_dbus_message_get_error(message, NULL, NULL));
	test_assert(!setter_called);

	test_next();
}

static void old_set_error_callback(struct l_dbus_message *message,
					void *user_data)
{
	struct l_dbus_message *call;

	test_assert(l_dbus_message_get_error(message, NULL, NULL));
	test_assert(setter_called);
	setter_called = false;

	call = l_dbus_message_new_method_call(dbus, "org.test",
						ROOT_PATH"/test",
						"org.test", "SetProperty");
	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, "sv", "Invalid",
							"s", "bar"));

	test_assert(l_dbus_send_with_reply(dbus, call, set_invalid_callback,
						NULL, NULL));
}

static void old_set_ro_callback(struct l_dbus_message *message,
					void *user_data)
{
	struct l_dbus_message *call;

	test_assert(l_dbus_message_get_error(message, NULL, NULL));
	test_assert(!setter_called);

	call = l_dbus_message_new_method_call(dbus, "org.test",
						ROOT_PATH"/test",
						"org.test", "SetProperty");
	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, "sv", "SetError",
							"s", "bar"));

	test_assert(l_dbus_send_with_reply(dbus, call, old_set_error_callback,
						NULL, NULL));
}

static void old_set_int_callback(struct l_dbus_message *message,
					void *user_data)
{
	struct l_dbus_message *call;

	test_assert(!l_dbus_message_get_error(message, NULL, NULL));
	test_assert(l_dbus_message_get_arguments(message, ""));
	test_assert(setter_called);
	setter_called = false;

	call = l_dbus_message_new_method_call(dbus, "org.test",
						ROOT_PATH"/test",
						"org.test", "SetProperty");
	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, "sv", "Readonly",
							"s", "bar"));

	test_assert(l_dbus_send_with_reply(dbus, call, old_set_ro_callback,
						NULL, NULL));
}

static void old_set_string_callback(struct l_dbus_message *message,
					void *user_data)
{
	struct l_dbus_message *call;

	test_assert(!l_dbus_message_get_error(message, NULL, NULL));
	test_assert(l_dbus_message_get_arguments(message, ""));
	test_assert(setter_called);
	setter_called = false;

	call = l_dbus_message_new_method_call(dbus, "org.test",
						ROOT_PATH"/test",
						"org.test", "SetProperty");
	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, "sv", "Integer",
							"u", 42));

	test_assert(l_dbus_send_with_reply(dbus, call, old_set_int_callback,
						NULL, NULL));
}

static void test_old_set(struct l_dbus *dbus, void *test_data)
{
	struct l_dbus_message *call =
		l_dbus_message_new_method_call(dbus, "org.test",
						ROOT_PATH"/test",
						"org.test", "SetProperty");

	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, "sv", "String",
							"s", "bar"));

	test_assert(!setter_called);
	test_assert(l_dbus_send_with_reply(dbus, call, old_set_string_callback,
						NULL, NULL));
}

static void new_get_invalid_callback(struct l_dbus_message *message,
					void *user_data)
{
	struct l_dbus_message *call;

	test_assert(l_dbus_message_get_error(message, NULL, NULL));

	call = l_dbus_message_new_method_call(dbus, "org.test",
					ROOT_PATH"/test",
					"org.freedesktop.DBus.Properties",
					"GetAll");
	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, "s", "org.test"));

	test_assert(l_dbus_send_with_reply(dbus, call, get_properties_callback,
						NULL, NULL));
}

static void new_get_bad_if_callback(struct l_dbus_message *message,
					void *user_data)
{
	struct l_dbus_message *call;

	test_assert(l_dbus_message_get_error(message, NULL, NULL));

	call = l_dbus_message_new_method_call(dbus, "org.test",
					ROOT_PATH"/test",
					"org.freedesktop.DBus.Properties",
					"Get");
	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, "ss",
							"org.test", "Invalid"));

	test_assert(l_dbus_send_with_reply(dbus, call, new_get_invalid_callback,
						NULL, NULL));
}

static void new_get_callback(struct l_dbus_message *message, void *user_data)
{
	struct l_dbus_message_iter variant;
	const char *strval;
	struct l_dbus_message *call;

	test_assert(!l_dbus_message_get_error(message, NULL, NULL));
	test_assert(l_dbus_message_get_arguments(message, "v", &variant));
	test_assert(l_dbus_message_iter_get_variant(&variant, "s", &strval));
	test_assert(!strcmp(strval, "foo"));

	call = l_dbus_message_new_method_call(dbus, "org.test",
					ROOT_PATH"/test",
					"org.freedesktop.DBus.Properties",
					"Get");
	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, "ss", "org.invalid",
							"String"));

	test_assert(l_dbus_send_with_reply(dbus, call, new_get_bad_if_callback,
						NULL, NULL));
}

static void test_new_get(struct l_dbus *dbus, void *test_data)
{
	struct l_dbus_message *call =
		l_dbus_message_new_method_call(dbus, "org.test",
					ROOT_PATH"/test",
					"org.freedesktop.DBus.Properties",
					"Get");

	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, "ss",
							"org.test", "String"));

	test_assert(l_dbus_send_with_reply(dbus, call, new_get_callback,
						NULL, NULL));
}

static void new_set_bad_if_callback(struct l_dbus_message *message,
					void *user_data)
{
	struct l_dbus_message *call;

	test_assert(l_dbus_message_get_error(message, NULL, NULL));
	test_assert(!setter_called);

	call = l_dbus_message_new_method_call(dbus, "org.test",
					ROOT_PATH"/test",
					"org.freedesktop.DBus.Properties",
					"Set");
	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, "ssv", "org.test",
							"Invalid", "s", "bar"));

	test_assert(l_dbus_send_with_reply(dbus, call, set_invalid_callback,
						NULL, NULL));
}

static void new_set_error_callback(struct l_dbus_message *message,
					void *user_data)
{
	struct l_dbus_message *call;

	test_assert(l_dbus_message_get_error(message, NULL, NULL));
	test_assert(setter_called);
	setter_called = false;

	call = l_dbus_message_new_method_call(dbus, "org.test",
					ROOT_PATH"/test",
					"org.freedesktop.DBus.Properties",
					"Set");
	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, "ssv", "org.invalid",
							"String", "s", "bar"));

	test_assert(l_dbus_send_with_reply(dbus, call, new_set_bad_if_callback,
						NULL, NULL));
}

static void new_set_ro_callback(struct l_dbus_message *message,
					void *user_data)
{
	struct l_dbus_message *call;

	test_assert(l_dbus_message_get_error(message, NULL, NULL));
	test_assert(!setter_called);

	call = l_dbus_message_new_method_call(dbus, "org.test",
					ROOT_PATH"/test",
					"org.freedesktop.DBus.Properties",
					"Set");
	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, "ssv", "org.test",
							"SetError",
							"s", "bar"));

	test_assert(l_dbus_send_with_reply(dbus, call, new_set_error_callback,
						NULL, NULL));
}

static void new_set_int_callback(struct l_dbus_message *message,
					void *user_data)
{
	struct l_dbus_message *call;

	test_assert(!l_dbus_message_get_error(message, NULL, NULL));
	test_assert(l_dbus_message_get_arguments(message, ""));
	test_assert(setter_called);
	setter_called = false;

	call = l_dbus_message_new_method_call(dbus, "org.test",
					ROOT_PATH"/test",
					"org.freedesktop.DBus.Properties",
					"Set");
	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, "ssv", "org.test",
							"Readonly",
							"s", "bar"));

	test_assert(l_dbus_send_with_reply(dbus, call, new_set_ro_callback,
						NULL, NULL));
}

static void new_set_string_callback(struct l_dbus_message *message,
					void *user_data)
{
	struct l_dbus_message *call;

	test_assert(!l_dbus_message_get_error(message, NULL, NULL));
	test_assert(l_dbus_message_get_arguments(message, ""));
	test_assert(setter_called);
	setter_called = false;

	call = l_dbus_message_new_method_call(dbus, "org.test",
					ROOT_PATH"/test",
					"org.freedesktop.DBus.Properties",
					"Set");
	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, "ssv", "org.test",
							"Integer", "u", 42));

	test_assert(l_dbus_send_with_reply(dbus, call, new_set_int_callback,
						NULL, NULL));
}

static void test_new_set(struct l_dbus *dbus, void *test_data)
{
	struct l_dbus_message *call =
		l_dbus_message_new_method_call(dbus, "org.test",
					ROOT_PATH"/test",
					"org.freedesktop.DBus.Properties",
					"Set");

	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, "ssv", "org.test",
							"String", "s", "bar"));

	test_assert(!setter_called);
	test_assert(l_dbus_send_with_reply(dbus, call, new_set_string_callback,
						NULL, NULL));
}

static struct l_timeout *signal_timeout;

static void signal_timeout_callback(struct l_timeout *timeout, void *user_data)
{
	signal_timeout = NULL;
	test_assert(false);
}

static bool old_signal_received, new_signal_received;
static bool signal_success;

static void test_check_signal_success(void)
{
	struct l_dbus_message *call;

	if (!old_signal_received || !new_signal_received)
		return;

	l_timeout_remove(signal_timeout);
	signal_timeout = NULL;

	if (!signal_success) {
		signal_success = true;

		/* Now repeat the test for the signal triggered by Set */

		old_signal_received = false;
		new_signal_received = false;

		signal_timeout = l_timeout_create(1, signal_timeout_callback,
							NULL, NULL);
		test_assert(signal_timeout);

		call = l_dbus_message_new_method_call(dbus, "org.test",
					ROOT_PATH"/test",
					"org.freedesktop.DBus.Properties",
					"Set");
		test_assert(call);
		test_assert(l_dbus_message_set_arguments(call, "ssv",
							"org.test", "String",
							"s", "bar"));

		test_assert(!setter_called);
		test_assert(l_dbus_send(dbus, call));
	} else {
		test_assert(setter_called);
		setter_called = false;

		test_next();
	}
}

static void test_old_signal_callback(struct l_dbus_message *message,
					void *user_data)
{
	const char *property, *value;
	struct l_dbus_message_iter variant;

	if (!signal_timeout)
		return;

	test_assert(l_dbus_message_get_arguments(message, "sv",
							&property, &variant));
	test_assert(!strcmp(property, "String"));
	test_assert(l_dbus_message_iter_get_variant(&variant, "s", &value));
	test_assert(!strcmp(value, "foo"));

	test_assert(!old_signal_received);
	old_signal_received = true;

	test_check_signal_success();
}

static void test_new_signal_callback(struct l_dbus_message *message,
					void *user_data)
{
	const char *interface, *property, *value;
	struct l_dbus_message_iter variant, changed, invalidated;

	if (!signal_timeout)
		return;

	test_assert(l_dbus_message_get_arguments(message, "sa{sv}as",
							&interface, &changed,
							&invalidated));

	test_assert(l_dbus_message_iter_next_entry(&changed, &property,
							&variant));
	test_assert(!strcmp(property, "String"));
	test_assert(l_dbus_message_iter_get_variant(&variant, "s", &value));
	test_assert(!strcmp(value, "foo"));

	test_assert(!l_dbus_message_iter_next_entry(&changed, &property,
							&variant));
	test_assert(!l_dbus_message_iter_next_entry(&invalidated,
							&property));

	test_assert(!new_signal_received);
	new_signal_received = true;

	test_check_signal_success();
}

static void test_property_signals(struct l_dbus *dbus, void *test_data)
{
	old_signal_received = false;
	new_signal_received = false;

	signal_timeout = l_timeout_create(1, signal_timeout_callback,
						NULL, NULL);
	test_assert(signal_timeout);

	test_assert(l_dbus_property_changed(dbus, ROOT_PATH"/test",
						"org.test", "String"));
}

static void object_manager_callback(struct l_dbus_message *message,
					void *user_data)
{
	struct l_dbus_message_iter objects, interfaces, properties, variant;
	const char *path, *interface, *name;
	bool object_manager_found = false;
	bool test_found = false;
	bool properties_found = false;

	test_assert(!l_dbus_message_get_error(message, NULL, NULL));
	test_assert(l_dbus_message_get_arguments(message, "a{oa{sa{sv}}}",
							&objects));

	while (l_dbus_message_iter_next_entry(&objects, &path, &interfaces)) {
		while (l_dbus_message_iter_next_entry(&interfaces, &interface,
							&properties)) {
			if (!strcmp(path, ROOT_PATH) && !strcmp(interface,
					"org.freedesktop.DBus.ObjectManager")) {
				test_assert(!object_manager_found);
				object_manager_found = true;
				test_assert(!l_dbus_message_iter_next_entry(
							&properties, &name,
							&variant));
			}

			if (!strcmp(path, ROOT_PATH"/test") &&
					!strcmp(interface,
					"org.freedesktop.DBus.Properties")) {
				test_assert(!properties_found);
				properties_found = true;
				test_assert(!l_dbus_message_iter_next_entry(
							&properties, &name,
							&variant));
			}

			if (!strcmp(path, ROOT_PATH"/test") &&
					!strcmp(interface, "org.test")) {
				test_assert(!test_found);
				test_found = true;
				validate_properties(&properties);
			}
		}
	}

	test_assert(object_manager_found && test_found && properties_found);

	test_next();
}

static void test_object_manager_get(struct l_dbus *dbus, void *test_data)
{
	struct l_dbus_message *call =
		l_dbus_message_new_method_call(dbus, "org.test",
					ROOT_PATH,
					"org.freedesktop.DBus.ObjectManager",
					"GetManagedObjects");

	test_assert(call);
	test_assert(l_dbus_message_set_arguments(call, ""));

	test_assert(l_dbus_send_with_reply(dbus, call, object_manager_callback,
						NULL, NULL));
}

static struct l_timeout *om_signal_timeout;

static void om_signal_timeout_callback(struct l_timeout *timeout,
					void *user_data)
{
	om_signal_timeout = NULL;
	test_assert(false);
}

static bool expect_interfaces_added;

static void om_signal_callback(struct l_dbus_message *message, void *user_data)
{
	const char *path, *interface, *member;
	struct l_dbus_message_iter interfaces, properties;

	if (!om_signal_timeout)
		return;

	member = l_dbus_message_get_member(message);

	if (!strcmp(member, "InterfacesAdded"))
		test_assert(expect_interfaces_added);
	else if (!strcmp(member, "InterfacesRemoved"))
		test_assert(!expect_interfaces_added);
	else
		return;

	if (!strcmp(member, "InterfacesAdded")) {
		test_assert(l_dbus_message_get_arguments(message, "oa{sa{sv}}",
								&path,
								&interfaces));
		test_assert(!strcmp(path, ROOT_PATH"/test2"));

		test_assert(l_dbus_message_iter_next_entry(&interfaces,
								&interface,
								&properties));
		test_assert(!strcmp(interface, "org.test"));
		validate_properties(&properties);

		test_assert(!l_dbus_message_iter_next_entry(&interfaces,
								&interface,
								&properties));

		/* Now repeat the test for the InterfacesRemoved signal */

		expect_interfaces_added = false;
		test_assert(l_dbus_unregister_object(dbus, ROOT_PATH"/test2"));
	} else {
		test_assert(l_dbus_message_get_arguments(message, "oas",
								&path,
								&interfaces));
		test_assert(!strcmp(path, ROOT_PATH"/test2"));

		test_assert(l_dbus_message_iter_next_entry(&interfaces,
								&interface));
		test_assert(!strcmp(interface, "org.test"));

		test_assert(!l_dbus_message_iter_next_entry(&interfaces,
								&interface));

		l_timeout_remove(om_signal_timeout);
		om_signal_timeout = NULL;

		test_next();
	}
}

static void test_object_manager_signals(struct l_dbus *dbus, void *test_data)
{
	om_signal_timeout = l_timeout_create(1, om_signal_timeout_callback,
						NULL, NULL);
	test_assert(om_signal_timeout);

	expect_interfaces_added = true;
	test_assert(l_dbus_object_add_interface(dbus, ROOT_PATH"/test2",
						"org.test", NULL));
}

static void test_run(void)
{
	success = false;

	l_dbus_set_ready_handler(dbus, ready_callback, dbus, NULL);
	l_dbus_set_disconnect_handler(dbus, disconnect_callback, NULL, NULL);

	if (!l_dbus_register_interface(dbus, "org.test", setup_test_interface,
					NULL, true)) {
		l_info("Unable to register interface");
		return;
	}

	if (!l_dbus_object_add_interface(dbus, ROOT_PATH"/test",
						"org.test", NULL)) {
		l_info("Unable to instantiate interface");
		return;
	}

	if (!l_dbus_object_add_interface(dbus, ROOT_PATH"/test",
				"org.freedesktop.DBus.Properties", NULL)) {
		l_info("Unable to instantiate the properties interface");
		return;
	}

	l_dbus_add_signal_watch(dbus, "org.test", ROOT_PATH"/test", "org.test",
				"PropertyChanged", L_DBUS_MATCH_NONE,
				test_old_signal_callback, NULL);
	l_dbus_add_signal_watch(dbus, "org.test", ROOT_PATH"/test",
				"org.freedesktop.DBus.Properties",
				"PropertiesChanged", L_DBUS_MATCH_ARGUMENT(0),
				"org.test", L_DBUS_MATCH_NONE,
				test_new_signal_callback, NULL);

	if (!l_dbus_object_manager_enable(dbus, ROOT_PATH)) {
		l_info("Unable to enable Object Manager");
		return;
	}

	l_dbus_add_signal_watch(dbus, "org.test", ROOT_PATH,
				"org.freedesktop.DBus.ObjectManager",
				NULL, L_DBUS_MATCH_NONE,
				om_signal_callback, NULL);

	l_main_run_with_signal(signal_handler, NULL);
}

int main(int argc, char *argv[])
{
	struct l_signal *sigchld;
	int i;

	if (!l_main_init())
		return -1;

	test_add("Legacy properties get", test_old_get, NULL);
	test_add("Legacy properties set", test_old_set, NULL);
	test_add("Legacy optional property", test_old_optional_get, NULL);
	test_add("org.freedesktop.DBus.Properties get", test_new_get, NULL);
	test_add("org.freedesktop.DBus.Properties set", test_new_set, NULL);
	test_add("Property changed signals", test_property_signals, NULL);
	test_add("org.freedesktop.DBus.ObjectManager get",
			test_object_manager_get, NULL);
	test_add("org.freedesktop.DBus.ObjectManager signals",
			test_object_manager_signals, NULL);

	sigchld = l_signal_create(SIGCHLD, sigchld_handler, NULL, NULL);

	l_log_set_stderr();

	if (!start_dbus_daemon())
		return -1;

	for (i = 0; i < 10; i++) {
		usleep(200 * 1000);

		dbus = l_dbus_new(TEST_BUS_ADDRESS);
		if (dbus)
			break;
	}

	if (!dbus)
		goto done;

	test_run();

	l_dbus_destroy(dbus);

done:
	if (dbus_daemon_pid > 0)
		kill(dbus_daemon_pid, SIGKILL);

	l_signal_remove(sigchld);
	l_queue_destroy(tests, l_free);

	l_main_exit();

	if (!success)
		abort();

	return 0;
}
