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 638055c91..68faf421e 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" @@ -3407,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 @@ -3759,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 ############################################################################### @@ -4431,10 +4508,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 +4549,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/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/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/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/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/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/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/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/shared/nm-utils/nm-macros-internal.h b/shared/nm-utils/nm-macros-internal.h index bd375a35a..908b25fdb 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 */ @@ -1367,4 +1375,6 @@ nm_close (int fd) return r; } +#define NM_PID_T_INVAL ((pid_t) -1) + #endif /* __NM_MACROS_INTERNAL_H__ */ 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. */ 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[@]}" \ diff --git a/tools/test-networkmanager-service.py b/tools/test-networkmanager-service.py index 68097778e..e4246d31b 100755 --- a/tools/test-networkmanager-service.py +++ b/tools/test-networkmanager-service.py @@ -3,14 +3,60 @@ 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 import random import collections import uuid +import hashlib + +_DEFAULT_ARG = object() + +######################################################### + +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() @@ -65,6 +111,7 @@ NM_ACTIVE_CONNECTION_STATE_DEACTIVATING = 3 NM_ACTIVE_CONNECTION_STATE_DEACTIVATED = 4 ######################################################### + IFACE_DBUS = 'org.freedesktop.DBus' class UnknownInterfaceException(dbus.DBusException): @@ -88,9 +135,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) @@ -166,7 +225,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 @@ -182,7 +245,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): @@ -226,11 +289,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' @@ -242,17 +306,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): @@ -279,13 +343,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): @@ -315,24 +379,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: @@ -340,7 +415,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 @@ -380,13 +456,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') @@ -411,6 +489,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): @@ -440,12 +519,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: @@ -516,14 +589,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') @@ -715,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): @@ -731,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) @@ -771,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)) @@ -831,11 +890,43 @@ class NetworkManager(ExportedObj): def DeviceAdded(self, devpath): pass + 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 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): + 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) self.DeviceAdded(to_path(device)) + return device @dbus.service.signal(IFACE_NM, signature='o') def DeviceRemoved(self, devpath): @@ -880,70 +971,67 @@ 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_first(ident = args['device'], require = TestError) + 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): - 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, mac): - for d in self.devices: - if d.iface == ifname: - return to_path(d.add_test_ap(ssid, mac)) - 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): @@ -979,15 +1067,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 @@ -995,7 +1087,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: @@ -1082,7 +1183,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" @@ -1098,6 +1199,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() @@ -1107,24 +1221,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: