/* * Copyright 2002-2008 Red Hat Inc. * Copyright 2011-2017 Collabora Ltd. * * SPDX-License-Identifier: MIT * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation files * (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include "test-utils.h" #include #include #if HAVE_LOCALE_H #include #endif #ifdef DBUS_UNIX # include #endif #include "dbus/dbus-message-internal.h" #include "dbus/dbus-test-tap.h" #ifdef DBUS_ENABLE_EMBEDDED_TESTS /* * Like strdup(), but crash on out-of-memory, and pass through NULL * unchanged (the "0" in the name is meant to be a mnemonic for this, * similar to g_strcmp0()). */ static char * strdup0_or_die (const char *str) { char *ret; if (str == NULL) return NULL; /* not an error */ ret = strdup (str); if (ret == NULL) _dbus_test_fatal ("Out of memory"); return ret; } #endif typedef struct { DBusLoop *loop; DBusConnection *connection; } CData; static dbus_bool_t add_watch (DBusWatch *watch, void *data) { CData *cd = data; return _dbus_loop_add_watch (cd->loop, watch); } static void remove_watch (DBusWatch *watch, void *data) { CData *cd = data; _dbus_loop_remove_watch (cd->loop, watch); } static void toggle_watch (DBusWatch *watch, void *data) { CData *cd = data; _dbus_loop_toggle_watch (cd->loop, watch); } static dbus_bool_t add_timeout (DBusTimeout *timeout, void *data) { CData *cd = data; return _dbus_loop_add_timeout (cd->loop, timeout); } static void remove_timeout (DBusTimeout *timeout, void *data) { CData *cd = data; _dbus_loop_remove_timeout (cd->loop, timeout); } static void dispatch_status_function (DBusConnection *connection, DBusDispatchStatus new_status, void *data) { DBusLoop *loop = data; if (new_status != DBUS_DISPATCH_COMPLETE) { while (!_dbus_loop_queue_dispatch (loop, connection)) _dbus_wait_for_memory (); } } static void cdata_free (void *data) { CData *cd = data; dbus_connection_unref (cd->connection); _dbus_loop_unref (cd->loop); dbus_free (cd); } static CData* cdata_new (DBusLoop *loop, DBusConnection *connection) { CData *cd; cd = dbus_new0 (CData, 1); if (cd == NULL) return NULL; cd->loop = loop; cd->connection = connection; dbus_connection_ref (cd->connection); _dbus_loop_ref (cd->loop); return cd; } dbus_bool_t test_connection_try_setup (TestMainContext *ctx, DBusConnection *connection) { DBusLoop *loop = ctx; CData *cd; cd = NULL; dbus_connection_set_dispatch_status_function (connection, dispatch_status_function, loop, NULL); /* ownership of cd taken */ cd = cdata_new (loop, connection); if (cd == NULL) goto nomem; if (!dbus_connection_set_watch_functions (connection, add_watch, remove_watch, toggle_watch, cd, cdata_free)) goto nomem; cd = cdata_new (loop, connection); if (cd == NULL) goto nomem; if (!dbus_connection_set_timeout_functions (connection, add_timeout, remove_timeout, NULL, cd, cdata_free)) goto nomem; /* ownership taken */ cd = NULL; if (dbus_connection_get_dispatch_status (connection) != DBUS_DISPATCH_COMPLETE) { if (!_dbus_loop_queue_dispatch (loop, connection)) goto nomem; } return TRUE; nomem: if (cd) cdata_free (cd); dbus_connection_set_dispatch_status_function (connection, NULL, NULL, NULL); dbus_connection_set_watch_functions (connection, NULL, NULL, NULL, NULL, NULL); dbus_connection_set_timeout_functions (connection, NULL, NULL, NULL, NULL, NULL); return FALSE; } static void die (const char *message) _DBUS_GNUC_NORETURN; static void die (const char *message) { printf ("Bail out! %s\n", message); fflush (stdout); exit (1); } void test_connection_setup (TestMainContext *ctx, DBusConnection *connection) { if (!test_connection_try_setup (ctx, connection)) die ("Not enough memory to set up connection"); } void test_connection_shutdown (TestMainContext *ctx, DBusConnection *connection) { if (!dbus_connection_set_watch_functions (connection, NULL, NULL, NULL, NULL, NULL)) die ("setting watch functions to NULL failed"); if (!dbus_connection_set_timeout_functions (connection, NULL, NULL, NULL, NULL, NULL)) die ("setting timeout functions to NULL failed"); dbus_connection_set_dispatch_status_function (connection, NULL, NULL, NULL); } typedef struct { DBusLoop *loop; DBusServer *server; } ServerData; static void serverdata_free (void *data) { ServerData *sd = data; dbus_server_unref (sd->server); _dbus_loop_unref (sd->loop); dbus_free (sd); } static ServerData* serverdata_new (DBusLoop *loop, DBusServer *server) { ServerData *sd; sd = dbus_new0 (ServerData, 1); if (sd == NULL) return NULL; sd->loop = loop; sd->server = server; dbus_server_ref (sd->server); _dbus_loop_ref (sd->loop); return sd; } static dbus_bool_t add_server_watch (DBusWatch *watch, void *data) { ServerData *context = data; return _dbus_loop_add_watch (context->loop, watch); } static void toggle_server_watch (DBusWatch *watch, void *data) { ServerData *context = data; _dbus_loop_toggle_watch (context->loop, watch); } static void remove_server_watch (DBusWatch *watch, void *data) { ServerData *context = data; _dbus_loop_remove_watch (context->loop, watch); } static dbus_bool_t add_server_timeout (DBusTimeout *timeout, void *data) { ServerData *context = data; return _dbus_loop_add_timeout (context->loop, timeout); } static void remove_server_timeout (DBusTimeout *timeout, void *data) { ServerData *context = data; _dbus_loop_remove_timeout (context->loop, timeout); } dbus_bool_t test_server_try_setup (TestMainContext *ctx, DBusServer *server) { DBusLoop *loop = ctx; ServerData *sd; sd = serverdata_new (loop, server); if (sd == NULL) goto nomem; if (!dbus_server_set_watch_functions (server, add_server_watch, remove_server_watch, toggle_server_watch, sd, serverdata_free)) { goto nomem; } sd = serverdata_new (loop, server); if (sd == NULL) goto nomem; if (!dbus_server_set_timeout_functions (server, add_server_timeout, remove_server_timeout, NULL, sd, serverdata_free)) { goto nomem; } return TRUE; nomem: if (sd) serverdata_free (sd); test_server_shutdown (loop, server); return FALSE; } void test_server_setup (TestMainContext *ctx, DBusServer *server) { if (!test_server_try_setup (ctx, server)) die ("Not enough memory to set up server"); } void test_server_shutdown (TestMainContext *ctx, DBusServer *server) { dbus_server_disconnect (server); if (!dbus_server_set_watch_functions (server, NULL, NULL, NULL, NULL, NULL)) die ("setting watch functions to NULL failed"); if (!dbus_server_set_timeout_functions (server, NULL, NULL, NULL, NULL, NULL)) die ("setting timeout functions to NULL failed"); } TestMainContext * test_main_context_get (void) { TestMainContext *ret = _dbus_loop_new (); if (ret == NULL) die ("Out of memory"); return ret; } TestMainContext * test_main_context_try_get (void) { return _dbus_loop_new (); } TestMainContext * test_main_context_ref (TestMainContext *ctx) { return _dbus_loop_ref (ctx); } void test_main_context_unref (TestMainContext *ctx) { _dbus_loop_unref (ctx); } void test_main_context_iterate (TestMainContext *ctx, dbus_bool_t may_block) { _dbus_loop_iterate (ctx, may_block); } void test_pending_call_store_reply (DBusPendingCall *pc, void *data) { DBusMessage **message_p = data; *message_p = dbus_pending_call_steal_reply (pc); _dbus_assert (*message_p != NULL); } #ifdef DBUS_ENABLE_EMBEDDED_TESTS /* * _dbus_test_main: * @argc: number of command-line arguments * @argv: array of @argc arguments * @n_tests: length of @tests * @tests: array of @n_tests tests * @flags: flags affecting all tests * @test_pre_hook: if not %NULL, called before each test * @test_post_hook: if not %NULL, called after each test * * Wrapper for dbus tests that do not use GLib. Processing of @tests * can be terminated early by an entry with @name = NULL, which is a * convenient way to put a trailing comma on every "real" test entry * without breaking compilation on pedantic C compilers. */ int _dbus_test_main (int argc, char **argv, size_t n_tests, const DBusTestCase *tests, DBusTestFlags flags, void (*test_pre_hook) (void), void (*test_post_hook) (void)) { char *test_data_dir; char *specific_test; size_t i; #ifdef DBUS_UNIX /* close any inherited fds so dbus-spawn's check for close-on-exec works */ _dbus_close_all (); #endif #if HAVE_SETLOCALE setlocale(LC_ALL, ""); #endif /* We can't assume that strings from _dbus_getenv() will remain valid * forever, because some tests call setenv(), which is allowed to * reallocate the entire environment block, and in Wine it seems that it * genuinely does; so we copy them. * * We can't use _dbus_strdup() here because the test might be checking * for memory leaks, so we don't want any libdbus allocations still * alive at the end; so we use strdup(), which is not in Standard C but * is available in both POSIX and Windows. */ if (argc > 1 && strcmp (argv[1], "--tap") != 0) test_data_dir = strdup0_or_die (argv[1]); else test_data_dir = strdup0_or_die (_dbus_getenv ("DBUS_TEST_DATA")); if (test_data_dir != NULL) _dbus_test_diag ("Test data in %s", test_data_dir); else if (flags & DBUS_TEST_FLAGS_REQUIRE_DATA) _dbus_test_fatal ("Must specify test data directory as argv[1] or " "in DBUS_TEST_DATA environment variable"); else _dbus_test_diag ("No test data!"); if (argc > 2) specific_test = strdup0_or_die (argv[2]); else specific_test = strdup0_or_die (_dbus_getenv ("DBUS_TEST_ONLY")); /* Some NSS modules like those for sssd and LDAP might allocate fds * on a one-per-process basis. Make sure those have already been * allocated before we enter the code under test, so that they don't * show up as having been "leaked" by the first module of code under * test that happens to do a NSS lookup. */ { DBusString username; dbus_uid_t ignored_uid = DBUS_UID_UNSET; _dbus_string_init_const (&username, "dbus-no-user-with-this-name"); /* We use a username that almost certainly doesn't exist, because * if we used something like root it might get handled early in the * NSS search order, before we get as far as asking sssd or LDAP. */ _dbus_parse_unix_user_from_config (&username, &ignored_uid); _dbus_test_check_memleaks ("initial nss query"); } for (i = 0; i < n_tests; i++) { long before, after; DBusInitialFDs *initial_fds = NULL; if (tests[i].name == NULL) break; if (n_tests > 1 && specific_test != NULL && strcmp (specific_test, tests[i].name) != 0) { _dbus_test_skip ("%s - Only intending to run %s", tests[i].name, specific_test); continue; } _dbus_test_diag ("Running test: %s", tests[i].name); _dbus_get_monotonic_time (&before, NULL); if (test_pre_hook) test_pre_hook (); if (flags & DBUS_TEST_FLAGS_CHECK_FD_LEAKS) initial_fds = _dbus_check_fdleaks_enter (); if (tests[i].func (test_data_dir)) _dbus_test_ok ("%s", tests[i].name); else _dbus_test_not_ok ("%s", tests[i].name); _dbus_get_monotonic_time (&after, NULL); _dbus_test_diag ("%s test took %ld seconds", tests[i].name, after - before); if (test_post_hook) test_post_hook (); if (flags & DBUS_TEST_FLAGS_CHECK_MEMORY_LEAKS) _dbus_test_check_memleaks (tests[i].name); if (flags & DBUS_TEST_FLAGS_CHECK_FD_LEAKS) _dbus_check_fdleaks_leave (initial_fds); } free (test_data_dir); free (specific_test); return _dbus_test_done_testing (); } #endif