From 286db5049ea2e9494747dfab595a97cd19c85452 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 7 May 2018 16:23:57 +0200 Subject: [PATCH 1/8] shared: add nm_auto_unref_gsource cleanup macro --- shared/nm-utils/nm-macros-internal.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/shared/nm-utils/nm-macros-internal.h b/shared/nm-utils/nm-macros-internal.h index bd375a35a..72c1f807a 100644 --- a/shared/nm-utils/nm-macros-internal.h +++ b/shared/nm-utils/nm-macros-internal.h @@ -153,6 +153,14 @@ _nm_auto_protect_errno (int *p_saved_errno) } #define NM_AUTO_PROTECT_ERRNO(errsv_saved) nm_auto(_nm_auto_protect_errno) _nm_unused const int errsv_saved = (errno) +static inline void +_nm_auto_unref_gsource (GSource **ptr) +{ + if (*ptr) + g_source_unref (g_steal_pointer (ptr)); +} +#define nm_auto_unref_gsource nm_auto(_nm_auto_unref_gsource) + /*****************************************************************************/ /* http://stackoverflow.com/a/11172679 */ From 34122c874e67f29aaf06e06999288a7ef92aeece Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 7 May 2018 16:24:30 +0200 Subject: [PATCH 2/8] shared: add NM_PID_T_INVAL macro for invalid PIDs --- shared/nm-utils/nm-macros-internal.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shared/nm-utils/nm-macros-internal.h b/shared/nm-utils/nm-macros-internal.h index 72c1f807a..908b25fdb 100644 --- a/shared/nm-utils/nm-macros-internal.h +++ b/shared/nm-utils/nm-macros-internal.h @@ -1375,4 +1375,6 @@ nm_close (int fd) return r; } +#define NM_PID_T_INVAL ((pid_t) -1) + #endif /* __NM_MACROS_INTERNAL_H__ */ From 11fedad5445d8688740563a0fb9fd1162ec9e118 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 7 May 2018 16:25:33 +0200 Subject: [PATCH 3/8] tests: minor code cleanup of nmtst_main_loop_run() --- shared/nm-utils/nm-test-utils.h | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h index 6dd337b20..efbe6c9ae 100644 --- a/shared/nm-utils/nm-test-utils.h +++ b/shared/nm-utils/nm-test-utils.h @@ -921,33 +921,26 @@ _nmtst_main_loop_run_timeout (gpointer user_data) { GMainLoop **p_loop = user_data; - g_assert (p_loop); - g_assert (*p_loop); - - g_main_loop_quit (*p_loop); - *p_loop = NULL; - + g_assert (p_loop && *p_loop); + g_main_loop_quit (g_steal_pointer (p_loop)); return G_SOURCE_REMOVE; } static inline gboolean nmtst_main_loop_run (GMainLoop *loop, guint timeout_ms) { - GSource *source = NULL; - guint id = 0; + nm_auto_unref_gsource GSource *source = NULL; GMainLoop *loopx = loop; if (timeout_ms > 0) { source = g_timeout_source_new (timeout_ms); g_source_set_callback (source, _nmtst_main_loop_run_timeout, &loopx, NULL); - id = g_source_attach (source, g_main_loop_get_context (loop)); - g_assert (id); - g_source_unref (source); + g_source_attach (source, g_main_loop_get_context (loop)); } g_main_loop_run (loop); - if (source && loopx) + if (source) g_source_destroy (source); /* if the timeout was reached, return FALSE. */ From 3934a2c392e8d6c600b211afee871989a338a1fe Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Sun, 6 May 2018 07:37:28 +0200 Subject: [PATCH 4/8] build: set LD_LIBRARY_PATH and GI_TYPELIB_PATH variables in run-nm-test.sh With autotools, we use libtool so that the right libraries are automatically found. Still, we won't find the right GI typelib. Add a mechanism so that when make/meson invokes the run-nm-test.sh runner, it passes the build-root directory. Also, try to autodetect when invoked manually. --- configure.ac | 2 +- meson.build | 4 +--- tools/run-nm-test.sh | 29 +++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index 7ac11e112..8c0014316 100644 --- a/configure.ac +++ b/configure.ac @@ -1228,7 +1228,7 @@ else with_valgrind_suppressions='$(top_srcdir)/valgrind.suppressions' fi fi -AC_SUBST(NM_LOG_COMPILER, 'LOG_COMPILER = "$(top_srcdir)/tools/run-nm-test.sh" --called-from-make "$(LIBTOOL)" "$(with_valgrind)" "'"$with_valgrind_suppressions"'" --launch-dbus=auto') +AC_SUBST(NM_LOG_COMPILER, 'LOG_COMPILER = "$(top_srcdir)/tools/run-nm-test.sh" --called-from-make "$(abs_top_builddir)" "$(LIBTOOL)" "$(with_valgrind)" "'"$with_valgrind_suppressions"'" --launch-dbus=auto') AM_PATH_PYTHON([], [], [PYTHON=python]) AC_SUBST(PYTHON, [$PYTHON]) diff --git a/meson.build b/meson.build index dc1524aa0..4564c5632 100644 --- a/meson.build +++ b/meson.build @@ -741,15 +741,13 @@ endif test_args = [ '--called-from-make', + meson.build_root(), '', enable_valgrind ? valgrind.path() : '', enable_valgrind ? valgrind_suppressions_path : '', '--launch-dbus=auto' ] -# FIXME -#AC_SUBST(NM_LOG_COMPILER, 'LOG_COMPILER = "$(top_srcdir)/tools/run-nm-test.sh" --called-from-make "$(LIBTOOL)" "$(with_valgrind)" "'"$with_valgrind_suppressions"'" --launch-dbus=auto') - py3 = import('python3') python = py3.find_python() diff --git a/tools/run-nm-test.sh b/tools/run-nm-test.sh index 7f3053ce1..29ac7cd36 100755 --- a/tools/run-nm-test.sh +++ b/tools/run-nm-test.sh @@ -34,7 +34,11 @@ else CALLED_FROM_MAKE=0 fi +BUILDDIR= + if [ "$CALLED_FROM_MAKE" == 1 ]; then + BUILDDIR="$1" + shift if [ -n "$1" ]; then NMTST_LIBTOOL=($1 --mode=execute); else @@ -144,6 +148,7 @@ else ;; esac done + # we support calling the script directly. In this case, # only pass the path to the test to run. if test -z "${TEST+x}"; then @@ -153,6 +158,20 @@ else NMTST_SUPPRESSIONS="$SCRIPT_PATH/../valgrind.suppressions" fi + if [[ -z "$NMTST_BUILDDIR" ]]; then + if [[ "${NMTST_BUILDDIR-x}" == x ]]; then + # autodetect + BUILDDIR="$(readlink -f "$TEST")" + while [[ -n "$BUILDDIR" ]]; do + BUILDDIR="$(dirname "$BUILDDIR")" + [[ "$BUILDDIR" == / ]] && BUILDDIR= + [[ -z "$BUILDDIR" ]] && break + [[ -e "$BUILDDIR/libnm/.libs/libnm.so" ]] && break + [[ -e "$BUILDDIR/libnm/libnm.so" ]] && break + done + fi + fi + fi if [ "$NMTST_SET_DEBUG" == 1 -a -z "${NMTST_DEBUG+x}" ]; then @@ -198,6 +217,16 @@ fi [ -x "$TEST" ] || die "Cannot execute test \"$TEST\"" +if [[ -n "$BUILDDIR" ]]; then + if [[ -d "$BUILDDIR/libnm" ]]; then + export GI_TYPELIB_PATH="$BUILDDIR/libnm/${GI_TYPELIB_PATH:+:$GI_TYPELIB_PATH}" + if [[ -d "$BUILDDIR/libnm/.libs" ]]; then + export LD_LIBRARY_PATH="$BUILDDIR/libnm/.libs${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + else + export LD_LIBRARY_PATH="$BUILDDIR/libnm${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + fi + fi +fi if ! _is_true "$NMTST_USE_VALGRIND" 0; then "${NMTST_DBUS_RUN_SESSION[@]}" \ From 9628aabc2fbf9315eb9c87478da1588c72d8b44c Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Sun, 6 May 2018 08:51:26 +0200 Subject: [PATCH 5/8] tests: use libnm via pygobject in tools/test-networkmanager-service.py tools/test-networkmanager-service.py is our NetworkManager stub server. NetworkManager uses libnm(-core) heavily, for example to decide whether a connection verifies (nm_connection_verify()) and for normalizing connections (nm_connection_normalize()). If the stub server wants to mimic NetworkManager, it also must use these function. Luckily, we already can do so, by loading libnm using python GObject introspection. We already correctly set GI_TYPELIB_PATH search path, so that the correct libnm is loaded -- provided that we build with introspection enabled. We still need to gracefully fail, if starting the stub server fails. That requries some extra effort. If the stub server notices that something is missing, it shall exit with status 77. That will cause the tests to g_test_skip(). --- Makefile.am | 34 ++++- libnm-glib/tests/test-nm-client.c | 21 ++- .../tests/test-remote-settings-client.c | 20 ++- libnm/tests/test-nm-client.c | 36 ++++- libnm/tests/test-remote-settings-client.c | 21 +++ libnm/tests/test-secret-agent.c | 23 +++ shared/nm-test-libnm-utils.h | 16 +- shared/nm-test-utils-impl.c | 144 +++++++++++++++--- tools/test-networkmanager-service.py | 12 +- 9 files changed, 285 insertions(+), 42 deletions(-) diff --git a/Makefile.am b/Makefile.am index 638055c91..7f5c328f2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -948,8 +948,11 @@ EXTRA_DIST += \ libnm/libnm.pc.in \ libnm/libnm.ver +libnm_NM_1_0_typelib = if HAVE_INTROSPECTION +libnm_NM_1_0_typelib += libnm/NM-1.0.typelib + libnm/NM-1.0.gir: libnm/libnm.la libnm_NM_1_0_gir_INCLUDES = Gio-2.0 libnm_NM_1_0_gir_PACKAGES = gio-2.0 @@ -1059,12 +1062,20 @@ EXTRA_DIST += \ ############################################################################### libnm_tests_programs = \ - libnm/tests/test-general \ + libnm/tests/test-general + +check_programs += $(libnm_tests_programs) + +libnm_tests_programs_req_introspection = \ libnm/tests/test-nm-client \ libnm/tests/test-remote-settings-client \ libnm/tests/test-secret-agent -check_programs += $(libnm_tests_programs) +if HAVE_INTROSPECTION +check_programs += $(libnm_tests_programs_req_introspection) +else +check_programs_norun += $(libnm_tests_programs_req_introspection) +endif libnm_tests_cppflags = \ $(dflt_cppflags_libnm_core) \ @@ -1122,6 +1133,12 @@ $(libnm_tests_test_nm_client_OBJECTS): $(libnm_core_lib_h_pub_mkenums) $(libnm_tests_test_remote_settings_client_OBJECTS): $(libnm_core_lib_h_pub_mkenums) $(libnm_tests_test_secret_agent_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +# tools/test-networkmanager-service.py uses libnm's typelib. Ensure it +# is built first. +$(libnm_tests_test_nm_client_OBJECTS): $(libnm_NM_1_0_typelib) +$(libnm_tests_test_remote_settings_client_OBJECTS): $(libnm_NM_1_0_typelib) +$(libnm_tests_test_secret_agent_OBJECTS): $(libnm_NM_1_0_typelib) + ############################################################################### # just test, that we can build "nm-vpn-plugin-utils.c" @@ -4431,10 +4448,16 @@ libnm_glib_tests_cppflags = \ $(GLIB_CFLAGS) \ $(DBUS_CFLAGS) -check_programs += \ +libnm_glib_tests_programs_req_introspection = \ libnm-glib/tests/test-nm-client \ libnm-glib/tests/test-remote-settings-client +if HAVE_INTROSPECTION +check_programs += $(libnm_glib_tests_programs_req_introspection) +else +check_programs_norun += $(libnm_glib_tests_programs_req_introspection) +endif + libnm_glib_tests_test_nm_client_CPPFLAGS = $(libnm_glib_tests_cppflags) libnm_glib_tests_test_nm_client_SOURCES = \ @@ -4466,6 +4489,11 @@ libnm_glib_tests_test_remote_settings_client_LDADD = \ $(libnm_glib_tests_test_remote_settings_client_OBJECTS): $(libnm_core_lib_h_pub_mkenums) +# tools/test-networkmanager-service.py uses libnm's typelib. Ensure it +# is built first. +$(libnm_glib_tests_test_nm_client_OBJECTS): $(libnm_NM_1_0_typelib) +$(libnm_glib_tests_test_remote_settings_client_OBJECTS): $(libnm_NM_1_0_typelib) + endif EXTRA_DIST += \ diff --git a/libnm-glib/tests/test-nm-client.c b/libnm-glib/tests/test-nm-client.c index 943252033..df8572153 100644 --- a/libnm-glib/tests/test-nm-client.c +++ b/libnm-glib/tests/test-nm-client.c @@ -155,6 +155,9 @@ test_device_added (void) DeviceAddedInfo info = { loop, FALSE, FALSE, 0, 0 }; sinfo = nmtstc_service_init (); + if (!nmtstc_service_available (sinfo)) + return; + client = nmtstc_nm_client_new (); devices = nm_client_get_devices (client); @@ -312,6 +315,9 @@ test_wifi_ap_added_removed (void) char *expected_path = NULL; sinfo = nmtstc_service_init (); + if (!nmtstc_service_available (sinfo)) + return; + client = nmtstc_nm_client_new (); /*************************************/ @@ -535,6 +541,9 @@ test_wimax_nsp_added_removed (void) char *expected_path = NULL; sinfo = nmtstc_service_init (); + if (!nmtstc_service_available (sinfo)) + return; + client = nmtstc_nm_client_new (); /*************************************/ @@ -720,6 +729,9 @@ test_devices_array (void) GVariant *ret; sinfo = nmtstc_service_init (); + if (!nmtstc_service_available (sinfo)) + return; + client = nmtstc_nm_client_new (); /*************************************/ @@ -820,7 +832,8 @@ manager_running_changed (GObject *client, static void test_client_manager_running (void) { - NMClient *client1, *client2; + gs_unref_object NMClient *client1 = NULL; + gs_unref_object NMClient *client2 = NULL; guint quit_id; int running_changed = 0; GError *error = NULL; @@ -842,6 +855,9 @@ test_client_manager_running (void) /* Now start the test service. */ sinfo = nmtstc_service_init (); + if (!nmtstc_service_available (sinfo)) + return; + client2 = nmtstc_nm_client_new (); /* client2 should know that NM is running, but the previously-created @@ -868,9 +884,6 @@ test_client_manager_running (void) g_assert_cmpint (running_changed, ==, 2); g_assert (!nm_client_get_manager_running (client1)); g_source_remove (quit_id); - - g_object_unref (client1); - g_object_unref (client2); } /*****************************************************************************/ diff --git a/libnm-glib/tests/test-remote-settings-client.c b/libnm-glib/tests/test-remote-settings-client.c index 0f3492981..031e83f81 100644 --- a/libnm-glib/tests/test-remote-settings-client.c +++ b/libnm-glib/tests/test-remote-settings-client.c @@ -70,6 +70,9 @@ test_add_connection (void) time_t start, now; gboolean done = FALSE; + if (!nmtstc_service_available (sinfo)) + return; + connection = nm_connection_new (); s_con = (NMSettingConnection *) nm_setting_connection_new (); @@ -145,6 +148,9 @@ test_make_invisible (void) gboolean done = FALSE, has_settings = FALSE; char *path; + if (!nmtstc_service_available (sinfo)) + return; + g_assert (remote != NULL); /* Listen for the remove event when the connection becomes invisible */ @@ -212,6 +218,9 @@ test_make_visible (void) char *path; NMRemoteConnection *new = NULL; + if (!nmtstc_service_available (sinfo)) + return; + g_assert (remote != NULL); /* Wait for the new-connection signal when the connection is visible again */ @@ -292,6 +301,9 @@ test_remove_connection (void) gboolean done = FALSE; char *path; + if (!nmtstc_service_available (sinfo)) + return; + /* Find a connection to delete */ list = nm_remote_settings_list_connections (settings); g_assert_cmpint (g_slist_length (list), >, 0); @@ -360,11 +372,14 @@ settings_service_running_changed (GObject *client, static void test_service_running (void) { - NMRemoteSettings *settings2; + gs_unref_object NMRemoteSettings *settings2 = NULL; guint quit_id; int running_changed = 0; gboolean running; + if (!nmtstc_service_available (sinfo)) + return; + loop = g_main_loop_new (NULL, FALSE); g_object_get (G_OBJECT (settings), @@ -403,6 +418,7 @@ test_service_running (void) /* Now restart it */ sinfo = nmtstc_service_init (); + g_assert (nmtstc_service_available (sinfo)); quit_id = g_timeout_add_seconds (5, loop_quit, loop); g_main_loop_run (loop); @@ -413,8 +429,6 @@ test_service_running (void) NM_REMOTE_SETTINGS_SERVICE_RUNNING, &running, NULL); g_assert (running == TRUE); - - g_object_unref (settings2); } /*****************************************************************************/ diff --git a/libnm/tests/test-nm-client.c b/libnm/tests/test-nm-client.c index b40e67dd6..21272e4d4 100644 --- a/libnm/tests/test-nm-client.c +++ b/libnm/tests/test-nm-client.c @@ -70,6 +70,9 @@ test_device_added (void) GError *error = NULL; sinfo = nmtstc_service_init (); + if (!nmtstc_service_available (sinfo)) + return; + client = nm_client_new (NULL, &error); g_assert_no_error (error); @@ -163,6 +166,9 @@ test_device_added_signal_after_init (void) GError *error = NULL; sinfo = nmtstc_service_init (); + if (!nmtstc_service_available (sinfo)) + return; + client = nm_client_new (NULL, &error); g_assert_no_error (error); @@ -311,6 +317,9 @@ test_wifi_ap_added_removed (void) char *expected_path = NULL; sinfo = nmtstc_service_init (); + if (!nmtstc_service_available (sinfo)) + return; + client = nm_client_new (NULL, &error); g_assert_no_error (error); @@ -510,6 +519,9 @@ test_wimax_nsp_added_removed (void) char *expected_path = NULL; sinfo = nmtstc_service_init (); + if (!nmtstc_service_available (sinfo)) + return; + client = nm_client_new (NULL, &error); g_assert_no_error (error); @@ -686,6 +698,8 @@ test_devices_array (void) GVariant *ret; sinfo = nmtstc_service_init (); + if (!nmtstc_service_available (sinfo)) + return; /* Make sure that we test the async codepath in at least one test... */ nm_client_new_async (NULL, new_client_cb, &client); @@ -778,7 +792,8 @@ nm_running_changed (GObject *client, static void test_client_nm_running (void) { - NMClient *client1, *client2; + gs_unref_object NMClient *client1 = NULL; + gs_unref_object NMClient *client2 = NULL; guint quit_id; int running_changed = 0; GError *error = NULL; @@ -801,6 +816,9 @@ test_client_nm_running (void) /* Now start the test service. */ sinfo = nmtstc_service_init (); + if (!nmtstc_service_available (sinfo)) + return; + client2 = nm_client_new (NULL, &error); g_assert_no_error (error); @@ -828,9 +846,6 @@ test_client_nm_running (void) g_assert_cmpint (running_changed, ==, 2); g_assert (!nm_client_get_nm_running (client1)); g_source_remove (quit_id); - - g_object_unref (client1); - g_object_unref (client2); } typedef struct { @@ -934,6 +949,9 @@ test_active_connections (void) GError *error = NULL; sinfo = nmtstc_service_init (); + if (!nmtstc_service_available (sinfo)) + return; + client = nm_client_new (NULL, &error); g_assert_no_error (error); @@ -1063,6 +1081,9 @@ test_activate_virtual (void) GError *error = NULL; sinfo = nmtstc_service_init (); + if (!nmtstc_service_available (sinfo)) + return; + client = nm_client_new (NULL, &error); g_assert_no_error (error); @@ -1138,6 +1159,9 @@ test_activate_failed (void) GError *error = NULL; sinfo = nmtstc_service_init (); + if (!nmtstc_service_available (sinfo)) + return; + client = nm_client_new (NULL, &error); g_assert_no_error (error); @@ -1172,6 +1196,9 @@ test_device_connection_compatibility (void) const char *hw_addr2 = "52:54:00:ab:db:24"; sinfo = nmtstc_service_init (); + if (!nmtstc_service_available (sinfo)) + return; + client = nm_client_new (NULL, &error); g_assert_no_error (error); @@ -1593,4 +1620,3 @@ main (int argc, char **argv) return g_test_run (); } - diff --git a/libnm/tests/test-remote-settings-client.c b/libnm/tests/test-remote-settings-client.c index f6c37e919..45d60c069 100644 --- a/libnm/tests/test-remote-settings-client.c +++ b/libnm/tests/test-remote-settings-client.c @@ -63,6 +63,9 @@ test_add_connection (void) time_t start, now; gboolean done = FALSE; + if (!nmtstc_service_available (sinfo)) + return; + connection = nmtst_create_minimal_connection (TEST_CON_ID, NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); nm_client_add_connection_async (client, @@ -137,6 +140,9 @@ test_make_invisible (void) gboolean has_settings = FALSE; char *path; + if (!nmtstc_service_available (sinfo)) + return; + g_assert (remote != NULL); /* Listen for the remove event when the connection becomes invisible */ @@ -215,6 +221,9 @@ test_make_visible (void) char *path; NMRemoteConnection *new = NULL; + if (!nmtstc_service_available (sinfo)) + return; + g_assert (remote != NULL); /* Wait for the new-connection signal when the connection is visible again */ @@ -304,6 +313,9 @@ test_remove_connection (void) gboolean done = FALSE; char *path; + if (!nmtstc_service_available (sinfo)) + return; + /* Find a connection to delete */ conns = nm_client_get_connections (client); g_assert_cmpint (conns->len, >, 0); @@ -384,6 +396,9 @@ test_add_remove_connection (void) time_t start, now; gboolean done = FALSE; + if (!nmtstc_service_available (sinfo)) + return; + /* This will cause the test server to immediately delete the connection * after creating it. */ @@ -437,6 +452,9 @@ test_add_bad_connection (void) time_t start, now; gboolean done = FALSE; + if (!nmtstc_service_available (sinfo)) + return; + /* The test daemon doesn't support bond connections */ connection = nmtst_create_minimal_connection ("bad connection test", NULL, NM_SETTING_BOND_SETTING_NAME, NULL); @@ -480,6 +498,9 @@ test_save_hostname (void) gboolean done = FALSE; GError *error = NULL; + if (!nmtstc_service_available (sinfo)) + return; + /* test-networkmanager-service.py requires the hostname to contain a '.' */ nm_client_save_hostname (client, "foo", NULL, &error); g_assert_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_HOSTNAME); diff --git a/libnm/tests/test-secret-agent.c b/libnm/tests/test-secret-agent.c index 911ef4488..746a9e643 100644 --- a/libnm/tests/test-secret-agent.c +++ b/libnm/tests/test-secret-agent.c @@ -247,6 +247,9 @@ test_setup (TestSecretAgentData *sadata, gconstpointer test_data) GError *error = NULL; sadata->sinfo = nmtstc_service_init (); + if (!sadata->sinfo) + return; + sadata->client = nm_client_new (NULL, &error); g_assert_no_error (error); @@ -311,6 +314,9 @@ test_cleanup (TestSecretAgentData *sadata, gconstpointer test_data) GVariant *ret; GError *error = NULL; + if (!sadata->sinfo) + return; + if (sadata->agent) { if (nm_secret_agent_old_get_registered (sadata->agent)) { nm_secret_agent_old_unregister (sadata->agent, NULL, &error); @@ -360,6 +366,9 @@ connection_activated_none_cb (GObject *c, static void test_secret_agent_none (TestSecretAgentData *sadata, gconstpointer test_data) { + if (!nmtstc_service_available (sadata->sinfo)) + return; + nm_client_activate_connection_async (sadata->client, sadata->connection, sadata->device, @@ -405,6 +414,9 @@ connection_activated_no_secrets_cb (GObject *c, static void test_secret_agent_no_secrets (TestSecretAgentData *sadata, gconstpointer test_data) { + if (!nmtstc_service_available (sadata->sinfo)) + return; + g_signal_connect (sadata->agent, "secret-requested", G_CALLBACK (secrets_requested_no_secrets_cb), sadata); @@ -456,6 +468,9 @@ secrets_requested_cancel_cb (TestSecretAgent *agent, static void test_secret_agent_cancel (TestSecretAgentData *sadata, gconstpointer test_data) { + if (!nmtstc_service_available (sadata->sinfo)) + return; + g_signal_connect (sadata->agent, "secret-requested", G_CALLBACK (secrets_requested_cancel_cb), sadata); @@ -510,6 +525,9 @@ secrets_requested_good_cb (TestSecretAgent *agent, static void test_secret_agent_good (TestSecretAgentData *sadata, gconstpointer test_data) { + if (!nmtstc_service_available (sadata->sinfo)) + return; + g_signal_connect (sadata->agent, "secret-requested", G_CALLBACK (secrets_requested_good_cb), sadata); @@ -582,6 +600,9 @@ test_secret_agent_auto_register (void) GError *error = NULL; sinfo = nmtstc_service_init (); + if (!nmtstc_service_available (sinfo)) + return; + loop = g_main_loop_new (NULL, FALSE); agent = test_secret_agent_new (); @@ -609,6 +630,8 @@ test_secret_agent_auto_register (void) /* Restart test service */ sinfo = nmtstc_service_init (); + g_assert (nmtstc_service_available (sinfo)); + g_main_loop_run (loop); g_assert (nm_secret_agent_old_get_registered (agent)); diff --git a/shared/nm-test-libnm-utils.h b/shared/nm-test-libnm-utils.h index c4731a520..20a15e5f8 100644 --- a/shared/nm-test-libnm-utils.h +++ b/shared/nm-test-libnm-utils.h @@ -42,18 +42,22 @@ typedef struct { NMTstcServiceInfo *nmtstc_service_init (void); void nmtstc_service_cleanup (NMTstcServiceInfo *info); +NMTstcServiceInfo *nmtstc_service_available (NMTstcServiceInfo *info); static inline void _nmtstc_auto_service_cleanup (NMTstcServiceInfo **info) { - if (info && *info) { - nmtstc_service_cleanup (*info); - *info = NULL; - } + nmtstc_service_cleanup (g_steal_pointer (info)); } - #define NMTSTC_SERVICE_INFO_SETUP(sinfo) \ NM_PRAGMA_WARNING_DISABLE ("-Wunused-variable") \ - __attribute__ ((cleanup(_nmtstc_auto_service_cleanup))) NMTstcServiceInfo *sinfo = nmtstc_service_init (); \ + __attribute__ ((cleanup(_nmtstc_auto_service_cleanup))) NMTstcServiceInfo *sinfo = ({ \ + NMTstcServiceInfo *_sinfo; \ + \ + _sinfo = nmtstc_service_init (); \ + if (!nmtstc_service_available (_sinfo)) \ + return; \ + _sinfo; \ + }); \ NM_PRAGMA_WARNING_REENABLE /*****************************************************************************/ diff --git a/shared/nm-test-utils-impl.c b/shared/nm-test-utils-impl.c index 998d792a2..6d6e54c17 100644 --- a/shared/nm-test-utils-impl.c +++ b/shared/nm-test-utils-impl.c @@ -21,6 +21,7 @@ #include "nm-default.h" #include +#include #include "NetworkManager.h" #include "nm-dbus-compat.h" @@ -72,13 +73,60 @@ _libdbus_create_proxy_test (DBusGConnection *bus) } #endif +typedef struct { + GMainLoop *mainloop; + GDBusConnection *bus; + int exit_code; + bool exited:1; + bool name_found:1; +} ServiceInitWaitData; + +static gboolean +_service_init_wait_probe_name (gpointer user_data) +{ + ServiceInitWaitData *data = user_data; + + if (!name_exists (data->bus, "org.freedesktop.NetworkManager")) + return G_SOURCE_CONTINUE; + + data->name_found = TRUE; + g_main_loop_quit (data->mainloop); + return G_SOURCE_REMOVE; +} + +static void +_service_init_wait_child_wait (GPid pid, + gint status, + gpointer user_data) +{ + ServiceInitWaitData *data = user_data; + + data->exited = TRUE; + data->exit_code = status; + g_main_loop_quit (data->mainloop); +} + +NMTstcServiceInfo * +nmtstc_service_available (NMTstcServiceInfo *info) +{ + gs_free char *m = NULL; + + if (info) + return info; + + /* This happens, when test-networkmanager-service.py exits with 77 status + * code. */ + m = g_strdup_printf ("missing dependency for running NetworkManager stub service %s", TEST_NM_SERVICE); + g_test_skip (m); + return NULL; +} + NMTstcServiceInfo * nmtstc_service_init (void) { NMTstcServiceInfo *info; const char *args[] = { TEST_NM_PYTHON, TEST_NM_SERVICE, NULL }; GError *error = NULL; - int i; info = g_malloc0 (sizeof (*info)); @@ -90,18 +138,55 @@ nmtstc_service_init (void) * make sure the service exits if the test program crashes. */ g_spawn_async_with_pipes (NULL, (char **) args, NULL, - G_SPAWN_SEARCH_PATH, + G_SPAWN_SEARCH_PATH + | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &info->pid, &info->keepalive_fd, NULL, NULL, &error); g_assert_no_error (error); - /* Wait until the service is registered on the bus */ - for (i = 1000; i > 0; i--) { - if (name_exists (info->bus, "org.freedesktop.NetworkManager")) - break; - g_usleep (G_USEC_PER_SEC / 50); + { + nm_auto_unref_gsource GSource *timeout_source = NULL; + nm_auto_unref_gsource GSource *child_source = NULL; + GMainContext *context = g_main_context_new (); + ServiceInitWaitData data = { + .bus = info->bus, + .mainloop = g_main_loop_new (context, FALSE), + }; + gboolean had_timeout; + + timeout_source = g_timeout_source_new (50); + g_source_set_callback (timeout_source, _service_init_wait_probe_name, &data, NULL); + g_source_attach (timeout_source, context); + + child_source = g_child_watch_source_new (info->pid); + g_source_set_callback (child_source, (GSourceFunc)(void (*) (void)) _service_init_wait_child_wait, &data, NULL); + g_source_attach (child_source, context); + + had_timeout = !nmtst_main_loop_run (data.mainloop, 3000); + + g_source_destroy (timeout_source); + g_source_destroy (child_source); + g_main_loop_unref (data.mainloop); + g_main_context_unref (context); + + if (had_timeout) + g_error ("test service %s did not start in time", TEST_NM_SERVICE); + if (!data.name_found) { + g_assert (data.exited); + info->pid = NM_PID_T_INVAL; + nmtstc_service_cleanup (info); + + if ( WIFEXITED (data.exit_code) + && WEXITSTATUS (data.exit_code) == 77) { + /* If the stub service exited with status 77 it means that it decided + * that it cannot conduct the tests and the test should be (gracefully) + * skip. The likely reason for that, is that libnm is not available + * via pygobject. */ + return NULL; + } + g_error ("test service %s exited with error code %d", TEST_NM_SERVICE, data.exit_code); + } } - g_assert (i > 0); /* Grab a proxy to our fake NM service to trigger tests */ info->proxy = g_dbus_proxy_new_sync (info->bus, @@ -126,26 +211,45 @@ nmtstc_service_init (void) void nmtstc_service_cleanup (NMTstcServiceInfo *info) { - int i; + int ret; + gint64 t; + int status; - g_object_unref (info->proxy); - kill (info->pid, SIGTERM); + if (!info) + return; - /* Wait until the bus notices the service is gone */ - for (i = 100; i > 0; i--) { - if (!name_exists (info->bus, "org.freedesktop.NetworkManager")) - break; - g_usleep (G_USEC_PER_SEC / 50); - } - g_assert (i > 0); + nm_close (nm_steal_fd (&info->keepalive_fd)); - g_object_unref (info->bus); - nm_close (info->keepalive_fd); + g_clear_object (&info->proxy); #if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB g_clear_pointer (&info->libdbus.bus, dbus_g_connection_unref); #endif + if (info->pid != NM_PID_T_INVAL) { + kill (info->pid, SIGTERM); + + t = g_get_monotonic_time (); +again_wait: + ret = waitpid (info->pid, &status, WNOHANG); + if (ret == 0) { + if (t + 2000000 < g_get_monotonic_time ()) { + kill (info->pid, SIGKILL); + g_error ("child process %lld did not exit within timeout", (long long) info->pid); + } + g_usleep (G_USEC_PER_SEC / 50); + goto again_wait; + } + if (ret == -1 && errno == EINTR) + goto again_wait; + + g_assert (ret == info->pid); + } + + g_assert (!name_exists (info->bus, "org.freedesktop.NetworkManager")); + + g_clear_object (&info->bus); + memset (info, 0, sizeof (*info)); g_free (info); } diff --git a/tools/test-networkmanager-service.py b/tools/test-networkmanager-service.py index 68097778e..17bd7e28c 100755 --- a/tools/test-networkmanager-service.py +++ b/tools/test-networkmanager-service.py @@ -3,8 +3,18 @@ from __future__ import print_function -from gi.repository import GLib import sys + +import gi +from gi.repository import GLib + +try: + gi.require_version('NM', '1.0') + from gi.repository import NM +except Exception as e: + print("Cannot load gi.NM: %s" % (str(e))) + sys.exit(77) + import dbus import dbus.service import dbus.mainloop.glib From 5e6b0382ad672254bdabf1997ef36bdc4fa0e11f Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 7 May 2018 17:10:07 +0200 Subject: [PATCH 6/8] build: let `make check` re-generate clients/common/settings-doc.h.in with NM_TEST_REGENERATE=1 When building with --disable-introspection, we re-use the pre-generated clients/common/settings-doc.h.in file. When building with --enable-introspection, we generate clients/common/settings-doc.h, and let `make check` verify that the generated file is identical to what we would generate. The common case where the generated file differ, is when code changed, in this case, the developer is advised to update settings-doc.h.in. Interpret environment variable NM_TEST_REGENERATE=1 to do this automatically during `make check`. This will be useful, as there might be several tests that compare a generated file with a file from version control. NM_TEST_REGENERATE=1 will be the general way to re-generate all these files. $ NM_TEST_REGENERATE=1 make check --- Makefile.am | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Makefile.am b/Makefile.am index 7f5c328f2..a7152bd07 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3424,10 +3424,14 @@ $(clients_common_settings_doc_h): clients/common/settings-docs.xsl libnm/nm-prop $(AM_V_GEN) $(XSLTPROC) --output $@ $< $(word 2,$^) DISTCLEANFILES += $(clients_common_settings_doc_h) check-local-settings-docs: $(clients_common_settings_doc_h) - @if test -z "$$NMTST_NO_CHECK_SETTINGS_DOCS" ; then \ + @if test -z "$$NMTST_NO_CHECK_SETTINGS_DOCS" ; then \ if ! cmp -s "$(srcdir)/$(clients_common_settings_doc_h).in" "$(builddir)/$(clients_common_settings_doc_h)" ; then \ - echo "The generated file \"$(builddir)/$(clients_common_settings_doc_h)\" differs from the source file \"$(srcdir)/$(clients_common_settings_doc_h).in\". You probably should copy the generated file over to the source file. You can skip this test by setting \$$NMTST_NO_CHECK_SETTINGS_DOCS=yes"; \ - false; \ + if test "$$NM_TEST_REGENERATE" == 1 ; then \ + cp -f "$(builddir)/$(clients_common_settings_doc_h)" "$(srcdir)/$(clients_common_settings_doc_h).in"; \ + else \ + echo "The generated file \"$(builddir)/$(clients_common_settings_doc_h)\" differs from the source file \"$(srcdir)/$(clients_common_settings_doc_h).in\". You probably should copy the generated file over to the source file. You can skip this test by setting \$$NMTST_NO_CHECK_SETTINGS_DOCS=yes". You can also automatically copy the file by rerunning the test with \$$NM_TEST_REGENERATE=1 ; \ + false; \ + fi; \ fi;\ fi check_local += check-local-settings-docs From d4093a3a2cb8e23248fed4e80e84b8816d453323 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Fri, 4 May 2018 09:02:53 +0200 Subject: [PATCH 7/8] clients/tests: add python test script for nmcli tests Add a test which runs nmcli against our stub NetworkManager service and compares the output. The output formats of nmcli are complicated and not easily understood. For example how --mode tabular|multiline interacts with selecting output-fields (--fields) and output modes ([default]|--terse|--pretty). Also, there are things like `nmcli connection show --order $FIELD_SPEC`. We need unit tests to ensure that we don't change the output accidentally. --- .travis.yml | 11 +- Makefile.am | 56 ++ .../test_001-001.expected | 19 + .../test_001-002.expected | 21 + .../test_001-003.expected | 13 + .../test_001-004.expected | 13 + .../test_001-005.expected | 13 + .../test_001-006.expected | 13 + .../test_002-001.expected | 17 + .../test_002-002.expected | 17 + .../test_002-003.expected | 35 + .../test_002-004.expected | 36 ++ .../test_002-005.expected | 42 ++ .../test_002-006.expected | 36 ++ .../test_002-007.expected | 16 + .../test_002-008.expected | 20 + .../test_002-009.expected | 15 + .../test_002-010.expected | 36 ++ .../test_002-011.expected | 42 ++ .../test_002-012.expected | 36 ++ .../test_002-013.expected | 16 + .../test_002-014.expected | 20 + .../test_002-015.expected | 15 + .../test_002-016.expected | 14 + .../test_002-017.expected | 33 + .../test_003-001.expected | 13 + .../test_003-002.expected | 15 + .../test_003-003.expected | 13 + .../test_003-004.expected | 16 + .../test_003-005.expected | 16 + .../test_003-006.expected | 16 + .../test_003-007.expected | 16 + .../test_003-008.expected | 22 + clients/tests/test-client.py | 603 ++++++++++++++++++ libnm/nm-client.c | 12 +- tools/test-networkmanager-service.py | 249 +++++--- 36 files changed, 1520 insertions(+), 76 deletions(-) create mode 100644 clients/tests/test-client.check-on-disk/test_001-001.expected create mode 100644 clients/tests/test-client.check-on-disk/test_001-002.expected create mode 100644 clients/tests/test-client.check-on-disk/test_001-003.expected create mode 100644 clients/tests/test-client.check-on-disk/test_001-004.expected create mode 100644 clients/tests/test-client.check-on-disk/test_001-005.expected create mode 100644 clients/tests/test-client.check-on-disk/test_001-006.expected create mode 100644 clients/tests/test-client.check-on-disk/test_002-001.expected create mode 100644 clients/tests/test-client.check-on-disk/test_002-002.expected create mode 100644 clients/tests/test-client.check-on-disk/test_002-003.expected create mode 100644 clients/tests/test-client.check-on-disk/test_002-004.expected create mode 100644 clients/tests/test-client.check-on-disk/test_002-005.expected create mode 100644 clients/tests/test-client.check-on-disk/test_002-006.expected create mode 100644 clients/tests/test-client.check-on-disk/test_002-007.expected create mode 100644 clients/tests/test-client.check-on-disk/test_002-008.expected create mode 100644 clients/tests/test-client.check-on-disk/test_002-009.expected create mode 100644 clients/tests/test-client.check-on-disk/test_002-010.expected create mode 100644 clients/tests/test-client.check-on-disk/test_002-011.expected create mode 100644 clients/tests/test-client.check-on-disk/test_002-012.expected create mode 100644 clients/tests/test-client.check-on-disk/test_002-013.expected create mode 100644 clients/tests/test-client.check-on-disk/test_002-014.expected create mode 100644 clients/tests/test-client.check-on-disk/test_002-015.expected create mode 100644 clients/tests/test-client.check-on-disk/test_002-016.expected create mode 100644 clients/tests/test-client.check-on-disk/test_002-017.expected create mode 100644 clients/tests/test-client.check-on-disk/test_003-001.expected create mode 100644 clients/tests/test-client.check-on-disk/test_003-002.expected create mode 100644 clients/tests/test-client.check-on-disk/test_003-003.expected create mode 100644 clients/tests/test-client.check-on-disk/test_003-004.expected create mode 100644 clients/tests/test-client.check-on-disk/test_003-005.expected create mode 100644 clients/tests/test-client.check-on-disk/test_003-006.expected create mode 100644 clients/tests/test-client.check-on-disk/test_003-007.expected create mode 100644 clients/tests/test-client.check-on-disk/test_003-008.expected create mode 100755 clients/tests/test-client.py diff --git a/.travis.yml b/.travis.yml index 72969fd84..e92131bf1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -104,9 +104,16 @@ script: - | if test "$BUILD_TYPE" == 'autotools'; then git clean -fdx && - ./autogen.sh --with-systemd-logind=no --enable-more-warnings=no --enable-ifcfg-rh --enable-config-plugin-ibft --enable-ifupdown --enable-tests && + ./autogen.sh --prefix="$PWD/INST" --with-systemd-logind=no --enable-more-warnings=no --enable-ifcfg-rh --enable-config-plugin-ibft --enable-ifupdown --enable-tests && make -j4 && - ./contrib/travis/travis-check.sh + if [ "$CC" == gcc ]; then + sudo locale-gen de_DE.UTF-8 && + sudo locale-gen pl_PL.UTF-8 && + sudo make install && + NM_TEST_CLIENT_CHECK_L10N=1 ./contrib/travis/travis-check.sh + else + ./contrib/travis/travis-check.sh + fi fi env: diff --git a/Makefile.am b/Makefile.am index a7152bd07..68faf421e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3780,6 +3780,62 @@ EXTRA_DIST += \ clients/tui/meson.build \ clients/tui/newt/meson.build +############################################################################### +# clients/tests +############################################################################### + +check-local-clients-tests-test-client: clients/cli/nmcli clients/tests/test-client.py + mkdir -p "$(builddir)/clients/tests/" + GI_TYPELIB_PATH="$(abs_builddir)/libnm$${GI_TYPELIB_PATH:+:$$GI_TYPELIB_PATH}" \ + LD_LIBRARY_PATH="$(abs_builddir)/libnm/.libs$${LD_LIBRARY_PATH:+:$$LD_LIBRARY_PATH}" \ + NM_TEST_CLIENT_BUILDDIR="$(abs_builddir)" \ + NM_TEST_CLIENT_NMCLI_PATH=clients/cli/nmcli \ + NM_TEST_CLIENT_IN_DBUS_SESSION=0 \ + $(srcdir)/clients/tests/test-client.py -v &> "$(builddir)/clients/tests/test-client.log" && r=ok; \ + cat "$(builddir)/clients/tests/test-client.log"; \ + test "$$r" == ok + +check_local += check-local-clients-tests-test-client + +CLEANFILES += clients/tests/test-client.log + +EXTRA_DIST += \ + clients/tests/test-client.py \ + \ + clients/tests/test-client.check-on-disk/test_001-001.expected \ + clients/tests/test-client.check-on-disk/test_001-002.expected \ + clients/tests/test-client.check-on-disk/test_001-003.expected \ + clients/tests/test-client.check-on-disk/test_001-004.expected \ + clients/tests/test-client.check-on-disk/test_001-005.expected \ + clients/tests/test-client.check-on-disk/test_001-006.expected \ + clients/tests/test-client.check-on-disk/test_002-001.expected \ + clients/tests/test-client.check-on-disk/test_002-002.expected \ + clients/tests/test-client.check-on-disk/test_002-003.expected \ + clients/tests/test-client.check-on-disk/test_002-004.expected \ + clients/tests/test-client.check-on-disk/test_002-005.expected \ + clients/tests/test-client.check-on-disk/test_002-006.expected \ + clients/tests/test-client.check-on-disk/test_002-007.expected \ + clients/tests/test-client.check-on-disk/test_002-008.expected \ + clients/tests/test-client.check-on-disk/test_002-009.expected \ + clients/tests/test-client.check-on-disk/test_002-010.expected \ + clients/tests/test-client.check-on-disk/test_002-011.expected \ + clients/tests/test-client.check-on-disk/test_002-012.expected \ + clients/tests/test-client.check-on-disk/test_002-013.expected \ + clients/tests/test-client.check-on-disk/test_002-014.expected \ + clients/tests/test-client.check-on-disk/test_002-015.expected \ + clients/tests/test-client.check-on-disk/test_002-016.expected \ + clients/tests/test-client.check-on-disk/test_002-017.expected \ + clients/tests/test-client.check-on-disk/test_003-001.expected \ + clients/tests/test-client.check-on-disk/test_003-002.expected \ + clients/tests/test-client.check-on-disk/test_003-003.expected \ + clients/tests/test-client.check-on-disk/test_003-004.expected \ + clients/tests/test-client.check-on-disk/test_003-005.expected \ + clients/tests/test-client.check-on-disk/test_003-006.expected \ + clients/tests/test-client.check-on-disk/test_003-007.expected \ + clients/tests/test-client.check-on-disk/test_003-008.expected \ + \ + $(NULL) + ############################################################################### # data ############################################################################### diff --git a/clients/tests/test-client.check-on-disk/test_001-001.expected b/clients/tests/test-client.check-on-disk/test_001-001.expected new file mode 100644 index 000000000..f3e8cf37a --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_001-001.expected @@ -0,0 +1,19 @@ +location: clients/tests/test-client.py:489:test_001()/1 +cmd: $NMCLI +lang: C +returncode: 0 +stdout: 277 bytes +>>> +DNS configuration: + servers: 1.2.3.4 5.6.7.8 + +Use "nmcli device show" to get complete information about known devices and +"nmcli connection show" to get an overview on active connection profiles. + +Consult nmcli(1) and nmcli-examples(5) manual pages for complete usage details. + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_001-002.expected b/clients/tests/test-client.check-on-disk/test_001-002.expected new file mode 100644 index 000000000..357cf3911 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_001-002.expected @@ -0,0 +1,21 @@ +location: clients/tests/test-client.py:490:test_001()/2 +cmd: $NMCLI +lang: pl_PL.UTF-8 +returncode: 0 +stdout: 310 bytes +>>> +DNS configuration: + servers: 1.2.3.4 5.6.7.8 + +Polecenie „nmcli device show” wyświetli pełne informacje o znanych +urządzeniach, a „nmcli connection show” wyświetli przegląd aktywnych +profili połączeń. + +Strony podręcznika nmcli(1) i nmcli-examples(5) zawierają pełne informacje +o użyciu. + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_001-003.expected b/clients/tests/test-client.check-on-disk/test_001-003.expected new file mode 100644 index 000000000..ea06b8d0b --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_001-003.expected @@ -0,0 +1,13 @@ +location: clients/tests/test-client.py:492:test_001()/3 +cmd: $NMCLI -f AP -mode multiline -p d show wlan0 +lang: C +returncode: 10 +stdout: 0 bytes +>>> + +<<< +stderr: 33 bytes +>>> +Error: Device 'wlan0' not found. + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_001-004.expected b/clients/tests/test-client.check-on-disk/test_001-004.expected new file mode 100644 index 000000000..8e58d1493 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_001-004.expected @@ -0,0 +1,13 @@ +location: clients/tests/test-client.py:493:test_001()/4 +cmd: $NMCLI -f AP -mode multiline -p d show wlan0 +lang: de_DE.utf8 +returncode: 10 +stdout: 0 bytes +>>> + +<<< +stderr: 47 bytes +>>> +Fehler: Gerät »wlan0« wurde nicht gefunden. + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_001-005.expected b/clients/tests/test-client.check-on-disk/test_001-005.expected new file mode 100644 index 000000000..c24b6cd32 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_001-005.expected @@ -0,0 +1,13 @@ +location: clients/tests/test-client.py:495:test_001()/5 +cmd: $NMCLI c s +lang: C +returncode: 0 +stdout: 26 bytes +>>> +NAME UUID TYPE DEVICE + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_001-006.expected b/clients/tests/test-client.check-on-disk/test_001-006.expected new file mode 100644 index 000000000..66c0e9917 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_001-006.expected @@ -0,0 +1,13 @@ +location: clients/tests/test-client.py:497:test_001()/6 +cmd: $NMCLI bogus s +lang: C +returncode: 2 +stdout: 0 bytes +>>> + +<<< +stderr: 68 bytes +>>> +Error: argument 'bogus' not understood. Try passing --help instead. + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_002-001.expected b/clients/tests/test-client.check-on-disk/test_002-001.expected new file mode 100644 index 000000000..bc0d4f5dd --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_002-001.expected @@ -0,0 +1,17 @@ +location: clients/tests/test-client.py:502:test_002()/1 +cmd: $NMCLI d +lang: C +returncode: 0 +stdout: 215 bytes +>>> +DEVICE TYPE STATE CONNECTION +eth0 ethernet unavailable -- +wlan0 wifi unavailable -- +wlan1 wifi unavailable -- +wlan1 wifi unavailable -- + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_002-002.expected b/clients/tests/test-client.check-on-disk/test_002-002.expected new file mode 100644 index 000000000..d516e2f84 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_002-002.expected @@ -0,0 +1,17 @@ +location: clients/tests/test-client.py:504:test_002()/2 +cmd: $NMCLI -f all d +lang: C +returncode: 0 +stdout: 530 bytes +>>> +DEVICE TYPE STATE DBUS-PATH CONNECTION CON-UUID CON-PATH +eth0 ethernet unavailable /org/freedesktop/NetworkManager/Devices/1 -- -- -- +wlan0 wifi unavailable /org/freedesktop/NetworkManager/Devices/2 -- -- -- +wlan1 wifi unavailable /org/freedesktop/NetworkManager/Devices/3 -- -- -- +wlan1 wifi unavailable /org/freedesktop/NetworkManager/Devices/4 -- -- -- + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_002-003.expected b/clients/tests/test-client.check-on-disk/test_002-003.expected new file mode 100644 index 000000000..ff88393cc --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_002-003.expected @@ -0,0 +1,35 @@ +location: clients/tests/test-client.py:506:test_002()/3 +cmd: $NMCLI +lang: C +returncode: 0 +stdout: 551 bytes +>>> +eth0: unavailable + "eth0" + ethernet (virtual), 72:41:AB:90:41:5D, hw + +wlan0: unavailable + "wlan0" + wifi (virtual), 5A:88:5E:B6:90:40, hw + +wlan1: unavailable + "wlan1" + wifi (virtual), 7C:D4:69:31:67:0B, hw + +wlan1: unavailable + "wlan1" + wifi (virtual), 41:21:6B:F3:C9:4A, hw + +DNS configuration: + servers: 1.2.3.4 5.6.7.8 + +Use "nmcli device show" to get complete information about known devices and +"nmcli connection show" to get an overview on active connection profiles. + +Consult nmcli(1) and nmcli-examples(5) manual pages for complete usage details. + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_002-004.expected b/clients/tests/test-client.check-on-disk/test_002-004.expected new file mode 100644 index 000000000..2e2989765 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_002-004.expected @@ -0,0 +1,36 @@ +location: clients/tests/test-client.py:508:test_002()/4 +cmd: $NMCLI -f AP -mode multiline d show wlan0 +lang: C +returncode: 0 +stdout: 1107 bytes +>>> +AP[1].IN-USE: +AP[1].SSID: wlan0-ap-3 +AP[1].MODE: Infra +AP[1].CHAN: 1 +AP[1].RATE: 54 Mbit/s +AP[1].SIGNAL: 61 +AP[1].BARS: *** +AP[1].SECURITY: WPA1 WPA2 +AP[2].IN-USE: +AP[2].SSID: wlan0-ap-1 +AP[2].MODE: Infra +AP[2].CHAN: 1 +AP[2].RATE: 54 Mbit/s +AP[2].SIGNAL: 34 +AP[2].BARS: ** +AP[2].SECURITY: WPA1 WPA2 +AP[3].IN-USE: +AP[3].SSID: wlan0-ap-2 +AP[3].MODE: Infra +AP[3].CHAN: 1 +AP[3].RATE: 54 Mbit/s +AP[3].SIGNAL: 29 +AP[3].BARS: * +AP[3].SECURITY: WPA1 WPA2 + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_002-005.expected b/clients/tests/test-client.check-on-disk/test_002-005.expected new file mode 100644 index 000000000..f70e30a4b --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_002-005.expected @@ -0,0 +1,42 @@ +location: clients/tests/test-client.py:509:test_002()/5 +cmd: $NMCLI -f AP -mode multiline -p d show wlan0 +lang: C +returncode: 0 +stdout: 1558 bytes +>>> +=============================================================================== + Device details (wlan0) +=============================================================================== +AP[1].IN-USE: +AP[1].SSID: wlan0-ap-3 +AP[1].MODE: Infra +AP[1].CHAN: 1 +AP[1].RATE: 54 Mbit/s +AP[1].SIGNAL: 61 +AP[1].BARS: *** +AP[1].SECURITY: WPA1 WPA2 +------------------------------------------------------------------------------- +AP[2].IN-USE: +AP[2].SSID: wlan0-ap-1 +AP[2].MODE: Infra +AP[2].CHAN: 1 +AP[2].RATE: 54 Mbit/s +AP[2].SIGNAL: 34 +AP[2].BARS: ** +AP[2].SECURITY: WPA1 WPA2 +------------------------------------------------------------------------------- +AP[3].IN-USE: +AP[3].SSID: wlan0-ap-2 +AP[3].MODE: Infra +AP[3].CHAN: 1 +AP[3].RATE: 54 Mbit/s +AP[3].SIGNAL: 29 +AP[3].BARS: * +AP[3].SECURITY: WPA1 WPA2 +------------------------------------------------------------------------------- + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_002-006.expected b/clients/tests/test-client.check-on-disk/test_002-006.expected new file mode 100644 index 000000000..e87dc1d11 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_002-006.expected @@ -0,0 +1,36 @@ +location: clients/tests/test-client.py:510:test_002()/6 +cmd: $NMCLI -f AP -mode multiline -t d show wlan0 +lang: C +returncode: 0 +stdout: 435 bytes +>>> +AP[1].IN-USE: +AP[1].SSID:wlan0-ap-3 +AP[1].MODE:Infra +AP[1].CHAN:1 +AP[1].RATE:54 Mbit/s +AP[1].SIGNAL:61 +AP[1].BARS:*** +AP[1].SECURITY:WPA1 WPA2 +AP[2].IN-USE: +AP[2].SSID:wlan0-ap-1 +AP[2].MODE:Infra +AP[2].CHAN:1 +AP[2].RATE:54 Mbit/s +AP[2].SIGNAL:34 +AP[2].BARS:** +AP[2].SECURITY:WPA1 WPA2 +AP[3].IN-USE: +AP[3].SSID:wlan0-ap-2 +AP[3].MODE:Infra +AP[3].CHAN:1 +AP[3].RATE:54 Mbit/s +AP[3].SIGNAL:29 +AP[3].BARS:* +AP[3].SECURITY:WPA1 WPA2 + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_002-007.expected b/clients/tests/test-client.check-on-disk/test_002-007.expected new file mode 100644 index 000000000..78f7bf66c --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_002-007.expected @@ -0,0 +1,16 @@ +location: clients/tests/test-client.py:511:test_002()/7 +cmd: $NMCLI -f AP -mode tabular d show wlan0 +lang: C +returncode: 0 +stdout: 304 bytes +>>> +NAME IN-USE SSID MODE CHAN RATE SIGNAL BARS SECURITY +AP[1] wlan0-ap-3 Infra 1 54 Mbit/s 61 *** WPA1 WPA2 +AP[2] wlan0-ap-1 Infra 1 54 Mbit/s 34 ** WPA1 WPA2 +AP[3] wlan0-ap-2 Infra 1 54 Mbit/s 29 * WPA1 WPA2 + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_002-008.expected b/clients/tests/test-client.check-on-disk/test_002-008.expected new file mode 100644 index 000000000..951ad9ce0 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_002-008.expected @@ -0,0 +1,20 @@ +location: clients/tests/test-client.py:512:test_002()/8 +cmd: $NMCLI -f AP -mode tabular -p d show wlan0 +lang: C +returncode: 0 +stdout: 460 bytes +>>> +========================== + Device details (wlan0) +========================== +NAME IN-USE SSID MODE CHAN RATE SIGNAL BARS SECURITY +---------------------------------------------------------------------------- +AP[1] wlan0-ap-3 Infra 1 54 Mbit/s 61 *** WPA1 WPA2 +AP[2] wlan0-ap-1 Infra 1 54 Mbit/s 34 ** WPA1 WPA2 +AP[3] wlan0-ap-2 Infra 1 54 Mbit/s 29 * WPA1 WPA2 + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_002-009.expected b/clients/tests/test-client.check-on-disk/test_002-009.expected new file mode 100644 index 000000000..ce7172e34 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_002-009.expected @@ -0,0 +1,15 @@ +location: clients/tests/test-client.py:513:test_002()/9 +cmd: $NMCLI -f AP -mode tabular -t d show wlan0 +lang: C +returncode: 0 +stdout: 165 bytes +>>> +AP[1]: :wlan0-ap-3:Infra:1:54 Mbit/s:61:*** :WPA1 WPA2 +AP[2]: :wlan0-ap-1:Infra:1:54 Mbit/s:34:** :WPA1 WPA2 +AP[3]: :wlan0-ap-2:Infra:1:54 Mbit/s:29:* :WPA1 WPA2 + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_002-010.expected b/clients/tests/test-client.check-on-disk/test_002-010.expected new file mode 100644 index 000000000..85b933715 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_002-010.expected @@ -0,0 +1,36 @@ +location: clients/tests/test-client.py:515:test_002()/10 +cmd: $NMCLI -f AP -mode multiline d show wlan0 +lang: pl_PL.UTF-8 +returncode: 0 +stdout: 1134 bytes +>>> +AP[1].IN-USE: +AP[1].SSID: wlan0-ap-3 +AP[1].MODE: Infrastruktura +AP[1].CHAN: 1 +AP[1].RATE: 54 Mb/s +AP[1].SIGNAL: 61 +AP[1].BARS: *** +AP[1].SECURITY: WPA1 WPA2 +AP[2].IN-USE: +AP[2].SSID: wlan0-ap-1 +AP[2].MODE: Infrastruktura +AP[2].CHAN: 1 +AP[2].RATE: 54 Mb/s +AP[2].SIGNAL: 34 +AP[2].BARS: ** +AP[2].SECURITY: WPA1 WPA2 +AP[3].IN-USE: +AP[3].SSID: wlan0-ap-2 +AP[3].MODE: Infrastruktura +AP[3].CHAN: 1 +AP[3].RATE: 54 Mb/s +AP[3].SIGNAL: 29 +AP[3].BARS: * +AP[3].SECURITY: WPA1 WPA2 + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_002-011.expected b/clients/tests/test-client.check-on-disk/test_002-011.expected new file mode 100644 index 000000000..e48611f59 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_002-011.expected @@ -0,0 +1,42 @@ +location: clients/tests/test-client.py:516:test_002()/11 +cmd: $NMCLI -f AP -mode multiline -p d show wlan0 +lang: pl_PL.UTF-8 +returncode: 0 +stdout: 1592 bytes +>>> +=============================================================================== + Informacje o urządzeniu (wlan0) +=============================================================================== +AP[1].IN-USE: +AP[1].SSID: wlan0-ap-3 +AP[1].MODE: Infrastruktura +AP[1].CHAN: 1 +AP[1].RATE: 54 Mb/s +AP[1].SIGNAL: 61 +AP[1].BARS: *** +AP[1].SECURITY: WPA1 WPA2 +------------------------------------------------------------------------------- +AP[2].IN-USE: +AP[2].SSID: wlan0-ap-1 +AP[2].MODE: Infrastruktura +AP[2].CHAN: 1 +AP[2].RATE: 54 Mb/s +AP[2].SIGNAL: 34 +AP[2].BARS: ** +AP[2].SECURITY: WPA1 WPA2 +------------------------------------------------------------------------------- +AP[3].IN-USE: +AP[3].SSID: wlan0-ap-2 +AP[3].MODE: Infrastruktura +AP[3].CHAN: 1 +AP[3].RATE: 54 Mb/s +AP[3].SIGNAL: 29 +AP[3].BARS: * +AP[3].SECURITY: WPA1 WPA2 +------------------------------------------------------------------------------- + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_002-012.expected b/clients/tests/test-client.check-on-disk/test_002-012.expected new file mode 100644 index 000000000..542261c38 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_002-012.expected @@ -0,0 +1,36 @@ +location: clients/tests/test-client.py:517:test_002()/12 +cmd: $NMCLI -f AP -mode multiline -t d show wlan0 +lang: pl_PL.UTF-8 +returncode: 0 +stdout: 462 bytes +>>> +AP[1].IN-USE: +AP[1].SSID:wlan0-ap-3 +AP[1].MODE:Infrastruktura +AP[1].CHAN:1 +AP[1].RATE:54 Mb/s +AP[1].SIGNAL:61 +AP[1].BARS:*** +AP[1].SECURITY:WPA1 WPA2 +AP[2].IN-USE: +AP[2].SSID:wlan0-ap-1 +AP[2].MODE:Infrastruktura +AP[2].CHAN:1 +AP[2].RATE:54 Mb/s +AP[2].SIGNAL:34 +AP[2].BARS:** +AP[2].SECURITY:WPA1 WPA2 +AP[3].IN-USE: +AP[3].SSID:wlan0-ap-2 +AP[3].MODE:Infrastruktura +AP[3].CHAN:1 +AP[3].RATE:54 Mb/s +AP[3].SIGNAL:29 +AP[3].BARS:* +AP[3].SECURITY:WPA1 WPA2 + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_002-013.expected b/clients/tests/test-client.check-on-disk/test_002-013.expected new file mode 100644 index 000000000..54ddb50a3 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_002-013.expected @@ -0,0 +1,16 @@ +location: clients/tests/test-client.py:518:test_002()/13 +cmd: $NMCLI -f AP -mode tabular d show wlan0 +lang: pl_PL.UTF-8 +returncode: 0 +stdout: 338 bytes +>>> +NAME IN-USE SSID MODE CHAN RATE SIGNAL BARS SECURITY +AP[1] wlan0-ap-3 Infrastruktura 1 54 Mb/s 61 *** WPA1 WPA2 +AP[2] wlan0-ap-1 Infrastruktura 1 54 Mb/s 34 ** WPA1 WPA2 +AP[3] wlan0-ap-2 Infrastruktura 1 54 Mb/s 29 * WPA1 WPA2 + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_002-014.expected b/clients/tests/test-client.check-on-disk/test_002-014.expected new file mode 100644 index 000000000..ea42ce9a4 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_002-014.expected @@ -0,0 +1,20 @@ +location: clients/tests/test-client.py:519:test_002()/14 +cmd: $NMCLI -f AP -mode tabular -p d show wlan0 +lang: pl_PL.UTF-8 +returncode: 0 +stdout: 530 bytes +>>> +=================================== + Informacje o urządzeniu (wlan0) +=================================== +NAME IN-USE SSID MODE CHAN RATE SIGNAL BARS SECURITY +----------------------------------------------------------------------------------- +AP[1] wlan0-ap-3 Infrastruktura 1 54 Mb/s 61 *** WPA1 WPA2 +AP[2] wlan0-ap-1 Infrastruktura 1 54 Mb/s 34 ** WPA1 WPA2 +AP[3] wlan0-ap-2 Infrastruktura 1 54 Mb/s 29 * WPA1 WPA2 + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_002-015.expected b/clients/tests/test-client.check-on-disk/test_002-015.expected new file mode 100644 index 000000000..3854822bb --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_002-015.expected @@ -0,0 +1,15 @@ +location: clients/tests/test-client.py:520:test_002()/15 +cmd: $NMCLI -f AP -mode tabular -t d show wlan0 +lang: pl_PL.UTF-8 +returncode: 0 +stdout: 192 bytes +>>> +AP[1]: :wlan0-ap-3:Infrastruktura:1:54 Mb/s:61:*** :WPA1 WPA2 +AP[2]: :wlan0-ap-1:Infrastruktura:1:54 Mb/s:34:** :WPA1 WPA2 +AP[3]: :wlan0-ap-2:Infrastruktura:1:54 Mb/s:29:* :WPA1 WPA2 + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_002-016.expected b/clients/tests/test-client.check-on-disk/test_002-016.expected new file mode 100644 index 000000000..452db9a44 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_002-016.expected @@ -0,0 +1,14 @@ +location: clients/tests/test-client.py:522:test_002()/16 +cmd: $NMCLI c +lang: C +returncode: 0 +stdout: 126 bytes +>>> +NAME UUID TYPE DEVICE +con-1 5fcfd6d7-1e63-3332-8826-a7eda103792d ethernet -- + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_002-017.expected b/clients/tests/test-client.check-on-disk/test_002-017.expected new file mode 100644 index 000000000..88ac43f02 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_002-017.expected @@ -0,0 +1,33 @@ +location: clients/tests/test-client.py:524:test_002()/17 +cmd: $NMCLI c s con-1 +lang: C +returncode: 0 +stdout: 990 bytes +>>> +connection.id: con-1 +connection.uuid: 5fcfd6d7-1e63-3332-8826-a7eda103792d +connection.stable-id: -- +connection.type: 802-3-ethernet +connection.interface-name: -- +connection.autoconnect: yes +connection.autoconnect-priority: 0 +connection.autoconnect-retries: -1 (default) +connection.auth-retries: -1 +connection.timestamp: 0 +connection.read-only: no +connection.permissions: -- +connection.zone: -- +connection.master: -- +connection.slave-type: -- +connection.autoconnect-slaves: -1 (default) +connection.secondaries: -- +connection.gateway-ping-timeout: 0 +connection.metered: unknown +connection.lldp: default +connection.mdns: -1 (default) + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_003-001.expected b/clients/tests/test-client.check-on-disk/test_003-001.expected new file mode 100644 index 000000000..9e37cf9f7 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_003-001.expected @@ -0,0 +1,13 @@ +location: clients/tests/test-client.py:534:test_003()/1 +cmd: $NMCLI c add type ethernet ifname '*' con-name con-xx1 +lang: C +returncode: 0 +stdout: 80 bytes +>>> +Connection 'con-xx1' (UUID-con-xx1-REPLACED-REPLACED-REPLA) successfully added. + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_003-002.expected b/clients/tests/test-client.check-on-disk/test_003-002.expected new file mode 100644 index 000000000..3447e6091 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_003-002.expected @@ -0,0 +1,15 @@ +location: clients/tests/test-client.py:537:test_003()/2 +cmd: $NMCLI c s +lang: C +returncode: 0 +stdout: 195 bytes +>>> +NAME UUID TYPE DEVICE +con-1 5fcfd6d7-1e63-3332-8826-a7eda103792d ethernet -- +con-xx1 UUID-con-xx1-REPLACED-REPLACED-REPLA ethernet -- + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_003-003.expected b/clients/tests/test-client.check-on-disk/test_003-003.expected new file mode 100644 index 000000000..c241bc5bc --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_003-003.expected @@ -0,0 +1,13 @@ +location: clients/tests/test-client.py:542:test_003()/3 +cmd: $NMCLI c add type ethernet ifname '*' +lang: C +returncode: 0 +stdout: 81 bytes +>>> +Connection 'ethernet' (UUID-ethernet-REPLACED-REPLACED-REPL) successfully added. + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_003-004.expected b/clients/tests/test-client.check-on-disk/test_003-004.expected new file mode 100644 index 000000000..2c921be2f --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_003-004.expected @@ -0,0 +1,16 @@ +location: clients/tests/test-client.py:545:test_003()/4 +cmd: $NMCLI c s +lang: C +returncode: 0 +stdout: 264 bytes +>>> +NAME UUID TYPE DEVICE +con-1 5fcfd6d7-1e63-3332-8826-a7eda103792d ethernet -- +con-xx1 UUID-con-xx1-REPLACED-REPLACED-REPLA ethernet -- +ethernet UUID-ethernet-REPLACED-REPLACED-REPL ethernet -- + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_003-005.expected b/clients/tests/test-client.check-on-disk/test_003-005.expected new file mode 100644 index 000000000..2ec86c01a --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_003-005.expected @@ -0,0 +1,16 @@ +location: clients/tests/test-client.py:547:test_003()/5 +cmd: $NMCLI c s +lang: pl_PL.UTF-8 +returncode: 0 +stdout: 264 bytes +>>> +NAME UUID TYPE DEVICE +con-1 5fcfd6d7-1e63-3332-8826-a7eda103792d ethernet -- +con-xx1 UUID-con-xx1-REPLACED-REPLACED-REPLA ethernet -- +ethernet UUID-ethernet-REPLACED-REPLACED-REPL ethernet -- + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_003-006.expected b/clients/tests/test-client.check-on-disk/test_003-006.expected new file mode 100644 index 000000000..1e8a29973 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_003-006.expected @@ -0,0 +1,16 @@ +location: clients/tests/test-client.py:550:test_003()/6 +cmd: $NMCLI -f ALL c s +lang: C +returncode: 0 +stdout: 912 bytes +>>> +NAME UUID TYPE TIMESTAMP TIMESTAMP-REAL AUTOCONNECT AUTOCONNECT-PRIORITY READONLY DBUS-PATH ACTIVE DEVICE STATE ACTIVE-PATH SLAVE +con-1 5fcfd6d7-1e63-3332-8826-a7eda103792d ethernet 0 never yes 0 no /org/freedesktop/NetworkManager/Settings/Connection/1 no -- -- -- -- +con-xx1 UUID-con-xx1-REPLACED-REPLACED-REPLA ethernet 0 never yes 0 no /org/freedesktop/NetworkManager/Settings/Connection/2 no -- -- -- -- +ethernet UUID-ethernet-REPLACED-REPLACED-REPL ethernet 0 never yes 0 no /org/freedesktop/NetworkManager/Settings/Connection/3 no -- -- -- -- + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_003-007.expected b/clients/tests/test-client.check-on-disk/test_003-007.expected new file mode 100644 index 000000000..e566f474f --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_003-007.expected @@ -0,0 +1,16 @@ +location: clients/tests/test-client.py:552:test_003()/7 +cmd: $NMCLI -f ALL c s +lang: pl_PL.UTF-8 +returncode: 0 +stdout: 912 bytes +>>> +NAME UUID TYPE TIMESTAMP TIMESTAMP-REAL AUTOCONNECT AUTOCONNECT-PRIORITY READONLY DBUS-PATH ACTIVE DEVICE STATE ACTIVE-PATH SLAVE +con-1 5fcfd6d7-1e63-3332-8826-a7eda103792d ethernet 0 nigdy tak 0 nie /org/freedesktop/NetworkManager/Settings/Connection/1 nie -- -- -- -- +con-xx1 UUID-con-xx1-REPLACED-REPLACED-REPLA ethernet 0 nigdy tak 0 nie /org/freedesktop/NetworkManager/Settings/Connection/2 nie -- -- -- -- +ethernet UUID-ethernet-REPLACED-REPLACED-REPL ethernet 0 nigdy tak 0 nie /org/freedesktop/NetworkManager/Settings/Connection/3 nie -- -- -- -- + +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.check-on-disk/test_003-008.expected b/clients/tests/test-client.check-on-disk/test_003-008.expected new file mode 100644 index 000000000..44be3e054 --- /dev/null +++ b/clients/tests/test-client.check-on-disk/test_003-008.expected @@ -0,0 +1,22 @@ +location: clients/tests/test-client.py:556:test_003()/8 +cmd: $NMCLI --complete-args -f ALL c s '' +lang: pl_PL.UTF-8 +returncode: 0 +stdout: 64 bytes +>>> + +--active +--order +apath +con-1 +con-xx1 +ethernet +help +id +path +uuid +<<< +stderr: 0 bytes +>>> + +<<< diff --git a/clients/tests/test-client.py b/clients/tests/test-client.py new file mode 100755 index 000000000..8afd22c16 --- /dev/null +++ b/clients/tests/test-client.py @@ -0,0 +1,603 @@ +#!/usr/bin/env python + +from __future__ import print_function + +import sys +import os +import errno +import unittest +import socket +import itertools +import subprocess +import shlex +import re +import dbus +import time +import dbus.service +import dbus.mainloop.glib + +# The test can be configured via the following environment variables: +ENV_NM_TEST_CLIENT_BUILDDIR = 'NM_TEST_CLIENT_BUILDDIR' +ENV_NM_TEST_CLIENT_NMCLI_PATH = 'NM_TEST_CLIENT_NMCLI_PATH' +ENV_NM_TEST_CLIENT_CHECK_L10N = 'NM_TEST_CLIENT_CHECK_L10N' +ENV_NM_TEST_REGENERATE = 'NM_TEST_REGENERATE' + +############################################################################### + +class PathConfiguration: + + @staticmethod + def srcdir(): + # this is the directory where the test script itself lies. + # Based on this directory, we find other parts that we expect + # in the source repository. + return os.path.dirname(os.path.abspath(__file__)) + + @staticmethod + def top_srcdir(): + return os.path.abspath(PathConfiguration.srcdir() + "/../..") + + @staticmethod + def test_networkmanager_service_path(): + v = os.path.abspath(PathConfiguration.top_srcdir() + "/tools/test-networkmanager-service.py") + assert os.path.exists(v), ("Cannot find test server at \"%s\"" % (v)) + return v + +############################################################################### + +os.sys.path.append(os.path.abspath(PathConfiguration.top_srcdir() + '/examples/python')) +import nmex + +dbus_session_inited = False + +_DEFAULT_ARG = object() + +############################################################################### + +class Util: + + @staticmethod + def python_has_version(major, minor = 0): + return sys.version_info[0] > major \ + or ( sys.version_info[0] == major \ + and sys.version_info[1] >= minor) + + @staticmethod + def is_string(s): + if Util.python_has_version(3): + t = str + else: + t = basestring + return isinstance(s, t) + + _find_unsafe = re.compile(r'[^\w@%+=:,./-]', + re.ASCII if sys.version_info[0] >= 3 else 0).search + + @staticmethod + def quote(s): + if Util.python_has_version(3, 3): + return shlex.quote(s) + if not s: + return "''" + if Util._find_unsafe(s) is None: + return s + return "'" + s.replace("'", "'\"'\"'") + "'" + + @staticmethod + def popen_wait(p, timeout = None): + # wait() has a timeout argument only since 3.3 + if Util.python_has_version(3, 3): + return p.wait(timeout) + if timeout is None: + return p.wait() + start = nmex.nm_boot_time_ns() + while True: + if p.poll() is not None: + return p.returncode + if start + (timeout * 1000000000) < nmex.nm_boot_time_ns(): + raise Exception("timeout expired") + time.sleep(0.05) + + @staticmethod + def iter_single(itr, min_num = 1, max_num = 1): + itr = list(itr) + n = 0 + v = None + for c in itr: + n += 1 + if n > 1: + break + v = c + if n < min_num: + raise AssertionError("Expected at least %s elements, but %s found" % (min_num, n)) + if n > max_num: + raise AssertionError("Expected at most %s elements, but %s found" % (max_num, n)) + return v + +############################################################################### + +class Configuration: + + def __init__(self): + self._values = {} + + def get(self, name): + v = self._values.get(name, None) + if name in self._values: + return v + if name == ENV_NM_TEST_CLIENT_BUILDDIR: + v = os.environ.get(ENV_NM_TEST_CLIENT_BUILDDIR, PathConfiguration.top_srcdir()) + if not os.path.isdir(v): + raise Exception("Missing builddir. Set NM_TEST_CLIENT_BUILDDIR?") + elif name == ENV_NM_TEST_CLIENT_NMCLI_PATH: + v = os.environ.get(ENV_NM_TEST_CLIENT_NMCLI_PATH, None) + if v is None: + try: + v = os.path.abspath(self.get(ENV_NM_TEST_CLIENT_BUILDDIR) + "/clients/cli/nmcli") + except: + pass + if not os.path.exists(v): + raise Exception("Missing nmcli binary. Set NM_TEST_CLIENT_NMCLI_PATH?") + elif name == ENV_NM_TEST_CLIENT_CHECK_L10N: + # if we test locales other than 'C', the output of nmcli depends on whether + # nmcli can load the translations. Unfortunately, I cannot find a way to + # make gettext use the po/*.gmo files from the build-dir. + # + # hence, such tests only work, if you also issue `make-install` + # + # Only by setting NM_TEST_CLIENT_CHECK_L10N=1, these tests are included + # as well. + v = (os.environ.get(ENV_NM_TEST_CLIENT_CHECK_L10N, '0') == '1') + elif name == ENV_NM_TEST_REGENERATE: + # in the "regenerate" mode, the tests will rewrite the files on disk against + # which we assert. That is useful, if there are intentional changes and + # we want to regenerate the expected output. + v = (os.environ.get(ENV_NM_TEST_REGENERATE, '0') == '1') + else: + raise Exception() + self._values[name] = v + return v + +conf = Configuration() + +############################################################################### + +class NMStubServer: + + @staticmethod + def _conn_get_main_object(conn): + try: + return conn.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager') + except: + return None + + def __init__(self): + service_path = PathConfiguration.test_networkmanager_service_path() + self._conn = dbus.SessionBus() + p = subprocess.Popen([sys.executable, service_path], + stdin = subprocess.PIPE) + + start = nmex.nm_boot_time_ns() + while True: + if p.poll() is not None: + p.stdin.close() + if p.returncode == 77: + raise unittest.SkipTest('the stub service %s exited with status 77' % (service_path)) + raise Exception('the stub service %s exited unexpectedly' % (service_path)) + nmobj = self._conn_get_main_object(self._conn) + if nmobj is not None: + break + if (nmex.nm_boot_time_ns() - start) / 1000000 >= 2000: + p.stdin.close() + p.kill() + Util.popen_wait(p, 1000) + raise Exception("after starting stub service the D-Bus name was not claimed in time") + + self._nmobj = nmobj + self._nmiface = dbus.Interface(nmobj, "org.freedesktop.NetworkManager.LibnmGlibTest") + self._p = p + + def shutdown(self): + self._nmobj = None + self._nmiface = None + self._conn = None + self._p.stdin.close() + self._p.kill() + Util.popen_wait(self._p, 1000) + self._p = None + if self._conn_get_main_object(self._conn) is not None: + raise Exception("Stub service is not still here although it should shut down") + + class _MethodProxy: + def __init__(self, parent, method_name): + self._parent = parent + self._method_name = method_name + def __call__(self, *args, **kwargs): + dbus_iface = kwargs.pop('dbus_iface', None) + if dbus_iface is None: + dbus_iface = self._parent._nmiface + method = dbus_iface.get_dbus_method(self._method_name) + if kwargs: + # for convenience, we allow the caller to specify arguments + # as kwargs. In this case, we construct a a{sv} array as last argument. + kwargs2 = {} + args = list(args) + args.append(kwargs2) + for k in kwargs.keys(): + kwargs2[k] = kwargs[k] + return method(*args) + + def __getattr__(self, member): + if not member.startswith("op_"): + raise AttributeError(member) + return self._MethodProxy(self, member[3:]) + + def addConnection(self, connection, verify_connection = True): + return self.op_AddConnection(connection, verify_connection) + + def findConnectionUuid(self, con_id): + try: + u = Util.iter_single(self.op_FindConnections(con_id = con_id))[1] + assert u, ("Invalid uuid %s" % (u)) + except Exception as e: + raise AssertionError("Unexpectedly not found connection %s: %s" % (con_id, str(e))) + return u + +############################################################################### + +class NmTestBase(unittest.TestCase): + pass + +class TestNmcli(NmTestBase): + + @staticmethod + def _replace(text, replace_arr): + if not replace_arr: + return text + text = [text] + for replace in replace_arr: + try: + v_search = replace[0]() + except TypeError: + v_search = replace[0] + assert v_search is None or Util.is_string(v_search) + if not v_search: + continue + v_replace = replace[1] + v_search = v_search.encode('utf-8') + v_replace = v_replace.encode('utf-8') + text2 = [] + for t in text: + if isinstance(t, tuple): + text2.append(t) + continue + t2 = t.split(v_search) + text2.append(t2[0]) + for t3 in t2[1:]: + text2.append( (v_replace,) ) + text2.append(t3) + text = text2 + return b''.join([(t[0] if isinstance(t, tuple) else t) for t in text]) + + def call_nmcli(self, + args, + lang = None, + check_on_disk = _DEFAULT_ARG, + expected_returncode = _DEFAULT_ARG, + expected_stdout = _DEFAULT_ARG, + expected_stderr = _DEFAULT_ARG, + replace_stdout = None, + replace_stderr = None, + sort_lines_stdout = False): + + frame = sys._getframe(1) + + calling_fcn = frame.f_code.co_name + + calling_num = self._calling_num.get(calling_fcn, 0) + 1 + self._calling_num[calling_fcn] = calling_num + + if lang is None or lang == 'C': + lang = 'C' + language = '' + elif lang is 'de': + lang = 'de_DE.utf8' + language = 'de' + elif lang is 'pl': + lang = 'pl_PL.UTF-8' + language = 'pl' + else: + self.fail('invalid language %s' % (lang)) + + env = {} + for k in ['LD_LIBRARY_PATH', + 'DBUS_SESSION_BUS_ADDRESS']: + val = os.environ.get(k, None) + if val is not None: + env[k] = val + env['LANG'] = lang + env['LANGUAGE'] = language + env['LIBNM_USE_SESSION_BUS'] = '1' + env['LIBNM_USE_NO_UDEV'] = '1' + env['TERM'] = 'linux' + + args = [conf.get(ENV_NM_TEST_CLIENT_NMCLI_PATH)] + list(args) + + p = subprocess.Popen(args, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE, + env = env) + Util.popen_wait(p, 2000) + + (returncode, stdout, stderr) = (p.returncode, p.stdout.read(), p.stderr.read()) + + p.stdout.close() + p.stderr.close() + + stdout = self._replace(stdout, replace_stdout) + stderr = self._replace(stderr, replace_stderr) + + if sort_lines_stdout: + stdout = b'\n'.join(sorted(stdout.split(b'\n'))) + + ignore_l10n_diff = ( lang != 'C' + and not conf.get(ENV_NM_TEST_CLIENT_CHECK_L10N)) + + test_name = '%s-%03d' % (calling_fcn, calling_num) + + if check_on_disk is _DEFAULT_ARG: + check_on_disk = ( expected_returncode is _DEFAULT_ARG + and expected_stdout is _DEFAULT_ARG + and expected_stderr is _DEFAULT_ARG) + if expected_returncode is _DEFAULT_ARG: + expected_returncode = None + if expected_stdout is _DEFAULT_ARG: + expected_stdout = None + if expected_stderr is _DEFAULT_ARG: + expected_stderr = None + + if expected_stderr is not None: + if expected_stderr != stderr: + if ignore_l10n_diff: + self._skip_test_for_l10n_diff.append(test_name) + else: + self.assertEqual(expected_stderr, stderr) + if expected_stdout is not None: + if expected_stdout != stdout: + if ignore_l10n_diff: + self._skip_test_for_l10n_diff.append(test_name) + else: + self.assertEqual(expected_stdout, stdout) + if expected_returncode is not None: + self.assertEqual(expected_returncode, returncode) + + + dirname = PathConfiguration.srcdir() + '/test-client.check-on-disk' + filename = os.path.abspath(dirname + '/' + test_name + '.expected') + + if not check_on_disk: + if os.path.exists(filename): + self.fail("The file '%s' exists, although we expect it not to." % (filename)) + return + + try: + with open(filename, 'rb') as content_file: + content_old = content_file.read() + except: + content_old = None + + # we cannot use frame.f_code.co_filename directly, because it might be different depending + # on where the file lies and which is CWD. We still want to give the location of + # the file, so that the user can easier find the source (when looking at the .expected files) + script_filename = 'clients/tests/test-client.py' + self.assertTrue(os.path.abspath(frame.f_code.co_filename).endswith(script_filename)) + + calling_location = '%s:%d:%s()/%d' % (script_filename, frame.f_lineno, frame.f_code.co_name, calling_num) + + content_new = ('location: %s\n' % (calling_location)).encode('utf8') + \ + ('cmd: $NMCLI %s\n' % (' '.join([Util.quote(a) for a in args[1:]]))).encode('utf8') + \ + ('lang: %s\n' % (lang)).encode('utf8') + \ + ('returncode: %d\n' % (returncode)).encode('utf8') + \ + ('stdout: %d bytes\n>>>\n' % (len(stdout))).encode('utf8') + \ + stdout + \ + ('\n<<<\nstderr: %d bytes\n>>>\n' % (len(stderr))).encode('utf8') + \ + stderr + \ + '\n<<<\n'.encode('utf8') + + w = conf.get(ENV_NM_TEST_REGENERATE) + + if content_old is not None: + if content_old == content_new: + return + + if not w: + if ignore_l10n_diff: + self._skip_test_for_l10n_diff.append(test_name) + return + print("\n\n\nThe file '%s' does not have the expected content:" % (filename)) + print("ACTUAL OUTPUT:\n[[%s]]\n" % (content_new)) + print("EXPECT OUTPUT:\n[[%s]]\n" % (content_old)) + print("Let the test write the file by rerunning with NM_TEST_REGENERATE=1\n\n") + raise AssertionError("Unexpected output of command, expected %s. Rerun test with NM_TEST_REGENERATE=1 to regenerate files" % (filename)) + else: + if not w: + self.fail("The file '%s' does not exist. Let the test write the file by rerunning with NM_TEST_REGENERATE=1" % (filename)) + + try: + if not os.path.exists(dirname): + os.makedirs(dirname) + with open(filename, 'wb') as content_file: + content_file.write(content_new) + except Exception as e: + self.fail("Failure to write '%s': %s" % (filename, e)) + + def setUp(self): + if not dbus_session_inited: + self.skipTest("Own D-Bus session for testing is not initialized. Do you have dbus-run-session available?") + self.srv = NMStubServer() + self._calling_num = {} + self._skip_test_for_l10n_diff = [] + + def tearDown(self): + self.srv.shutdown() + self.srv = None + self._calling_num = None + if self._skip_test_for_l10n_diff: + # nmcli loads translations from the installation path. This failure commonly + # happens because you did not install the binary in the --prefix, before + # running the test. Hence, translations are not available or differ. + msg = "Skipped asserting for localized tests %s. Set NM_TEST_CLIENT_CHECK_L10N=1 to force fail." % (','.join(self._skip_test_for_l10n_diff)) + if Util.python_has_version(3): + # python2 does not suppot skipping the test during tearDown() + self.skipTest(msg) + print(msg + "\n") + self._skip_test_for_l10n_diff = None + + def init_001(self): + self.srv.op_AddObj('WiredDevice', + iface = 'eth0') + self.srv.op_AddObj('WifiDevice', + iface = 'wlan0') + self.srv.op_AddObj('WifiDevice', + iface = 'wlan1') + + # add another device with an identical ifname. The D-Bus API itself + # does not enforce the ifnames are unique. + self.srv.op_AddObj('WifiDevice', + ident = 'wlan1/x', + iface = 'wlan1') + + self.srv.op_AddObj('WifiAp', + device = 'wlan0') + self.srv.op_AddObj('WifiAp', + device = 'wlan0') + self.srv.op_AddObj('WifiAp', + device = 'wlan0') + + self.srv.op_AddObj('WifiAp', + device = 'wlan1') + + self.srv.addConnection( { + 'connection': { + 'type': '802-3-ethernet', + 'id': 'con-1', + }, + }) + + def test_001(self): + + self.call_nmcli([]) + self.call_nmcli([], lang = 'pl') + + self.call_nmcli(['-f', 'AP', '-mode', 'multiline', '-p', 'd', 'show', 'wlan0']) + self.call_nmcli(['-f', 'AP', '-mode', 'multiline', '-p', 'd', 'show', 'wlan0'], lang = 'de') + + self.call_nmcli(['c', 's']) + + self.call_nmcli(['bogus', 's']) + + def test_002(self): + self.init_001() + + self.call_nmcli(['d']) + + self.call_nmcli(['-f', 'all', 'd']) + + self.call_nmcli([]) + + self.call_nmcli(['-f', 'AP', '-mode', 'multiline', 'd', 'show', 'wlan0']) + self.call_nmcli(['-f', 'AP', '-mode', 'multiline', '-p', 'd', 'show', 'wlan0']) + self.call_nmcli(['-f', 'AP', '-mode', 'multiline', '-t', 'd', 'show', 'wlan0']) + self.call_nmcli(['-f', 'AP', '-mode', 'tabular', 'd', 'show', 'wlan0']) + self.call_nmcli(['-f', 'AP', '-mode', 'tabular', '-p', 'd', 'show', 'wlan0']) + self.call_nmcli(['-f', 'AP', '-mode', 'tabular', '-t', 'd', 'show', 'wlan0']) + + self.call_nmcli(['-f', 'AP', '-mode', 'multiline', 'd', 'show', 'wlan0'], lang = 'pl') + self.call_nmcli(['-f', 'AP', '-mode', 'multiline', '-p', 'd', 'show', 'wlan0'], lang = 'pl') + self.call_nmcli(['-f', 'AP', '-mode', 'multiline', '-t', 'd', 'show', 'wlan0'], lang = 'pl') + self.call_nmcli(['-f', 'AP', '-mode', 'tabular', 'd', 'show', 'wlan0'], lang = 'pl') + self.call_nmcli(['-f', 'AP', '-mode', 'tabular', '-p', 'd', 'show', 'wlan0'], lang = 'pl') + self.call_nmcli(['-f', 'AP', '-mode', 'tabular', '-t', 'd', 'show', 'wlan0'], lang = 'pl') + + self.call_nmcli(['c']) + + self.call_nmcli(['c', 's', 'con-1']) + + def test_003(self): + self.init_001() + + replace_stdout = [] + + replace_stdout.append((lambda: self.srv.findConnectionUuid('con-xx1'), 'UUID-con-xx1-REPLACED-REPLACED-REPLA')) + + self.call_nmcli(['c', 'add', 'type', 'ethernet', 'ifname', '*', 'con-name', 'con-xx1'], + replace_stdout = replace_stdout) + + self.call_nmcli(['c', 's'], + replace_stdout = replace_stdout) + + replace_stdout.append((lambda: self.srv.findConnectionUuid('ethernet'), 'UUID-ethernet-REPLACED-REPLACED-REPL')) + + self.call_nmcli(['c', 'add', 'type', 'ethernet', 'ifname', '*'], + replace_stdout = replace_stdout) + + self.call_nmcli(['c', 's'], + replace_stdout = replace_stdout) + self.call_nmcli(['c', 's'], lang = 'pl', + replace_stdout = replace_stdout) + + self.call_nmcli(['-f', 'ALL', 'c', 's'], + replace_stdout = replace_stdout) + self.call_nmcli(['-f', 'ALL', 'c', 's'], lang = 'pl', + replace_stdout = replace_stdout) + + self.call_nmcli(['--complete-args', '-f', 'ALL', 'c', 's', ''], lang = 'pl', + replace_stdout = replace_stdout, + sort_lines_stdout = True) + +############################################################################### + +def main(): + global dbus_session_inited + + if len(sys.argv) >= 2 and sys.argv[1] == '--started-with-dbus-session': + dbus_session_inited = True + del sys.argv[1] + + if not dbus_session_inited: + # we don't have yet our own dbus-session. Reexec ourself with + # a new dbus-session. + try: + try: + os.execlp('dbus-run-session', 'dbus-run-session', '--', sys.executable, __file__, '--started-with-dbus-session', *sys.argv[1:]) + except OSError as e: + if e.errno != errno.ENOENT: + raise + # we have no dbus-run-session in path? Fall-through + # to skip tests gracefully + else: + raise Exception('unknown error during exec') + except Exception as e: + assert False, ("Failure to re-exec dbus-run-session: %s" % (str(e))) + + if not dbus_session_inited: + # we still don't have a D-Bus session. Probably dbus-run-session is not available. + # retry with dbus-launch + if os.system('type dbus-launch 1>/dev/null') == 0: + try: + os.execlp('bash', 'bash', '-e', '-c', + 'eval `dbus-launch --sh-syntax`;\n' + \ + 'trap "kill $DBUS_SESSION_BUS_PID" EXIT;\n' + \ + '\n' + \ + ' '.join([Util.quote(a) for a in [sys.executable, __file__, '--started-with-dbus-session'] + sys.argv[1:]]) + ' \n' + \ + '') + except Exception as e: + m = str(e) + else: + m = 'unknown error' + assert False, ('Failure to re-exec to start script with dbus-launch: %s' % (m)) + + unittest.main() + +if __name__ == '__main__': + main() diff --git a/libnm/nm-client.c b/libnm/nm-client.c index f74851228..18d3e318c 100644 --- a/libnm/nm-client.c +++ b/libnm/nm-client.c @@ -110,6 +110,7 @@ typedef struct { GDBusObjectManager *object_manager; GCancellable *new_object_manager_cancellable; struct udev *udev; + bool udev_inited:1; } NMClientPrivate; enum { @@ -2603,9 +2604,14 @@ obj_nm_for_gdbus_object (NMClient *self, GDBusObject *object, GDBusObjectManager NULL); if (NM_IS_DEVICE (obj_nm)) { priv = NM_CLIENT_GET_PRIVATE (self); - if (!priv->udev) - priv->udev = udev_new (); - _nm_device_set_udev (NM_DEVICE (obj_nm), priv->udev); + if (G_UNLIKELY (!priv->udev_inited)) { + priv->udev_inited = TRUE; + /* for testing, we don't want to use udev in libnm. */ + if (!nm_streq0 (g_getenv ("LIBNM_USE_NO_UDEV"), "1")) + priv->udev = udev_new (); + } + if (priv->udev) + _nm_device_set_udev (NM_DEVICE (obj_nm), priv->udev); } g_object_set_qdata_full (G_OBJECT (object), _nm_object_obj_nm_quark (), obj_nm, g_object_unref); diff --git a/tools/test-networkmanager-service.py b/tools/test-networkmanager-service.py index 17bd7e28c..ed435e67f 100755 --- a/tools/test-networkmanager-service.py +++ b/tools/test-networkmanager-service.py @@ -21,6 +21,40 @@ import dbus.mainloop.glib import random import collections import uuid +import hashlib + +######################################################### + +class TestError(AssertionError): + def __init__(self, message = 'Unspecified error', errors = None): + AssertionError.__init__(self, message) + self.errors = errors + +def pseudorandom_stream(seed, length = None): + seed = str(seed) + v = None + i = 0 + while length is None or length > 0: + if not v: + s = seed + str(i) + s = s.encode('utf8') + v = hashlib.sha256(s).hexdigest() + i += 1 + yield int(v[0:2], 16) + v = v[2:] + if length is not None: + length -= 1 + +def pseudorandom_num(seed, v_end, v_start = 0): + n = 0 + span = v_end - v_start + for r in pseudorandom_stream(seed): + n = n * 256 + r + if n > span: + break + return v_start + (n % span) + +######################################################### mainloop = GLib.MainLoop() @@ -75,6 +109,7 @@ NM_ACTIVE_CONNECTION_STATE_DEACTIVATING = 3 NM_ACTIVE_CONNECTION_STATE_DEACTIVATED = 4 ######################################################### + IFACE_DBUS = 'org.freedesktop.DBus' class UnknownInterfaceException(dbus.DBusException): @@ -98,9 +133,21 @@ class ExportedObj(dbus.service.Object): DBusInterface = collections.namedtuple('DBusInterface', ['dbus_iface', 'get_props_func', 'prop_changed_func']) - def __init__(self, bus, object_path): + def __init__(self, bus, object_path, ident = None): dbus.service.Object.__init__(self, bus, object_path) self._bus = bus + + # ident is an optional (unique) identifier for the instance. + # The test driver may set it to reference to the object by + # this identifier. For NetworkManager, the real ID of an + # object on D-Bus is the object_path. But that is generated + # by the stub server only after the test user created the + # object. The ident parameter may be specified by the user + # and thus can be hard-coded in the test. + if ident is None: + ident = object_path + self.ident = ident + self.path = object_path self.__ensure_dbus_ifaces() object_manager.add_object(self) @@ -176,7 +223,11 @@ PD_AVAILABLE_CONNECTIONS = "AvailableConnections" class Device(ExportedObj): counter = 1 - def __init__(self, bus, iface, devtype): + def __init__(self, bus, iface, devtype, ident = None): + + if ident is None: + ident = iface + object_path = "/org/freedesktop/NetworkManager/Devices/%d" % Device.counter Device.counter = Device.counter + 1 @@ -192,7 +243,7 @@ class Device(ExportedObj): self.available_connections = [] self.add_dbus_interface(IFACE_DEVICE, self.__get_props, Device.PropertiesChanged) - ExportedObj.__init__(self, bus, object_path) + ExportedObj.__init__(self, bus, object_path, ident) # Properties interface def __get_props(self): @@ -236,11 +287,12 @@ class Device(ExportedObj): ################################################################### -def random_mac(): - return '%02X:%02X:%02X:%02X:%02X:%02X' % ( - random.randint(0, 255), random.randint(0, 255), random.randint(0, 255), - random.randint(0, 255), random.randint(0, 255), random.randint(0, 255) - ) +def random_mac(seed = None): + if seed is None: + r = tuple([random.randint(0, 255) for x in range(6)]) + else: + r = tuple(pseudorandom_stream(seed, 6)) + return '%02X:%02X:%02X:%02X:%02X:%02X' % r ################################################################### IFACE_WIRED = 'org.freedesktop.NetworkManager.Device.Wired' @@ -252,17 +304,17 @@ PE_CARRIER = "Carrier" PE_S390_SUBCHANNELS = "S390Subchannels" class WiredDevice(Device): - def __init__(self, bus, iface, mac, subchannels): - + def __init__(self, bus, iface, mac = None, subchannels = None, ident = None): if mac is None: - self.mac = random_mac() - else: - self.mac = mac + mac = random_mac(iface if ident is None else ident) + if subchannels is None: + subchannels = dbus.Array(signature = 's') + self.mac = mac self.carrier = False self.s390_subchannels = subchannels self.add_dbus_interface(IFACE_WIRED, self.__get_props, WiredDevice.PropertiesChanged) - Device.__init__(self, bus, iface, NM_DEVICE_TYPE_ETHERNET) + Device.__init__(self, bus, iface, NM_DEVICE_TYPE_ETHERNET, ident) # Properties interface def __get_props(self): @@ -289,13 +341,13 @@ PV_CARRIER = "Carrier" PV_VLAN_ID = "VlanId" class VlanDevice(Device): - def __init__(self, bus, iface): - self.mac = random_mac() + def __init__(self, bus, iface, ident = None): + self.mac = random_mac(iface if ident is None else ident) self.carrier = False self.vlan_id = 1 self.add_dbus_interface(IFACE_VLAN, self.__get_props, VlanDevice.PropertiesChanged) - Device.__init__(self, bus, iface, NM_DEVICE_TYPE_VLAN) + Device.__init__(self, bus, iface, NM_DEVICE_TYPE_VLAN, ident) # Properties interface def __get_props(self): @@ -325,24 +377,35 @@ PP_STRENGTH = "Strength" class WifiAp(ExportedObj): counter = 0 - def __init__(self, bus, ssid, mac, flags, wpaf, rsnf, freq): + def __init__(self, bus, ssid, bssid = None, flags = None, wpaf = None, rsnf = None, freq = None, strength = None, ident = None): path = "/org/freedesktop/NetworkManager/AccessPoint/%d" % WifiAp.counter WifiAp.counter = WifiAp.counter + 1 + if flags is None: + flags = 0x1 + if wpaf is None: + wpaf = 0x1cc + if rsnf is None: + rsnf = 0x1cc + if freq is None: + freq = 2412 + if bssid is None: + bssid = random_mac(path) + if strength is None: + strength = pseudorandom_num(path, 100) + self.ssid = ssid - if mac: - self.bssid = mac - else: - self.bssid = random_mac() + self.bssid = bssid self.flags = flags self.wpaf = wpaf self.rsnf = rsnf self.freq = freq - self.strength = random.randint(0, 100) + self.strength = strength + self.strength_counter = 0 self.strength_id = GLib.timeout_add_seconds(10, self.strength_cb, None) self.add_dbus_interface(IFACE_WIFI_AP, self.__get_props, WifiAp.PropertiesChanged) - ExportedObj.__init__(self, bus, path) + ExportedObj.__init__(self, bus, path, ident) def __del__(self): if self.strength_id > 0: @@ -350,7 +413,8 @@ class WifiAp(ExportedObj): self.strength_id = 0 def strength_cb(self, ignored): - self.strength = random.randint(0, 100) + self.strength_counter += 1 + self.strength = pseudorandom_num(self.path + str(self.strength_counter), 100) self.__notify(PP_STRENGTH) return True @@ -390,13 +454,15 @@ PW_ACTIVE_ACCESS_POINT = "ActiveAccessPoint" PW_WIRELESS_CAPABILITIES = "WirelessCapabilities" class WifiDevice(Device): - def __init__(self, bus, iface): - self.mac = random_mac() + def __init__(self, bus, iface, mac = None, ident = None): + if mac is None: + mac = random_mac(iface if ident is None else ident) + self.mac = mac self.aps = [] self.active_ap = None self.add_dbus_interface(IFACE_WIFI, self.__get_props, WifiDevice.PropertiesChanged) - Device.__init__(self, bus, iface, NM_DEVICE_TYPE_WIFI) + Device.__init__(self, bus, iface, NM_DEVICE_TYPE_WIFI, ident) # methods @dbus.service.method(dbus_interface=IFACE_WIFI, in_signature='', out_signature='ao') @@ -421,6 +487,7 @@ class WifiDevice(Device): self.aps.append(ap) self.__notify(PW_ACCESS_POINTS) self.AccessPointAdded(to_path(ap)) + return ap @dbus.service.signal(IFACE_WIFI, signature='o') def AccessPointRemoved(self, ap_path): @@ -450,12 +517,6 @@ class WifiDevice(Device): def PropertiesChanged(self, changed): pass - # test functions - def add_test_ap(self, ssid, mac): - ap = WifiAp(self._bus, ssid, mac, 0x1, 0x1cc, 0x1cc, 2412) - self.add_ap(ap) - return ap - def remove_ap_by_path(self, path): for ap in self.aps: if ap.path == path: @@ -526,14 +587,14 @@ PX_BSID = "Bsid" PX_ACTIVE_NSP = "ActiveNsp" class WimaxDevice(Device): - def __init__(self, bus, iface): - self.mac = random_mac() - self.bsid = random_mac() + def __init__(self, bus, iface, ident = None): + self.mac = random_mac(iface if ident is None else ident) + self.bsid = random_mac(iface if ident is None else ident) self.nsps = [] self.active_nsp = None self.add_dbus_interface(IFACE_WIMAX, self.__get_props, WimaxDevice.PropertiesChanged) - Device.__init__(self, bus, iface, NM_DEVICE_TYPE_WIMAX) + Device.__init__(self, bus, iface, NM_DEVICE_TYPE_WIMAX, ident) # methods @dbus.service.method(dbus_interface=IFACE_WIMAX, in_signature='', out_signature='ao') @@ -841,11 +902,20 @@ class NetworkManager(ExportedObj): def DeviceAdded(self, devpath): pass + def find_device(self, ident): + for d in self.devices: + if d.ident == ident: + return d + def add_device(self, device): + d = self.find_device(device.ident) + if d: + raise TestError("Device with ident=%s already added (%s)" % (device.ident, d.path)) self.devices.append(device) self.__notify(PM_DEVICES) self.__notify(PM_ALL_DEVICES) self.DeviceAdded(to_path(device)) + return device @dbus.service.signal(IFACE_NM, signature='o') def DeviceRemoved(self, devpath): @@ -890,32 +960,43 @@ class NetworkManager(ExportedObj): def Quit(self): mainloop.quit() + @dbus.service.method(IFACE_TEST, in_signature='a{ss}', out_signature='a(sss)') + def FindConnections(self, args): + return [(c.path, c.get_uuid(), c.get_id()) for c in settings.find_connections(**args)] + + @dbus.service.method(IFACE_TEST, in_signature='sa{sv}', out_signature='o') + def AddObj(self, class_name, args): + if class_name in ['WiredDevice', 'WifiDevice']: + py_class = globals()[class_name] + d = py_class(self._bus, **args) + return to_path(self.add_device(d)) + elif class_name in ['WifiAp']: + if 'device' not in args: + raise TestError('missing "device" paramter') + d = self.find_device(args['device']) + if not d: + raise TestError('no device "%s" found' % args['device']) + del args['device'] + if 'ssid' not in args: + args['ssid'] = d.ident + '-ap-' + str(WifiAp.counter + 1) + ap = WifiAp(self._bus, **args) + return to_path(d.add_ap(ap)) + raise TestError("Invalid python type \"%s\"" % (class_name)) + @dbus.service.method(IFACE_TEST, in_signature='ssas', out_signature='o') def AddWiredDevice(self, ifname, mac, subchannels): - for d in self.devices: - if d.iface == ifname: - raise PermissionDeniedException("Device already added") dev = WiredDevice(self._bus, ifname, mac, subchannels) - self.add_device(dev) - return to_path(dev) + return to_path(self.add_device(dev)) @dbus.service.method(IFACE_TEST, in_signature='s', out_signature='o') def AddWifiDevice(self, ifname): - for d in self.devices: - if d.iface == ifname: - raise PermissionDeniedException("Device already added") dev = WifiDevice(self._bus, ifname) - self.add_device(dev) - return to_path(dev) + return to_path(self.add_device(dev)) @dbus.service.method(IFACE_TEST, in_signature='s', out_signature='o') def AddWimaxDevice(self, ifname): - for d in self.devices: - if d.iface == ifname: - raise PermissionDeniedException("Device already added") dev = WimaxDevice(self._bus, ifname) - self.add_device(dev) - return to_path(dev) + return to_path(self.add_device(dev)) @dbus.service.method(IFACE_TEST, in_signature='o', out_signature='') def RemoveDevice(self, path): @@ -926,10 +1007,11 @@ class NetworkManager(ExportedObj): raise UnknownDeviceException("Device not found") @dbus.service.method(IFACE_TEST, in_signature='sss', out_signature='o') - def AddWifiAp(self, ifname, ssid, mac): - for d in self.devices: - if d.iface == ifname: - return to_path(d.add_test_ap(ssid, mac)) + def AddWifiAp(self, ifname, ssid, bssid): + d = self.find_device(ifname) + if d: + ap = WifiAp(self._bus, ssid, bssid) + return to_path(d.add_ap(ap)) raise UnknownDeviceException("Device not found") @dbus.service.method(IFACE_TEST, in_signature='so', out_signature='') @@ -989,15 +1071,19 @@ class MissingSettingException(dbus.DBusException): _dbus_error_name = IFACE_CONNECTION + '.MissingSetting' class Connection(ExportedObj): - def __init__(self, bus, object_path, settings, remove_func, verify_connection=True): + def __init__(self, bus, path_counter, settings, remove_func, verify_connection=True): + path = "/org/freedesktop/NetworkManager/Settings/Connection/%s" % (path_counter) + + if 'connection' not in settings: + settings['connection'] = { } + if self.get_id(settings) is None: + settings['connection']['id'] = 'connection-%s' % (path_counter) if self.get_uuid(settings) is None: - if 'connection' not in settings: - settings['connection'] = { } - settings['connection']['uuid'] = uuid.uuid4() + settings['connection']['uuid'] = str(uuid.uuid3(uuid.NAMESPACE_URL, path)) self.verify(settings, verify_strict=verify_connection) - self.path = object_path + self.path = path self.settings = settings self.remove_func = remove_func self.visible = True @@ -1005,7 +1091,16 @@ class Connection(ExportedObj): self.props['Unsaved'] = False self.add_dbus_interface(IFACE_CONNECTION, self.__get_props, None) - ExportedObj.__init__(self, bus, object_path) + ExportedObj.__init__(self, bus, path) + + def get_id(self, settings=None): + if settings is None: + settings = self.settings + if 'connection' in settings: + s_con = settings['connection'] + if 'id' in s_con: + return s_con['id'] + return None def get_uuid(self, settings=None): if settings is None: @@ -1092,7 +1187,7 @@ class Settings(ExportedObj): def __init__(self, bus, object_path): self.connections = {} self.bus = bus - self.counter = 1 + self.counter = 0 self.remove_next_connection = False self.props = {} self.props['Hostname'] = "foobar.baz" @@ -1108,6 +1203,19 @@ class Settings(ExportedObj): def get_connection(self, path): return self.connections[path] + def find_connections(self, path = None, con_id = None, con_uuid = None): + for c in self.connections.values(): + if path is not None: + if c.path != path: + continue + if con_id is not None: + if c.get_id() != con_id: + continue + if con_uuid is not None: + if c.get_uuid() != con_uuid: + continue + yield c + @dbus.service.method(dbus_interface=IFACE_SETTINGS, in_signature='', out_signature='ao') def ListConnections(self): return self.connections.keys() @@ -1117,24 +1225,23 @@ class Settings(ExportedObj): return self.add_connection(settings) def add_connection(self, settings, verify_connection=True): - path = "/org/freedesktop/NetworkManager/Settings/Connection/{0}".format(self.counter) - con = Connection(self.bus, path, settings, self.delete_connection, verify_connection) + self.counter += 1 + con = Connection(self.bus, self.counter, settings, self.delete_connection, verify_connection) uuid = con.get_uuid() if uuid in [c.get_uuid() for c in self.connections.values()]: raise InvalidSettingException('cannot add duplicate connection with uuid %s' % (uuid)) - self.counter = self.counter + 1 - self.connections[path] = con + self.connections[con.path] = con self.props['Connections'] = dbus.Array(self.connections.keys(), 'o') - self.NewConnection(path) + self.NewConnection(con.path) self.__notify('Connections') if self.remove_next_connection: self.remove_next_connection = False - self.connections[path].Delete() + self.connections[con.path].Delete() - return path + return con.path def update_connection(self, connection, path=None, verify_connection=True): if path is None: From 6d34d5e41bc5684f3787852fd280e23fd04b4001 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 9 May 2018 12:46:34 +0200 Subject: [PATCH 8/8] tests: refactor finding device in NetworkManager stub - add find_devices() and find_device_first() functions, to not re-implement iterating the device list. - for test functions, accept the device's "ident", instead of ifname. The "ident" must b unique, contrary to the "ifname". --- tools/test-networkmanager-service.py | 106 +++++++++++++-------------- 1 file changed, 51 insertions(+), 55 deletions(-) diff --git a/tools/test-networkmanager-service.py b/tools/test-networkmanager-service.py index ed435e67f..e4246d31b 100755 --- a/tools/test-networkmanager-service.py +++ b/tools/test-networkmanager-service.py @@ -23,6 +23,8 @@ import collections import uuid import hashlib +_DEFAULT_ARG = object() + ######################################################### class TestError(AssertionError): @@ -786,11 +788,8 @@ class NetworkManager(ExportedObj): @dbus.service.method(dbus_interface=IFACE_NM, in_signature='s', out_signature='o') def GetDeviceByIpIface(self, ip_iface): - for d in self.devices: - # ignore iface/ip_iface distinction for now - if d.iface == ip_iface: - return to_path(d) - raise UnknownDeviceException("No device found for the requested iface.") + d = self.find_device_first(ip_iface = ip_iface, require = UnknownDeviceException) + return to_path(d) @dbus.service.method(dbus_interface=IFACE_NM, in_signature='ooo', out_signature='o') def ActivateConnection(self, conpath, devpath, specific_object): @@ -802,11 +801,7 @@ class NetworkManager(ExportedObj): hash = connection.GetSettings() s_con = hash['connection'] - device = None - for d in self.devices: - if d.path == devpath: - device = d - break + device = self.find_device_first(path = devpath) if not device and s_con['type'] == 'vlan': ifname = s_con['interface-name'] device = VlanDevice(self._bus, ifname) @@ -842,14 +837,7 @@ class NetworkManager(ExportedObj): @dbus.service.method(dbus_interface=IFACE_NM, in_signature='a{sa{sv}}oo', out_signature='oo') def AddAndActivateConnection(self, connection, devpath, specific_object): - device = None - for d in self.devices: - if d.path == devpath: - device = d - break - if not device: - raise UnknownDeviceException("No device found for the requested iface.") - + device = self.find_device_first(path = devpath, require = UnknownDeviceException) conpath = settings.AddConnection(connection) return (conpath, self.ActivateConnection(conpath, devpath, specific_object)) @@ -902,15 +890,38 @@ class NetworkManager(ExportedObj): def DeviceAdded(self, devpath): pass - def find_device(self, ident): + def find_devices(self, ident = _DEFAULT_ARG, path = _DEFAULT_ARG, iface = _DEFAULT_ARG, ip_iface = _DEFAULT_ARG): + r = None for d in self.devices: - if d.ident == ident: - return d + if ident is not _DEFAULT_ARG: + if d.ident != ident: + continue + if path is not _DEFAULT_ARG: + if d.path != path: + continue + if iface is not _DEFAULT_ARG: + if d.iface != iface: + continue + if ip_iface is not _DEFAULT_ARG: + # ignore iface/ip_iface distinction for now + if d.iface != ip_iface: + continue + yield d + + def find_device_first(self, ident = _DEFAULT_ARG, path = _DEFAULT_ARG, iface = _DEFAULT_ARG, ip_iface = _DEFAULT_ARG, require = None): + r = None + for d in self.find_devices(ident = ident, path = path, iface = iface, ip_iface = ip_iface): + r = d + break + if r is None and require: + if require is TestError: + raise TestError('Device not found') + raise UnknownDeviceException('Device not found') + return r def add_device(self, device): - d = self.find_device(device.ident) - if d: - raise TestError("Device with ident=%s already added (%s)" % (device.ident, d.path)) + if self.find_device_first(ident = device.ident, path = device.path) is not None: + raise TestError("Duplicate device ident=%s / path=%s" % (device.ident, device.path)) self.devices.append(device) self.__notify(PM_DEVICES) self.__notify(PM_ALL_DEVICES) @@ -973,9 +984,7 @@ class NetworkManager(ExportedObj): elif class_name in ['WifiAp']: if 'device' not in args: raise TestError('missing "device" paramter') - d = self.find_device(args['device']) - if not d: - raise TestError('no device "%s" found' % args['device']) + d = self.find_device_first(ident = args['device'], require = TestError) del args['device'] if 'ssid' not in args: args['ssid'] = d.ident + '-ap-' + str(WifiAp.counter + 1) @@ -1000,42 +1009,29 @@ class NetworkManager(ExportedObj): @dbus.service.method(IFACE_TEST, in_signature='o', out_signature='') def RemoveDevice(self, path): - for d in self.devices: - if d.path == path: - self.remove_device(d) - return - raise UnknownDeviceException("Device not found") + d = self.find_device_first(path = path, require = TestError) + self.remove_device(d) @dbus.service.method(IFACE_TEST, in_signature='sss', out_signature='o') - def AddWifiAp(self, ifname, ssid, bssid): - d = self.find_device(ifname) - if d: - ap = WifiAp(self._bus, ssid, bssid) - return to_path(d.add_ap(ap)) - raise UnknownDeviceException("Device not found") + def AddWifiAp(self, ident, ssid, bssid): + d = self.find_device_first(ident = ident, require = TestError) + ap = WifiAp(self._bus, ssid, bssid) + return to_path(d.add_ap(ap)) @dbus.service.method(IFACE_TEST, in_signature='so', out_signature='') - def RemoveWifiAp(self, ifname, ap_path): - for d in self.devices: - if d.iface == ifname: - d.remove_ap_by_path(ap_path) - return - raise UnknownDeviceException("Device not found") + def RemoveWifiAp(self, ident, ap_path): + d = self.find_device_first(ident = ident, require = TestError) + d.remove_ap_by_path(ap_path) @dbus.service.method(IFACE_TEST, in_signature='ss', out_signature='o') - def AddWimaxNsp(self, ifname, name): - for d in self.devices: - if d.iface == ifname: - return to_path(d.add_test_nsp(name)) - raise UnknownDeviceException("Device not found") + def AddWimaxNsp(self, ident, name): + d = self.find_device_first(ident = ident, require = TestError) + return to_path(d.add_test_nsp(name)) @dbus.service.method(IFACE_TEST, in_signature='so', out_signature='') - def RemoveWimaxNsp(self, ifname, nsp_path): - for d in self.devices: - if d.iface == ifname: - d.remove_nsp_by_path(nsp_path) - return - raise UnknownDeviceException("Device not found") + def RemoveWimaxNsp(self, ident, nsp_path): + d = self.find_device_first(ident = ident, require = TestError) + d.remove_nsp_by_path(nsp_path) @dbus.service.method(IFACE_TEST, in_signature='', out_signature='') def AutoRemoveNextConnection(self):