Commit 1191262f authored by Simon McVittie's avatar Simon McVittie

Translate Python-based tests to C

This simplifies bootstrapping: now you don't have to build dbus,
build dbus-python (with GLib), and use dbus-python to test dbus.

It also avoids test failures when using facilities like
AddressSanitizer. When libdbus is built with AddressSanitizer, but the
system copies of Python and dbus-python were not, dbus-python will exit
the Python interpreter on load, because libasan wasn't already
initialized. The simplest way to avoid this is to not use Python:
the scripts are not *that* hard to translate into C.

Both of these tests happen to be conditionally compiled for Unix only.
test_activation_forking() relies on code in TestSuiteForkingEchoService
that calls fork(), which can only work on Unix; meanwhile,
test_system_signals() tests the system bus configuration, which is
only relevant to Unix because we don't support using dbus-daemon as
a privilege boundary on Windows (and in any case D-Bus is not a Windows
OS feature, so the system bus cannot be used to communicate with OS
services like it can on most Linux systems).

This is also a partial solution to
<#135>, by reducing the
size of name-test/.

For this to work, we need to build the test-service helper executable
even if embedded tests are disabled.
Signed-off-by: Simon McVittie's avatarSimon McVittie <smcv@collabora.com>
parent ebe57443
......@@ -25,9 +25,7 @@ dist: trusty
language: c
script:
- ./tools/ci-install.sh
# python-dbus and python-gi aren't available to Travis's version of
# Python in /opt, which it uses as a default
- PYTHON=/usr/bin/python ci_parallel=2 ci_sudo=yes ./tools/ci-build.sh
- ci_parallel=2 ci_sudo=yes ./tools/ci-build.sh
env:
- ci_variant=production
......
......@@ -166,20 +166,10 @@ for your binding.
Bootstrapping D-Bus on new platforms
===
A full build of D-Bus, with all regression tests enabled and run, has some
dependencies which themselves depend on D-Bus, either for compilation or
for some of *their* regression tests: GLib, dbus-glib and dbus-python are
currently affected.
To avoid circular dependencies, when bootstrapping D-Bus for the first time
on a new OS or CPU architecture, you can either cross-compile some of
those components, or choose the build order and options carefully:
* build and install D-Bus without tests
- do not use the --enable-modular-tests=yes configure option
- do not use the --enable-tests=yes configure option
* build and install GLib, again without tests
* use those versions of libdbus and GLib to build and install dbus-glib
* ... and use those to install dbus-python
* rebuild libdbus; this time you can run all of the tests
* rebuild GLib; this time you can run all of the tests
A full build of dbus, with all regression tests enabled and run, depends
on GLib. A full build of GLib, with all regression tests enabled and run,
depends on dbus.
To break this cycle, don't enable full test coverage (for at least one
of those projects) during bootstrapping. You can rebuild with full test
coverage after you have built both dbus and GLib at least once.
......@@ -247,7 +247,7 @@ AC_ARG_ENABLE([tests],
[
if test "x$enableval" = xyes; then
AC_MSG_NOTICE([Full test coverage was requested with --enable-tests=yes])
AC_MSG_NOTICE([This has many dependencies (GLib, Python etc.)])
AC_MSG_NOTICE([This requires GLib])
fi
enable_embedded_tests=$enableval
enable_modular_tests=$enableval
......@@ -313,22 +313,6 @@ AC_ARG_ENABLE([installed-tests],
AM_CONDITIONAL([DBUS_ENABLE_INSTALLED_TESTS],
[test "x$enable_installed_tests" = xyes])
if test "x$enable_tests" = xyes; then
# full test coverage is required, Python is a hard dependency
AC_MSG_NOTICE([Full test coverage (--enable-tests=yes) requires Python, dbus-python, pygi])
AM_PATH_PYTHON([2.6])
AC_MSG_CHECKING([for Python modules for full test coverage])
if "$PYTHON" -c "import dbus, gi.repository.GObject, dbus.mainloop.glib"; then
AC_MSG_RESULT([yes])
else
AC_MSG_RESULT([no])
AC_MSG_ERROR([cannot import dbus, gi.repository.GObject, dbus.mainloop.glib Python modules])
fi
else
# --enable-tests not given: do not abort if Python is missing
AM_PATH_PYTHON([2.6], [], [:])
fi
if test x$enable_verbose_mode = xyes; then
AC_DEFINE(DBUS_ENABLE_VERBOSE_MODE,1,[Support a verbose mode])
fi
......
......@@ -64,7 +64,6 @@ TEST_BINARIES = \
test-exit \
test-names \
test-segfault \
test-service \
test-shell-service \
$(NULL)
......@@ -145,6 +144,7 @@ nobase_testexec_PROGRAMS =
nobase_testmeta_DATA =
installable_helpers = \
test-service \
test-sleep-forever \
$(NULL)
installable_tests = \
......
/* Integration tests for the dbus-daemon
*
* Author: Simon McVittie <simon.mcvittie@collabora.co.uk>
* Copyright © 2008 Red Hat, Inc.
* Copyright © 2010-2011 Nokia Corporation
* Copyright © 2015 Collabora Ltd.
* Copyright © 2015-2018 Collabora Ltd.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
......@@ -103,6 +104,7 @@ typedef struct {
gboolean right_conn_echo;
gboolean right_conn_hold;
gboolean wait_forever_called;
guint activation_forking_counter;
gchar *tmp_runtime_dir;
gchar *saved_runtime_dir;
......@@ -1946,6 +1948,225 @@ test_fd_limit (Fixture *f,
#endif /* !HAVE_PRLIMIT */
}
#define ECHO_SERVICE "org.freedesktop.DBus.TestSuiteEchoService"
#define FORKING_ECHO_SERVICE "org.freedesktop.DBus.TestSuiteForkingEchoService"
#define ECHO_SERVICE_PATH "/org/freedesktop/TestSuite"
#define ECHO_SERVICE_INTERFACE "org.freedesktop.TestSuite"
/*
* Helper for test_activation_forking: whenever the forking service is
* activated, start it again.
*/
static DBusHandlerResult
activation_forking_signal_filter (DBusConnection *connection,
DBusMessage *message,
void *user_data)
{
Fixture *f = user_data;
if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
"NameOwnerChanged"))
{
dbus_bool_t ok;
const char *name;
const char *old_owner;
const char *new_owner;
ok = dbus_message_get_args (message, &f->e,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_STRING, &old_owner,
DBUS_TYPE_STRING, &new_owner,
DBUS_TYPE_INVALID);
test_assert_no_error (&f->e);
g_assert_true (ok);
g_test_message ("owner of \"%s\": \"%s\" -> \"%s\"",
name, old_owner, new_owner);
if (g_strcmp0 (name, FORKING_ECHO_SERVICE) != 0)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
if (f->activation_forking_counter > 10)
{
g_test_message ("Activated 10 times OK, TestSuiteForkingEchoService pass");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
f->activation_forking_counter++;
if (g_strcmp0 (new_owner, "") == 0)
{
/* Reactivate it, and tell it to exit immediately. */
DBusMessage *echo_call = NULL;
DBusMessage *exit_call = NULL;
gchar *payload = NULL;
payload = g_strdup_printf ("counter %u", f->activation_forking_counter);
echo_call = dbus_message_new_method_call (FORKING_ECHO_SERVICE,
ECHO_SERVICE_PATH,
ECHO_SERVICE_INTERFACE,
"Echo");
exit_call = dbus_message_new_method_call (FORKING_ECHO_SERVICE,
ECHO_SERVICE_PATH,
ECHO_SERVICE_INTERFACE,
"Exit");
if (echo_call == NULL ||
!dbus_message_append_args (echo_call,
DBUS_TYPE_STRING, &payload,
DBUS_TYPE_INVALID) ||
exit_call == NULL ||
!dbus_connection_send (connection, echo_call, NULL) ||
!dbus_connection_send (connection, exit_call, NULL))
g_error ("OOM");
dbus_clear_message (&echo_call);
dbus_clear_message (&exit_call);
g_free (payload);
}
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
/*
* Assert that Unix services are allowed to daemonize, and this does not
* cause us to signal an activation failure.
*/
static void
test_activation_forking (Fixture *f,
gconstpointer context G_GNUC_UNUSED)
{
DBusMessage *call = NULL;
DBusMessage *reply = NULL;
const char *hello = "hello world";
if (f->skip)
return;
if (!dbus_connection_add_filter (f->left_conn,
activation_forking_signal_filter,
f, NULL))
g_error ("OOM");
/* Start it up */
call = dbus_message_new_method_call (FORKING_ECHO_SERVICE,
ECHO_SERVICE_PATH,
ECHO_SERVICE_INTERFACE,
"Echo");
if (call == NULL ||
!dbus_message_append_args (call,
DBUS_TYPE_STRING, &hello,
DBUS_TYPE_INVALID))
g_error ("OOM");
dbus_bus_add_match (f->left_conn,
"sender='org.freedesktop.DBus'",
&f->e);
test_assert_no_error (&f->e);
reply = test_main_context_call_and_wait (f->ctx, f->left_conn, call,
DBUS_TIMEOUT_USE_DEFAULT);
dbus_clear_message (&call);
g_test_message ("TestSuiteForkingEchoService initial reply OK");
dbus_clear_message (&reply);
/* Now monitor for exits: when that happens, start it up again.
* The goal here is to try to hit any race conditions in activation. */
f->activation_forking_counter = 0;
call = dbus_message_new_method_call (FORKING_ECHO_SERVICE,
ECHO_SERVICE_PATH,
ECHO_SERVICE_INTERFACE,
"Exit");
if (call == NULL || !dbus_connection_send (f->left_conn, call, NULL))
g_error ("OOM");
dbus_clear_message (&call);
while (f->activation_forking_counter <= 10)
test_main_context_iterate (f->ctx, TRUE);
dbus_connection_remove_filter (f->left_conn,
activation_forking_signal_filter, f);
}
/*
* Helper for test_system_signals: Receive Foo signals and add them to
* the held_messages queue.
*/
static DBusHandlerResult
foo_signal_filter (DBusConnection *connection,
DBusMessage *message,
void *user_data)
{
Fixture *f = user_data;
if (dbus_message_is_signal (message, ECHO_SERVICE_INTERFACE, "Foo"))
g_queue_push_tail (&f->held_messages, dbus_message_ref (message));
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
/*
* Assert that the system bus(-like) configuration allows services
* to emit signals, even if there is no service-specific configuration
* to allow it.
*
* Essentially equivalent to the old test/name-test/test-wait-for-echo.py.
*/
static void
test_system_signals (Fixture *f,
gconstpointer context G_GNUC_UNUSED)
{
DBusMessage *call = NULL;
DBusMessage *response = NULL;
g_test_bug ("18229");
if (f->skip)
return;
if (!dbus_connection_add_filter (f->left_conn, foo_signal_filter,
f, NULL))
g_error ("OOM");
dbus_bus_add_match (f->left_conn,
"interface='" ECHO_SERVICE_INTERFACE "'",
&f->e);
test_assert_no_error (&f->e);
call = dbus_message_new_method_call (ECHO_SERVICE,
ECHO_SERVICE_PATH,
ECHO_SERVICE_INTERFACE,
"EmitFoo");
if (call == NULL || !dbus_connection_send (f->left_conn, call, NULL))
g_error ("OOM");
dbus_clear_message (&call);
while (g_queue_get_length (&f->held_messages) < 1)
test_main_context_iterate (f->ctx, TRUE);
g_test_message ("got signal");
g_assert_cmpuint (g_queue_get_length (&f->held_messages), ==, 1);
response = g_queue_pop_head (&f->held_messages);
g_assert_cmpint (dbus_message_get_type (response), ==,
DBUS_MESSAGE_TYPE_SIGNAL);
g_assert_cmpstr (dbus_message_get_interface (response), ==,
ECHO_SERVICE_INTERFACE);
g_assert_cmpstr (dbus_message_get_path (response), ==,
ECHO_SERVICE_PATH);
g_assert_cmpstr (dbus_message_get_signature (response), ==, "d");
g_assert_cmpstr (dbus_message_get_member (response), ==, "Foo");
dbus_clear_message (&response);
dbus_connection_remove_filter (f->left_conn, foo_signal_filter, f);
}
#endif
static void
......@@ -2077,6 +2298,16 @@ static Config as_another_user_config = {
* real system bus does */
TEST_USER_ROOT, SPECIFY_ADDRESS
};
static Config tmp_session_config = {
NULL, 1, "valid-config-files/tmp-session.conf",
TEST_USER_ME, SPECIFY_ADDRESS
};
static Config nearly_system_config = {
NULL, 1, "valid-config-files-system/tmp-session-like-system.conf",
TEST_USER_ME, SPECIFY_ADDRESS
};
#endif
int
......@@ -2160,6 +2391,11 @@ main (int argc,
setup, test_fd_limit, teardown);
g_test_add ("/fd-limit/system", Fixture, &as_another_user_config,
setup, test_fd_limit, teardown);
g_test_add ("/activation/forking", Fixture, &tmp_session_config,
setup, test_activation_forking, teardown);
g_test_add ("/system-policy/allow-signals", Fixture, &nearly_system_config,
setup, test_system_signals, teardown);
#endif
ret = g_test_run ();
......
......@@ -37,7 +37,6 @@ AM_TESTS_ENVIRONMENT = \
export DBUS_TOP_BUILDDIR=@abs_top_builddir@; \
export DBUS_TOP_SRCDIR=@abs_top_srcdir@; \
export PATH="@abs_top_builddir@/bus:$$PATH"; \
export PYTHON=@PYTHON@; \
export DBUS_TEST_DATA=@abs_top_builddir@/test/data; \
export DBUS_TEST_DAEMON=@abs_top_builddir@/bus/dbus-daemon$(EXEEXT); \
export DBUS_TEST_DBUS_LAUNCH=@abs_top_builddir@/tools/dbus-launch$(EXEEXT); \
......@@ -60,7 +59,7 @@ TESTS += \
endif
endif
EXTRA_DIST=run-test.sh run-test-systemserver.sh test-wait-for-echo.py test-activation-forking.py
EXTRA_DIST=run-test.sh run-test-systemserver.sh
if DBUS_ENABLE_EMBEDDED_TESTS
......
......@@ -68,22 +68,8 @@ dbus_send_test () {
rm -f output.tmp
}
py_test () {
t="$1"
shift
if test "x$PYTHON" = "x:"; then
interpret_result 77 "$t" "(Python interpreter not found)"
else
e=0
echo "# running test $t"
$PYTHON "$DBUS_TOP_SRCDIR/test/name-test/$t" "$@" >&2 || e=$?
interpret_result "$e" "$t" "$@"
fi
}
test_num=1
# TAP syntax: we plan to run 2 tests
echo "1..2"
# TAP syntax: we plan to run 1 test
echo "1..1"
dbus_send_test test-expected-echo-fail 1 DBus.Error --print-reply --dest=org.freedesktop.DBus.TestSuiteEchoService /org/freedesktop/TestSuite org.freedesktop.TestSuite.Echo string:hi
py_test test-wait-for-echo.py
......@@ -53,22 +53,8 @@ c_test () {
interpret_result "$e" "$t" "$@"
}
py_test () {
t="$1"
shift
if test "x$PYTHON" = "x:"; then
interpret_result 77 "$t" "(Python interpreter not found)"
else
e=0
echo "# running test $t"
$PYTHON "$DBUS_TOP_SRCDIR/test/name-test/$t" "$@" >&2 || e=$?
interpret_result "$e" "$t" "$@"
fi
}
test_num=1
# TAP test plan: we will run 2 tests
echo "1..2"
# TAP test plan: we will run 1 test
echo "1..1"
py_test test-activation-forking.py
c_test test-autolaunch
#!/usr/bin/env python
import os,sys
try:
from gi.repository import GObject
import dbus
import dbus.mainloop.glib
except:
print("Failed import, aborting test")
sys.exit(0)
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
loop = GObject.MainLoop()
exitcode = 0
bus = dbus.SessionBus()
bus_iface = dbus.Interface(bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus'), 'org.freedesktop.DBus')
o = bus.get_object('org.freedesktop.DBus.TestSuiteForkingEchoService', '/org/freedesktop/TestSuite')
i = dbus.Interface(o, 'org.freedesktop.TestSuite')
# Start it up
reply = i.Echo("hello world")
print("TestSuiteForkingEchoService initial reply OK")
def ignore(*args, **kwargs):
pass
# Now monitor for exits, when that happens, start it up again.
# The goal here is to try to hit any race conditions in activation.
counter = 0
def on_forking_echo_owner_changed(name, old, new):
global counter
global o
global i
if counter > 10:
print("Activated 10 times OK, TestSuiteForkingEchoService pass")
loop.quit()
return
counter += 1
if new == '':
o = bus.get_object('org.freedesktop.DBus.TestSuiteForkingEchoService', '/org/freedesktop/TestSuite')
i = dbus.Interface(o, 'org.freedesktop.TestSuite')
i.Echo("counter %r" % counter)
i.Exit(reply_handler=ignore, error_handler=ignore)
bus_iface.connect_to_signal('NameOwnerChanged', on_forking_echo_owner_changed, arg0='org.freedesktop.DBus.TestSuiteForkingEchoService')
i.Exit(reply_handler=ignore, error_handler=ignore)
def check_counter():
if counter == 0:
print("Failed to get NameOwnerChanged for TestSuiteForkingEchoService")
sys.exit(1)
GObject.timeout_add(15000, check_counter)
loop.run()
sys.exit(0)
#!/usr/bin/env python
import os,sys
try:
import dbus
import dbus.mainloop.glib
from gi.repository import GObject
except:
print("Failed import, aborting test")
sys.exit(0)
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
loop = GObject.MainLoop()
exitcode = 0
def handle_noreceipt():
print("Failed to get signal")
global exitcode
exitcode = 1
loop.quit()
GObject.timeout_add(7000, handle_noreceipt)
bus = dbus.SessionBus()
def sighandler(*args, **kwargs):
print("got signal")
loop.quit()
bus.add_signal_receiver(sighandler, dbus_interface='org.freedesktop.TestSuite', signal_name='Foo')
o = bus.get_object('org.freedesktop.DBus.TestSuiteEchoService', '/org/freedesktop/TestSuite')
i = dbus.Interface(o, 'org.freedesktop.TestSuite')
def nullhandler(*args, **kwargs):
pass
i.EmitFoo(reply_handler=nullhandler, error_handler=nullhandler)
loop.run()
sys.exit(exitcode)
......@@ -143,9 +143,6 @@ case "$ci_distro" in
libglib2.0-dev \
libselinux1-dev \
libx11-dev \
python \
python-dbus \
python-gi \
valgrind \
wget \
xauth \
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment