summary refs log tree commit diff
path: root/tools
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2021-02-11 18:11:46 +0100
committerMichael Biebl <biebl@debian.org>2021-02-11 18:11:46 +0100
commit80ec1decc49c72efec2a8b87c06245c92c0ab807 (patch)
treee3b229aa94e8dcf0590f2317664176e7b8f7607b /tools
parent65f86e8f56267192d42f2b629fc6b0c99fb9cd0c (diff)
New upstream version 1.29.90 upstream/1.29.90
Diffstat (limited to 'tools')
-rwxr-xr-xtools/check-compare-generated.sh18
-rwxr-xr-xtools/check-config-options.sh8
-rwxr-xr-xtools/check-docs.sh1
-rwxr-xr-xtools/check-gitlab-ci.sh37
-rwxr-xr-xtools/check-settings-docs.sh19
-rwxr-xr-xtools/check-tree.sh24
-rwxr-xr-xtools/create-exports-NetworkManager.sh21
-rwxr-xr-xtools/enums-to-docbook.pl2
-rwxr-xr-xtools/generate-docs-nm-property-infos.pl2
-rwxr-xr-xtools/generate-docs-nm-settings-docs-gir.py2
-rwxr-xr-xtools/generate-docs-nm-settings-docs-merge.py2
-rwxr-xr-xtools/run-nm-test.sh68
-rwxr-xr-xtools/test-networkmanager-service.py270
13 files changed, 408 insertions, 66 deletions
diff --git a/tools/check-compare-generated.sh b/tools/check-compare-generated.sh
new file mode 100755
index 00000000..44b70f95
--- /dev/null
+++ b/tools/check-compare-generated.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+set -e
+
+srcdir="$1"
+builddir="$2"
+doc_h="$3"
+
+[ -n "$NMTST_NO_CHECK_SETTINGS_DOCS" ] && exit 0
+
+cmp -s "${srcdir}/${doc_h}.in" "${builddir}/${doc_h}" && exit 0
+
+if [ "$NM_TEST_REGENERATE" = 1 ] ; then
+   cp -f "${builddir}/${doc_h}" "${srcdir}/${doc_h}.in"
+else
+   echo "*** Error: the generated file '${builddir}/${doc_h}' differs from the source file '${srcdir}/${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"
+   exit 1
+fi
diff --git a/tools/check-config-options.sh b/tools/check-config-options.sh
index 7a775955..4cbd4855 100755
--- a/tools/check-config-options.sh
+++ b/tools/check-config-options.sh
@@ -5,21 +5,21 @@ ret=0
 
 get_supported_options()
 {
-    awk '/START OPTION LIST/{flag=1;next}/END OPTION LIST/{flag=0}flag' "$srcdir/src/nm-config.c" |
+    awk '/START OPTION LIST/{flag=1;next}/END OPTION LIST/{flag=0}flag' "$srcdir/src/core/nm-config.c" |
     grep -o 'NM_CONFIG_KEYFILE_KEY_\w*'
 }
 
 get_missing_options()
 {
-    grep -v '/\* check-config-options skip \*/' "$srcdir/src/nm-config.h" |
+    grep -v '/\* check-config-options skip \*/' "$srcdir/src/core/nm-config.h" |
     grep -o 'NM_CONFIG_KEYFILE_KEY_\w*' |
     grep -v -Fx -f <(get_supported_options)
 }
 
 get_src_con_defaults()
 {
-    sed -ne 's/.*\<NM_CON_DEFAULT\s*("\([^"]*\)").*/\1/p' $(find "$srcdir/src/" -name \*.c ! -name test\*.c)
-    sed -ne 's/.*\<NM_CON_DEFAULT_NOP\s*("\([^"]*\)").*/\1/p' $(find "$srcdir/src/" -name \*.c ! -name test\*.c)
+    sed -n 's/\<NM_CON_DEFAULT/\n\0/gp' $(find "$srcdir/src/core/" -name \*.c ! -name test\*.c) |
+    sed -n 's/.*\<NM_CON_DEFAULT\(_NOP\)\?\s*("\([^"]*\)").*/\2/p'
 }
 
 get_man_con_defaults()
diff --git a/tools/check-docs.sh b/tools/check-docs.sh
index e0ed7837..f4f05b91 100755
--- a/tools/check-docs.sh
+++ b/tools/check-docs.sh
@@ -71,6 +71,7 @@ annotation-glossary
 api-index-full
 nm-dbus-interface
 nm-errors
+nm-keyfile
 nm-utils
 nm-version
 "
diff --git a/tools/check-gitlab-ci.sh b/tools/check-gitlab-ci.sh
new file mode 100755
index 00000000..8ad0fbb2
--- /dev/null
+++ b/tools/check-gitlab-ci.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+set -e
+
+if [ "$NMTST_SKIP_CHECK_GITLAB_CI" = 1 ]; then
+    exit 0
+fi
+
+if [ $# -eq 0 ] ; then
+    BASEDIR="$(dirname "$(readlink -f "$0")")/.."
+elif [ $# -eq 1 ] ; then
+    BASEDIR="$1"
+else
+    echo "invalid arguments"
+    exit 1
+fi
+
+cd "$BASEDIR"
+
+if ! [ -f ./.gitlab-ci.yml ] ; then
+    # we have no gitlab-ci. Probably this is not a git-checkout
+    # but a dist'ed source-tree. Nothing to check.
+    exit 0
+fi
+
+if ! command -v ci-fairy &> /dev/null ; then
+    # ci-fairy not available. Cannot check.
+    exit 0
+fi
+
+
+if [ "$NM_TEST_REGENERATE" == 1 ] ; then
+    ci-fairy generate-template
+    exit 0
+fi
+
+diff "./.gitlab-ci.yml" <(ci-fairy generate-template -o /dev/stdout)
diff --git a/tools/check-settings-docs.sh b/tools/check-settings-docs.sh
deleted file mode 100755
index 8695ccc0..00000000
--- a/tools/check-settings-docs.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/sh
-
-srcdir=$1
-builddir=$2
-doc_h=$3
-
-if [ -z "$NMTST_NO_CHECK_SETTINGS_DOCS" ] ; then
-    if ! cmp -s "${srcdir}/${doc_h}.in" "${builddir}/${doc_h}"; then
-        if [ "$NM_TEST_REGENERATE" = 1 ] ; then
-            cp -f "${builddir}/${doc_h}" "${srcdir}/${doc_h}.in"
-        else
-            echo "*** Error: the generated file '${builddir}/${doc_h}' differs from the source file '${srcdir}/${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"
-            exit 1
-        fi
-    fi
-fi
-
-exit 0
-
diff --git a/tools/check-tree.sh b/tools/check-tree.sh
new file mode 100755
index 00000000..cff85c3e
--- /dev/null
+++ b/tools/check-tree.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+set -e
+
+TOP_DIR="$(dirname "$0")/.."
+
+die() {
+    printf 'FAIL: %s\n' "$*" >&2
+    exit 1
+}
+
+check_cmp() {
+    local a="$1"
+    local b="$2"
+
+    cmp "$TOP_DIR/$a" "$TOP_DIR/$b" && return 0
+
+    diff "$TOP_DIR/$a" "$TOP_DIR/$b" || :
+    die "files \"$a\" and \"$b\" differ!"
+}
+
+check_cmp shared/nm-base/nm-ethtool-utils-base.h libnm/nm-ethtool-utils.h
+check_cmp libnm-core/nm-meta-setting-base-impl.h clients/common/nm-meta-setting-base-impl.h
+check_cmp libnm-core/nm-meta-setting-base-impl.c clients/common/nm-meta-setting-base-impl.c
diff --git a/tools/create-exports-NetworkManager.sh b/tools/create-exports-NetworkManager.sh
index 6cb7c2e7..f7fd3bab 100755
--- a/tools/create-exports-NetworkManager.sh
+++ b/tools/create-exports-NetworkManager.sh
@@ -8,7 +8,7 @@ die() {
     exit 1
 }
 
-# generates the linker version script src/NetworkManager.ver
+# generates the linker version script src/core/NetworkManager.ver
 # by looking at the symbols needed by the device and settings
 # plugins. Note that this depends on how NetworkManager and
 # the plugins are build. For example, compiling without
@@ -40,7 +40,10 @@ call_nm() {
 }
 
 get_symbols_nm () {
-    base=./src/NetworkManager-all-sym
+    base=./src/core/.libs/NetworkManager-all-sym
+    if ! test -f "$base"; then
+        base=./src/core/NetworkManager-all-sym
+    fi
     call_nm "$base" |
         sed -n 's/^[tTDGRBS] //p' |
         _sort
@@ -53,9 +56,9 @@ EOF
 }
 
 get_symbols_missing() {
-    (for f in $(find ./src/settings/plugins/*/${libs} \
-                     ./src/devices/*/${libs} \
-                     ./src/ppp/${libs} -name '*.so' 2>/dev/null); do
+    (for f in $(find ./src/core/settings/plugins/*/${libs} \
+                     ./src/core/devices/*/${libs} \
+                     ./src/core/ppp/${libs} -name '*.so' 2>/dev/null); do
         call_nm "$f" |
             sed -n 's/^\([U]\) \(\(nm_\|nmp_\|_nm\|NM\|_NM\|nmtst_\|c_siphash_\|c_list_\).*\)$/\2/p'
     done) |
@@ -79,7 +82,7 @@ do_rebuild() {
 }
 
 do_update() {
-    do_generate > ./src/NetworkManager.ver
+    do_generate > ./src/core/NetworkManager.ver
 }
 
 SYMBOLS_MISSING="$(get_symbols_missing | pretty)"
@@ -107,7 +110,7 @@ else
     libs=.libs/
 fi
 
-test -f ./src/${libs}libNetworkManager.a || die "must be called from NetworkManager top build dir after building the tree"
+test -f ./src/core/${libs}libNetworkManager.a || die "must be called from NetworkManager top build dir after building the tree"
 
 case "$1" in
     rebuild)
@@ -122,8 +125,8 @@ case "$1" in
         if test -z "${NM_BUILD_NO_CREATE_EXPORTS+x}"; then
             do_update
         else
-            if test -f "./src/NetworkManager.ver"; then
-                touch ./src/NetworkManager.ver
+            if test -f "./src/core/NetworkManager.ver"; then
+                touch ./src/core/NetworkManager.ver
             fi
         fi
         ;;
diff --git a/tools/enums-to-docbook.pl b/tools/enums-to-docbook.pl
index f422acbd..8439eaee 100755
--- a/tools/enums-to-docbook.pl
+++ b/tools/enums-to-docbook.pl
@@ -1,5 +1,5 @@
 #!/usr/bin/perl -n
-# SPDX-License-Identifier: GPL-2.0+
+# SPDX-License-Identifier: GPL-2.0-or-later
 #
 # Copyright (C) 2016 Red Hat, Inc.
 #
diff --git a/tools/generate-docs-nm-property-infos.pl b/tools/generate-docs-nm-property-infos.pl
index bbdec3b6..f850ee74 100755
--- a/tools/generate-docs-nm-property-infos.pl
+++ b/tools/generate-docs-nm-property-infos.pl
@@ -1,5 +1,5 @@
 #!/usr/bin/env perl
-# SPDX-License-Identifier: GPL-2.0+
+# SPDX-License-Identifier: GPL-2.0-or-later
 #
 # Copyright (C) 2014 Red Hat, Inc.
 #
diff --git a/tools/generate-docs-nm-settings-docs-gir.py b/tools/generate-docs-nm-settings-docs-gir.py
index 317b3297..6df85190 100755
--- a/tools/generate-docs-nm-settings-docs-gir.py
+++ b/tools/generate-docs-nm-settings-docs-gir.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-# SPDX-License-Identifier: LGPL-2.1+
+# SPDX-License-Identifier: LGPL-2.1-or-later
 #
 # Copyright (C) 2009 - 2017 Red Hat, Inc.
 #
diff --git a/tools/generate-docs-nm-settings-docs-merge.py b/tools/generate-docs-nm-settings-docs-merge.py
index 0da3a0bb..5fe61997 100755
--- a/tools/generate-docs-nm-settings-docs-merge.py
+++ b/tools/generate-docs-nm-settings-docs-merge.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-# SPDX-License-Identifier: LGPL-2.1+
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 from __future__ import print_function
 
diff --git a/tools/run-nm-test.sh b/tools/run-nm-test.sh
index f452d9d4..6d835a8f 100755
--- a/tools/run-nm-test.sh
+++ b/tools/run-nm-test.sh
@@ -23,6 +23,43 @@ _is_true() {
     esac
 }
 
+usage() {
+    echo "$0 [\$OPTIONS] [--] \$TEST [\$TEST_OPTIONS]"
+    echo ""
+    echo "  Runs the unit test with setting up dbus-session (as necessary),"
+    echo "  optionally build the test first, and run valgrind"
+    echo ""
+    echo "  --help|-h: help"
+    echo "  --launch-dbus: the test runner by default automatically launches a D-Bus session"
+    echo "        depending on a hard-coded list of tests that require it. This flag overwrites"
+    echo "        the automatism to always launch a D-Bus session"
+    echo "  --no-launch-dbus|-D: prevent launching a D-Bus session"
+    echo "  --no-libtool: when running with valgrind, the script tries automatically to"
+    echo "        use libtool as necessary. This disables libtool usage" 
+    echo "  --make-first|-m: before running the test, make it (only works with autotools build)"
+    echo "  --valgrind|-v: run under valgrind"
+    echo "  --no-valgrind|-V: disable running under valgrind (overrides NMTST_USE_VALGRIND=1)"
+    echo "  -d: set NMTST_DEBUG=d"
+    echo "  --test|-t \$TEST: set the test that should be run"
+    echo ""
+    echo " With \"--test\" and \"--\" you can select the test and which arguments are"
+    echo " passed to the test. You can omit these, in which case the first unknown parameter"
+    echo " is the test and all other unknown parameters are passed to the test. For example"
+    echo "   $0 -m --test src/core/tests/test-core -- -p /general/match-spec/device"
+    echo " can also be called as"
+    echo "   $0 src/core/tests/test-core -p /general/match-spec/device -m"
+    echo ""
+    echo "  The following environment variables are honored:"
+    echo "    NMTST_USE_VALGRIND=0|1: enable/disable valgrind"
+    echo "    NMTST_LIBTOOL=: libtool path (or disable)"
+    echo "    NMTST_LAUNCH_DBUS=0|1: whether to lounch a D-Bus session"
+    echo "    NMTST_SET_DEBUG=0|1: saet NMTST_DEBUG=d"
+    echo ""
+    echo " This script is also called by the build system as test wrapper. In that case"
+    echo " different, internal command line syntax is used. In that case, environment variables"
+    echo " are still honored, so \`NMTST_USE_VALGRIND=1 make check\` works as expected"
+}
+
 SCRIPT_PATH="${SCRIPT_PATH:-$(readlink -f "$(dirname "$0")")}"
 
 VALGRIND_ERROR=37
@@ -90,6 +127,7 @@ if [ "$CALLED_FROM_MAKE" == 1 ]; then
     TEST="$1"; shift
     NMTST_MAKE_FIRST=0
 
+    TEST_ARGV=("$@")
 else
     if [[ -z "${NMTST_USE_VALGRIND+x}" ]]; then
         # by default, disable valgrind checks.
@@ -103,9 +141,14 @@ else
     else
         NMTST_LIBTOOL=("$NMTST_LIBTOOL" "--mode=execute")
     fi
+    TEST_ARGV=()
     unset TEST
     while test $# -gt 0; do
         case "$1" in
+        --help|-h)
+            usage
+            exit 0
+            ;;
         "--launch-dbus")
             NMTST_LAUNCH_DBUS=1
             shift
@@ -141,19 +184,26 @@ else
             ;;
         "--")
             shift
+            if test -z "${TEST+x}"; then
+                TEST="$1";
+                shift
+            fi
+            TEST_ARGV+=("$@")
             break
             ;;
         *)
-            break
+            if test -z "${TEST+x}"; then
+                TEST="$1";
+            else
+                TEST_ARGV+=("$1")
+            fi
+            shift
             ;;
         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
-        TEST="$1"; shift
-    fi
     if [[ -z "${NMTST_SUPPRESSIONS+x}" ]]; then
         NMTST_SUPPRESSIONS="$SCRIPT_PATH/../valgrind.suppressions"
     fi
@@ -178,6 +228,8 @@ if [ "$NMTST_SET_DEBUG" == 1 -a -z "${NMTST_DEBUG+x}" ]; then
     export NMTST_DEBUG=d
 fi
 
+[ -n "$TEST" ] || die "Missing test name. Specify it on the command line."
+
 if _is_true "$NMTST_MAKE_FIRST" 0; then
     git_dir="$(readlink -f "$(git rev-parse --show-toplevel)")"
     rel_path="$(realpath --relative-to="$git_dir" -m "$TEST" 2>/dev/null)" || die "cannot resolve test-name \"$TEST\". Did you call the script properly?"
@@ -207,7 +259,7 @@ fi
 
 NMTST_DBUS_RUN_SESSION=()
 if _is_true "$NMTST_LAUNCH_DBUS"; then
-    if ! which dbus-run-session &>/dev/null ; then
+    if ! command -v dbus-run-session &>/dev/null ; then
         eval `dbus-launch --sh-syntax`
         trap "kill $DBUS_SESSION_BUS_PID" EXIT
     else
@@ -244,12 +296,12 @@ fi
 if ! _is_true "$NMTST_USE_VALGRIND" 0; then
     export NM_TEST_UNDER_VALGRIND=0
     exec "${NMTST_DBUS_RUN_SESSION[@]}" \
-    "$TEST" "$@"
+    "$TEST" "${TEST_ARGV[@]}"
     die "exec \"$TEST\" failed"
 fi
 
 if [[ -z "${NMTST_VALGRIND}" ]]; then
-    NMTST_VALGRIND=`which valgrind` || die "cannot find valgrind binary. Set \$NMTST_VALGRIND"
+    NMTST_VALGRIND="$(command -v valgrind)" || die "cannot find valgrind binary. Set \$NMTST_VALGRIND"
 else
     test -e "${NMTST_VALGRIND}" || die "cannot find valgrind binary from NMTST_VALGRIND=\"${NMTST_VALGRIND}\""
 fi
@@ -276,7 +328,7 @@ export NM_TEST_UNDER_VALGRIND=1
     --num-callers=100 \
     --log-file="$LOGFILE" \
     "$TEST" \
-    "$@"
+    "${TEST_ARGV[@]}"
 RESULT=$?
 
 test -s "$LOGFILE"
diff --git a/tools/test-networkmanager-service.py b/tools/test-networkmanager-service.py
index 359c5e5d..fe5e4d44 100755
--- a/tools/test-networkmanager-service.py
+++ b/tools/test-networkmanager-service.py
@@ -299,6 +299,8 @@ class Util:
             return GLib.Variant("u", int(val))
         if isinstance(val, dbus.UInt64):
             return GLib.Variant("t", int(val))
+        if isinstance(val, dbus.Int32):
+            return GLib.Variant("i", int(val))
         if isinstance(val, dbus.Boolean):
             return GLib.Variant("b", bool(val))
         if isinstance(val, dbus.Byte):
@@ -389,6 +391,7 @@ IFACE_SETTINGS = "org.freedesktop.NetworkManager.Settings"
 IFACE_AGENT_MANAGER = "org.freedesktop.NetworkManager.AgentManager"
 IFACE_AGENT = "org.freedesktop.NetworkManager.SecretAgent"
 IFACE_WIRED = "org.freedesktop.NetworkManager.Device.Wired"
+IFACE_MODEM = "org.freedesktop.NetworkManager.Device.Modem"
 IFACE_VLAN = "org.freedesktop.NetworkManager.Device.Vlan"
 IFACE_WIFI_AP = "org.freedesktop.NetworkManager.AccessPoint"
 IFACE_ACTIVE_CONNECTION = "org.freedesktop.NetworkManager.Connection.Active"
@@ -404,46 +407,74 @@ IFACE_DHCP6_CONFIG = "org.freedesktop.NetworkManager.DHCP6Config"
 
 class BusErr:
     class UnknownInterfaceException(dbus.DBusException):
-        _dbus_error_name = IFACE_DBUS + ".UnknownInterface"
+        def __init__(self, *args, **kwargs):
+            self._dbus_error_name = "{}.UnknownInterface".format(IFACE_DBUS)
+            dbus.DBusException.__init__(self, *args, **kwargs)
 
     class UnknownPropertyException(dbus.DBusException):
-        _dbus_error_name = IFACE_DBUS + ".UnknownProperty"
+        def __init__(self, *args, **kwargs):
+            self._dbus_error_name = "{}.UnknownProperty".format(IFACE_DBUS)
+            dbus.DBusException.__init__(self, *args, **kwargs)
 
     class InvalidPropertyException(dbus.DBusException):
-        _dbus_error_name = IFACE_CONNECTION + ".InvalidProperty"
+        def __init__(self, *args, **kwargs):
+            self._dbus_error_name = "{}.InvalidProperty".format(IFACE_CONNECTION)
+            dbus.DBusException.__init__(self, *args, **kwargs)
 
     class MissingPropertyException(dbus.DBusException):
-        _dbus_error_name = IFACE_CONNECTION + ".MissingProperty"
+        def __init__(self, *args, **kwargs):
+            self._dbus_error_name = "{}.MissingProperty".format(IFACE_CONNECTION)
+            dbus.DBusException.__init__(self, *args, **kwargs)
 
     class InvalidSettingException(dbus.DBusException):
-        _dbus_error_name = IFACE_CONNECTION + ".InvalidSetting"
+        def __init__(self, *args, **kwargs):
+            self._dbus_error_name = "{}.InvalidSetting".format(IFACE_CONNECTION)
+            dbus.DBusException.__init__(self, *args, **kwargs)
 
     class MissingSettingException(dbus.DBusException):
-        _dbus_error_name = IFACE_CONNECTION + ".MissingSetting"
+        def __init__(self, *args, **kwargs):
+            self._dbus_error_name = "{}.MissingSetting".format(IFACE_CONNECTION)
+            dbus.DBusException.__init__(self, *args, **kwargs)
 
     class NotSoftwareException(dbus.DBusException):
-        _dbus_error_name = IFACE_DEVICE + ".NotSoftware"
+        def __init__(self, *args, **kwargs):
+            self._dbus_error_name = "{}.NotSoftware".format(IFACE_DEVICE)
+            dbus.DBusException.__init__(self, *args, **kwargs)
 
     class ApNotFoundException(dbus.DBusException):
-        _dbus_error_name = IFACE_WIFI + ".AccessPointNotFound"
+        def __init__(self, *args, **kwargs):
+            self._dbus_error_name = "{}.AccessPointNotFound".format(IFACE_WIFI)
+            dbus.DBusException.__init__(self, *args, **kwargs)
 
     class PermissionDeniedException(dbus.DBusException):
-        _dbus_error_name = IFACE_NM + ".PermissionDenied"
+        def __init__(self, *args, **kwargs):
+            self._dbus_error_name = "{}.PermissionDenied".format(IFACE_NM)
+            dbus.DBusException.__init__(self, *args, **kwargs)
 
     class UnknownDeviceException(dbus.DBusException):
-        _dbus_error_name = IFACE_NM + ".UnknownDevice"
+        def __init__(self, *args, **kwargs):
+            self._dbus_error_name = "{}.UnknownDevice".format(IFACE_NM)
+            dbus.DBusException.__init__(self, *args, **kwargs)
 
     class UnknownConnectionException(dbus.DBusException):
-        _dbus_error_name = IFACE_NM + ".UnknownConnection"
+        def __init__(self, *args, **kwargs):
+            self._dbus_error_name = "{}.UnknownConnection".format(IFACE_NM)
+            dbus.DBusException.__init__(self, *args, **kwargs)
 
     class InvalidHostnameException(dbus.DBusException):
-        _dbus_error_name = IFACE_SETTINGS + ".InvalidHostname"
+        def __init__(self, *args, **kwargs):
+            self._dbus_error_name = "{}.InvalidHostname".format(IFACE_SETTINGS)
+            dbus.DBusException.__init__(self, *args, **kwargs)
 
     class NoSecretsException(dbus.DBusException):
-        _dbus_error_name = IFACE_AGENT_MANAGER + ".NoSecrets"
+        def __init__(self, *args, **kwargs):
+            self._dbus_error_name = "{}.NoSecrets".format(IFACE_AGENT_MANAGER)
+            dbus.DBusException.__init__(self, *args, **kwargs)
 
     class UserCanceledException(dbus.DBusException):
-        _dbus_error_name = IFACE_AGENT_MANAGER + ".UserCanceled"
+        def __init__(self, *args, **kwargs):
+            self._dbus_error_name = "{}.UserCanceled".format(IFACE_AGENT_MANAGER)
+            dbus.DBusException.__init__(self, *args, **kwargs)
 
     @staticmethod
     def from_nmerror(e):
@@ -778,6 +809,7 @@ class ExportedObj(dbus.service.Object):
 
 PRP_DEVICE_UDI = "Udi"
 PRP_DEVICE_IFACE = "Interface"
+PRP_DEVICE_IPIFACE = "IpInterface"
 PRP_DEVICE_DRIVER = "Driver"
 PRP_DEVICE_STATE = "State"
 PRP_DEVICE_STATE_REASON = "StateReason"
@@ -813,9 +845,15 @@ class Device(ExportedObj):
 
         self.prp_state = NM.DeviceState.UNAVAILABLE
 
+        if devtype == NM.DeviceType.MODEM:
+            udi = "/org/freedesktop/ModemManager1/Modem/0"
+        else:
+            udi = "/sys/devices/virtual/%s" % iface
+
         props = {
-            PRP_DEVICE_UDI: "/sys/devices/virtual/%s" % (iface),
+            PRP_DEVICE_UDI: udi,
             PRP_DEVICE_IFACE: iface,
+            PRP_DEVICE_IPIFACE: iface,
             PRP_DEVICE_DRIVER: "virtual",
             PRP_DEVICE_STATE: dbus.UInt32(self.prp_state),
             PRP_DEVICE_STATE_REASON: dbus.Struct(
@@ -1018,10 +1056,32 @@ class Device(ExportedObj):
         raise BusErr.NotSoftwareException()
         pass
 
+    @dbus.service.signal(IFACE_DEVICE, signature="uuu")
+    def StateChanged(self, new_state, old_state, reason):
+        pass
+
     @dbus.service.signal(IFACE_DEVICE, signature="a{sv}")
     def PropertiesChanged(self, changed):
         pass
 
+    def set_state(self, state, reason):
+        # libnm is plugged on notify::state-reason and not on state-changed dbus signal
+        # so we must simulate the change of property to emit a state-changed signal on libnm
+        self._dbus_property_set(IFACE_DEVICE, PRP_NM_STATE, dbus.UInt32(state))
+        self._dbus_property_set(
+            IFACE_DEVICE,
+            PRP_DEVICE_STATE_REASON,
+            (dbus.UInt32(state), dbus.UInt32(reason)),
+        )
+        old_state = self.prp_state
+        self.prp_state = state
+        self.StateChanged(
+            dbus.UInt32(self.prp_state), dbus.UInt32(old_state), dbus.UInt32(reason)
+        )
+
+    def set_carrier_status(self, carrier_status):
+        self._dbus_property_set(IFACE_WIRED, PRP_WIRED_CARRIER, carrier_status)
+
     def set_active_connection(self, ac):
         self._dbus_property_set(IFACE_DEVICE, PRP_DEVICE_ACTIVE_CONNECTION, ac)
 
@@ -1048,6 +1108,14 @@ class Device(ExportedObj):
             ExportedObj.to_path_array(self.available_connections_get()),
         )
 
+    @dbus.service.method(IFACE_TEST, in_signature="", out_signature="")
+    def Start(self):
+        self.start()
+
+    @dbus.service.method(IFACE_TEST, in_signature="", out_signature="")
+    def Stop(self):
+        self.stop()
+
 
 ###############################################################################
 
@@ -1071,7 +1139,7 @@ class WiredDevice(Device):
             PRP_WIRED_HW_ADDRESS: mac,
             PRP_WIRED_PERM_HW_ADDRESS: mac,
             PRP_WIRED_SPEED: dbus.UInt32(100),
-            PRP_WIRED_CARRIER: False,
+            PRP_WIRED_CARRIER: True,
             PRP_WIRED_S390_SUBCHANNELS: subchannels,
         }
 
@@ -1083,6 +1151,30 @@ class WiredDevice(Device):
 
 
 ###############################################################################
+PM_CURRENT_CAPABILITIES = "CurrentCapabilities"
+PM_MODEM_CAPABILITIES = "ModemCapabilities"
+
+# capability to make device seen compatible with GSM connection
+NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS = 0x00000004
+
+
+class ModemDevice(Device):
+    def __init__(self, iface):
+        Device.__init__(self, iface, NM.DeviceType.MODEM)
+
+        props = {
+            PM_CURRENT_CAPABILITIES: dbus.UInt32(NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS),
+            PM_MODEM_CAPABILITIES: dbus.UInt32(0),
+        }
+
+        self.dbus_interface_add(IFACE_MODEM, props, ModemDevice.PropertiesChanged)
+
+    @dbus.service.signal(IFACE_MODEM, signature="a{sv}")
+    def PropertiesChanged(self, changed):
+        pass
+
+
+###############################################################################
 
 PRP_VLAN_HW_ADDRESS = "HwAddress"
 PRP_VLAN_CARRIER = "Carrier"
@@ -1331,6 +1423,8 @@ class ActiveConnection(ExportedObj):
         self.is_vpn = con_inst.is_vpn()
 
         self._activation_id = None
+        self._deactivation_id = None
+        self.activation_state_change_delay_ms = 50
 
         s_con = con_inst.con_hash[NM.SETTING_CONNECTION_SETTING_NAME]
 
@@ -1382,23 +1476,72 @@ class ActiveConnection(ExportedObj):
     def _activation_step2(self):
         assert self._activation_id is not None
         self._activation_id = None
-        self._set_state(
-            NM.ActiveConnectionState.ACTIVATED, NM.ActiveConnectionStateReason.UNKNOWN
-        )
+
+        s_con = self.con_inst.con_hash[NM.SETTING_CONNECTION_SETTING_NAME]
+        conn_id = s_con[NM.SETTING_CONNECTION_ID]
+
+        if gl.force_activation_failure.get(conn_id, False):
+            self._set_state(
+                NM.ActiveConnectionState.DEACTIVATED,
+                NM.ActiveConnectionStateReason.UNKNOWN,
+            )
+            self.device.set_state(NM.DeviceState.FAILED, NM.DeviceStateReason.UNKNOWN)
+        else:
+            self._set_state(
+                NM.ActiveConnectionState.ACTIVATED,
+                NM.ActiveConnectionStateReason.UNKNOWN,
+            )
+            self.device.set_state(NM.DeviceState.ACTIVATED, NM.DeviceStateReason.NONE)
         return False
 
     def _activation_step1(self):
         assert self._activation_id is not None
-        self._activation_id = GLib.timeout_add(50, self._activation_step2)
+        self._activation_id = GLib.timeout_add(
+            self.activation_state_change_delay_ms, self._activation_step2
+        )
         self.device.set_active_connection(self)
+        self.device.set_state(NM.DeviceState.PREPARE, NM.DeviceStateReason.NONE)
         self._set_state(
             NM.ActiveConnectionState.ACTIVATING, NM.ActiveConnectionStateReason.UNKNOWN
         )
         return False
 
+    def _deactivation_step1(self):
+        assert self._deactivation_id is not None
+        self._deactivation_id = None
+        self.device.set_state(
+            NM.DeviceState.DISCONNECTED, NM.DeviceStateReason.USER_REQUESTED
+        )
+        self._set_state(
+            NM.ActiveConnectionState.DEACTIVATED,
+            NM.ActiveConnectionStateReason.USER_DISCONNECTED,
+        )
+
+        return False
+
+    def set_state(self, state, reason):
+        self._set_state(state, reason)
+
     def start_activation(self):
         assert self._activation_id is None
-        self._activation_id = GLib.timeout_add(50, self._activation_step1)
+        self._activation_id = GLib.timeout_add(
+            self.activation_state_change_delay_ms, self._activation_step1
+        )
+
+    def start_deactivation(self):
+        assert self._deactivation_id is None
+        self._set_state(
+            NM.ActiveConnectionState.DEACTIVATING,
+            NM.ActiveConnectionStateReason.USER_DISCONNECTED,
+        )
+        self.device.set_state(
+            NM.DeviceState.DEACTIVATING, NM.DeviceStateReason.USER_REQUESTED
+        )
+        self._set_state(
+            NM.ActiveConnectionState.DEACTIVATING,
+            NM.ActiveConnectionStateReason.USER_DISCONNECTED,
+        )
+        self._deactivation_id = GLib.timeout_add(50, self._deactivation_step1)
 
     @dbus.service.signal(IFACE_VPN_CONNECTION, signature="a{sv}")
     def PropertiesChanged(self, changed):
@@ -1587,7 +1730,16 @@ class NetworkManager(ExportedObj):
 
     @dbus.service.method(dbus_interface=IFACE_NM, in_signature="o", out_signature="")
     def DeactivateConnection(self, active_connection):
-        pass
+        # Look for an active connection with the same object path
+        for ac in self.active_connections:
+            if ac.path == str(active_connection):
+                ac.activation_cancel()
+                ac.start_deactivation()
+                return
+
+        raise BusErr.UnknownConnectionException(
+            "Connection not found: %s" % str(active_connection)
+        )
 
     @dbus.service.method(dbus_interface=IFACE_NM, in_signature="b", out_signature="")
     def Sleep(self, do_sleep):
@@ -1778,6 +1930,11 @@ class NetworkManager(ExportedObj):
         return ExportedObj.to_path(self.add_device(dev))
 
     @dbus.service.method(IFACE_TEST, in_signature="s", out_signature="o")
+    def AddModemDevice(self, ifname):
+        dev = ModemDevice(ifname)
+        return ExportedObj.to_path(self.add_device(dev))
+
+    @dbus.service.method(IFACE_TEST, in_signature="s", out_signature="o")
     def AddWifiDevice(self, ifname):
         dev = WifiDevice(ifname)
         return ExportedObj.to_path(self.add_device(dev))
@@ -1808,6 +1965,64 @@ class NetworkManager(ExportedObj):
     def AddConnection(self, con_hash, do_verify_strict):
         return gl.settings.add_connection(con_hash, do_verify_strict)
 
+    @dbus.service.method(dbus_interface=IFACE_TEST, in_signature="sb", out_signature="")
+    def SetActiveConnectionFailure(self, connection_id, failure):
+        gl.force_activation_failure[connection_id] = failure
+
+    @dbus.service.method(dbus_interface=IFACE_TEST, in_signature="ou", out_signature="")
+    def SetActiveConnectionStateChangedDelay(self, devpath, delay_ms):
+        for ac in reversed(self.active_connections):
+            if ac.device.path == devpath:
+                ac.activation_state_change_delay_ms = delay_ms
+                return
+        raise BusErr.UnknownDeviceException(
+            "Device with iface '%s' not found" % devpath
+        )
+
+    @dbus.service.method(
+        dbus_interface=IFACE_TEST, in_signature="ouu", out_signature=""
+    )
+    def SetActiveConnectionState(self, devpath, state, reason):
+        for ac in reversed(self.active_connections):
+            if ac.device.path == devpath:
+                ac.set_state(state, reason)
+                return
+        raise BusErr.UnknownDeviceException(
+            "Device with iface '%s' not found" % devpath
+        )
+
+    @dbus.service.method(
+        dbus_interface=IFACE_TEST, in_signature="ouu", out_signature=""
+    )
+    def SetDeviceState(self, devpath, state, reason):
+        try:
+            nmstate = NM.DeviceState(state)
+        except ValueError as e:
+            raise BusErr.InvalidPropertyException("Invalid device state: " % e)
+
+        try:
+            nmreason = NM.DeviceStateReason(reason)
+        except ValueError as e:
+            raise BusErr.InvalidPropertyException("Invalid device state reason: " % e)
+
+        for d in self.devices:
+            if d.path == devpath:
+                d.set_state(nmstate, nmreason)
+                return
+        raise BusErr.UnknownDeviceException(
+            "Device with iface '%s' not found" % devpath
+        )
+
+    @dbus.service.method(dbus_interface=IFACE_TEST, in_signature="ob", out_signature="")
+    def SetCarrierStatus(self, devpath, status):
+        for d in self.devices:
+            if d.path == devpath:
+                d.set_carrier_status(status)
+                return
+        raise BusErr.UnknownDeviceException(
+            "Device with iface '%s' not found" % devpath
+        )
+
     @dbus.service.method(
         dbus_interface=IFACE_TEST, in_signature="sa{sa{sv}}b", out_signature=""
     )
@@ -2007,6 +2222,12 @@ class Settings(ExportedObj):
     def AddConnection(self, con_hash):
         return self.add_connection(con_hash)
 
+    @dbus.service.method(
+        dbus_interface=IFACE_SETTINGS, in_signature="", out_signature="b"
+    )
+    def ReloadConnections(self):
+        return True
+
     def add_connection(self, con_hash, do_verify_strict=True):
         self.c_counter += 1
         con_inst = Connection(self.c_counter, con_hash, do_verify_strict)
@@ -2280,6 +2501,10 @@ class IP4Config(ExportedObj):
         for k, v in props.items():
             self._dbus_property_set(IFACE_IP4_CONFIG, k, v)
 
+    @dbus.service.method(IFACE_TEST, in_signature="s", out_signature="")
+    def SetGateway(self, gateway):
+        self._dbus_property_set(IFACE_IP4_CONFIG, PRP_IP4_CONFIG_GATEWAY, gateway)
+
     @dbus.service.signal(IFACE_IP4_CONFIG, signature="a{sv}")
     def PropertiesChanged(self, path):
         pass
@@ -2721,6 +2946,7 @@ def main():
 
     gl.mainloop = GLib.MainLoop()
     gl.bus = dbus.SessionBus()
+    gl.force_activation_failure = {}
 
     gl.object_manager = ObjectManager("/org/freedesktop")
     gl.manager = NetworkManager()