about summary refs log tree commit diff
path: root/src/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests')
-rw-r--r--src/tests/Makefile.am32
-rw-r--r--src/tests/Makefile.in618
-rw-r--r--src/tests/config/Makefile.in437
-rw-r--r--src/tests/config/NetworkManager.conf68
-rw-r--r--src/tests/config/conf.d/00-overrides.conf46
-rw-r--r--src/tests/config/conf.d/10-more.conf29
-rw-r--r--src/tests/config/conf.d/90-last.conf3
-rw-r--r--src/tests/config/nm-test-device.c2
-rw-r--r--src/tests/config/test-config.c253
-rw-r--r--src/tests/test-general-with-expect.c459
-rw-r--r--src/tests/test-general.c281
-rw-r--r--src/tests/test-ip4-config.c8
-rw-r--r--src/tests/test-route-manager.c917
13 files changed, 2729 insertions, 424 deletions
diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
index a96d1ce9..83632bd1 100644
--- a/src/tests/Makefile.am
+++ b/src/tests/Makefile.am
@@ -20,6 +20,8 @@ noinst_PROGRAMS = \
 	test-general-with-expect \
 	test-ip4-config \
 	test-ip6-config \
+	test-route-manager-linux \
+	test-route-manager-fake \
 	test-dcb \
 	test-resolvconf-capture \
 	test-wired-defname
@@ -40,6 +42,34 @@ test_ip6_config_SOURCES = \
 test_ip6_config_LDADD = \
 	$(top_builddir)/src/libNetworkManager.la
 
+####### route manager test #######
+
+test_route_manager_fake_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-I$(top_srcdir)/src/platform/tests \
+	-DSETUP=nm_fake_platform_setup \
+	-DKERNEL_HACKS=0
+
+test_route_manager_fake_SOURCES = \
+	$(top_srcdir)/src/platform/tests/test-common.c \
+	test-route-manager.c
+
+test_route_manager_fake_LDADD = \
+	$(top_builddir)/src/libNetworkManager.la
+
+test_route_manager_linux_SOURCES = \
+	$(top_srcdir)/src/platform/tests/test-common.c \
+	test-route-manager.c
+
+test_route_manager_linux_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-I$(top_srcdir)/src/platform/tests \
+	-DSETUP=nm_linux_platform_setup \
+	-DKERNEL_HACKS=1
+
+test_route_manager_linux_LDADD = \
+	$(top_builddir)/src/libNetworkManager.la
+
 ####### DCB test #######
 
 test_dcb_SOURCES = \
@@ -90,6 +120,8 @@ EXTRA_DIST = test-secret-agent.py
 TESTS = \
 	test-ip4-config \
 	test-ip6-config \
+	test-route-manager-fake \
+	test-route-manager-linux \
 	test-dcb \
 	test-resolvconf-capture \
 	test-general \
diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in
index 61e757e7..f84c5536 100644
--- a/src/tests/Makefile.in
+++ b/src/tests/Makefile.in
@@ -90,12 +90,14 @@ build_triplet = @build@
 host_triplet = @host@
 noinst_PROGRAMS = test-general$(EXEEXT) \
 	test-general-with-expect$(EXEEXT) test-ip4-config$(EXEEXT) \
-	test-ip6-config$(EXEEXT) test-dcb$(EXEEXT) \
+	test-ip6-config$(EXEEXT) test-route-manager-linux$(EXEEXT) \
+	test-route-manager-fake$(EXEEXT) test-dcb$(EXEEXT) \
 	test-resolvconf-capture$(EXEEXT) test-wired-defname$(EXEEXT)
 TESTS = test-ip4-config$(EXEEXT) test-ip6-config$(EXEEXT) \
-	test-dcb$(EXEEXT) test-resolvconf-capture$(EXEEXT) \
-	test-general$(EXEEXT) test-general-with-expect$(EXEEXT) \
-	test-wired-defname$(EXEEXT)
+	test-route-manager-fake$(EXEEXT) \
+	test-route-manager-linux$(EXEEXT) test-dcb$(EXEEXT) \
+	test-resolvconf-capture$(EXEEXT) test-general$(EXEEXT) \
+	test-general-with-expect$(EXEEXT) test-wired-defname$(EXEEXT)
 subdir = src/tests
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
 am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
@@ -149,6 +151,20 @@ test_resolvconf_capture_OBJECTS =  \
 	$(am_test_resolvconf_capture_OBJECTS)
 test_resolvconf_capture_DEPENDENCIES =  \
 	$(top_builddir)/src/libNetworkManager.la
+am_test_route_manager_fake_OBJECTS =  \
+	test_route_manager_fake-test-common.$(OBJEXT) \
+	test_route_manager_fake-test-route-manager.$(OBJEXT)
+test_route_manager_fake_OBJECTS =  \
+	$(am_test_route_manager_fake_OBJECTS)
+test_route_manager_fake_DEPENDENCIES =  \
+	$(top_builddir)/src/libNetworkManager.la
+am_test_route_manager_linux_OBJECTS =  \
+	test_route_manager_linux-test-common.$(OBJEXT) \
+	test_route_manager_linux-test-route-manager.$(OBJEXT)
+test_route_manager_linux_OBJECTS =  \
+	$(am_test_route_manager_linux_OBJECTS)
+test_route_manager_linux_DEPENDENCIES =  \
+	$(top_builddir)/src/libNetworkManager.la
 am_test_wired_defname_OBJECTS = test-wired-defname.$(OBJEXT)
 test_wired_defname_OBJECTS = $(am_test_wired_defname_OBJECTS)
 test_wired_defname_DEPENDENCIES =  \
@@ -190,10 +206,14 @@ am__v_CCLD_1 =
 SOURCES = $(test_dcb_SOURCES) $(test_general_SOURCES) \
 	$(test_general_with_expect_SOURCES) $(test_ip4_config_SOURCES) \
 	$(test_ip6_config_SOURCES) $(test_resolvconf_capture_SOURCES) \
+	$(test_route_manager_fake_SOURCES) \
+	$(test_route_manager_linux_SOURCES) \
 	$(test_wired_defname_SOURCES)
 DIST_SOURCES = $(test_dcb_SOURCES) $(test_general_SOURCES) \
 	$(test_general_with_expect_SOURCES) $(test_ip4_config_SOURCES) \
 	$(test_ip6_config_SOURCES) $(test_resolvconf_capture_SOURCES) \
+	$(test_route_manager_fake_SOURCES) \
+	$(test_route_manager_linux_SOURCES) \
 	$(test_wired_defname_SOURCES)
 RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
 	ctags-recursive dvi-recursive html-recursive info-recursive \
@@ -215,7 +235,7 @@ am__recursive_targets = \
   $(RECURSIVE_CLEAN_TARGETS) \
   $(am__extra_recursive_targets)
 AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
-	distdir
+	check recheck distdir
 am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
 # Read a list of newline-separated strings from the standard input,
 # and print each of them once, without duplicates.  Input order is
@@ -257,9 +277,190 @@ am__tty_colors = { \
     std=''; \
   fi; \
 }
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+    $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+    *) f=$$p;; \
+  esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+  srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+  for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+  for p in $$list; do echo "$$p $$p"; done | \
+  sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+  $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+    if (++n[$$2] == $(am__install_max)) \
+      { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+    END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+  sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+  sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+  test -z "$$files" \
+    || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+    || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+         $(am__cd) "$$dir" && rm -f $$files; }; \
+  }
+am__recheck_rx = ^[ 	]*:recheck:[ 	]*
+am__global_test_result_rx = ^[ 	]*:global-test-result:[ 	]*
+am__copy_in_global_log_rx = ^[ 	]*:copy-in-global-log:[ 	]*
+# A command that, given a newline-separated list of test names on the
+# standard input, print the name of the tests that are to be re-run
+# upon "make recheck".
+am__list_recheck_tests = $(AWK) '{ \
+  recheck = 1; \
+  while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+    { \
+      if (rc < 0) \
+        { \
+          if ((getline line2 < ($$0 ".log")) < 0) \
+	    recheck = 0; \
+          break; \
+        } \
+      else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \
+        { \
+          recheck = 0; \
+          break; \
+        } \
+      else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \
+        { \
+          break; \
+        } \
+    }; \
+  if (recheck) \
+    print $$0; \
+  close ($$0 ".trs"); \
+  close ($$0 ".log"); \
+}'
+# A command that, given a newline-separated list of test names on the
+# standard input, create the global log from their .trs and .log files.
+am__create_global_log = $(AWK) ' \
+function fatal(msg) \
+{ \
+  print "fatal: making $@: " msg | "cat >&2"; \
+  exit 1; \
+} \
+function rst_section(header) \
+{ \
+  print header; \
+  len = length(header); \
+  for (i = 1; i <= len; i = i + 1) \
+    printf "="; \
+  printf "\n\n"; \
+} \
+{ \
+  copy_in_global_log = 1; \
+  global_test_result = "RUN"; \
+  while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+    { \
+      if (rc < 0) \
+         fatal("failed to read from " $$0 ".trs"); \
+      if (line ~ /$(am__global_test_result_rx)/) \
+        { \
+          sub("$(am__global_test_result_rx)", "", line); \
+          sub("[ 	]*$$", "", line); \
+          global_test_result = line; \
+        } \
+      else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \
+        copy_in_global_log = 0; \
+    }; \
+  if (copy_in_global_log) \
+    { \
+      rst_section(global_test_result ": " $$0); \
+      while ((rc = (getline line < ($$0 ".log"))) != 0) \
+      { \
+        if (rc < 0) \
+          fatal("failed to read from " $$0 ".log"); \
+        print line; \
+      }; \
+      printf "\n"; \
+    }; \
+  close ($$0 ".trs"); \
+  close ($$0 ".log"); \
+}'
+# Restructured Text title.
+am__rst_title = { sed 's/.*/   &   /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; }
+# Solaris 10 'make', and several other traditional 'make' implementations,
+# pass "-e" to $(SHELL), and POSIX 2008 even requires this.  Work around it
+# by disabling -e (using the XSI extension "set +e") if it's set.
+am__sh_e_setup = case $$- in *e*) set +e;; esac
+# Default flags passed to test drivers.
+am__common_driver_flags = \
+  --color-tests "$$am__color_tests" \
+  --enable-hard-errors "$$am__enable_hard_errors" \
+  --expect-failure "$$am__expect_failure"
+# To be inserted before the command running the test.  Creates the
+# directory for the log if needed.  Stores in $dir the directory
+# containing $f, in $tst the test, in $log the log.  Executes the
+# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and
+# passes TESTS_ENVIRONMENT.  Set up options for the wrapper that
+# will run the test scripts (or their associated LOG_COMPILER, if
+# thy have one).
+am__check_pre = \
+$(am__sh_e_setup);					\
+$(am__vpath_adj_setup) $(am__vpath_adj)			\
+$(am__tty_colors);					\
+srcdir=$(srcdir); export srcdir;			\
+case "$@" in						\
+  */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;;	\
+    *) am__odir=.;; 					\
+esac;							\
+test "x$$am__odir" = x"." || test -d "$$am__odir" 	\
+  || $(MKDIR_P) "$$am__odir" || exit $$?;		\
+if test -f "./$$f"; then dir=./;			\
+elif test -f "$$f"; then dir=;				\
+else dir="$(srcdir)/"; fi;				\
+tst=$$dir$$f; log='$@'; 				\
+if test -n '$(DISABLE_HARD_ERRORS)'; then		\
+  am__enable_hard_errors=no; 				\
+else							\
+  am__enable_hard_errors=yes; 				\
+fi; 							\
+case " $(XFAIL_TESTS) " in				\
+  *[\ \	]$$f[\ \	]* | *[\ \	]$$dir$$f[\ \	]*) \
+    am__expect_failure=yes;;				\
+  *)							\
+    am__expect_failure=no;;				\
+esac; 							\
+$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT)
+# A shell command to get the names of the tests scripts with any registered
+# extension removed (i.e., equivalently, the names of the test logs, with
+# the '.log' extension removed).  The result is saved in the shell variable
+# '$bases'.  This honors runtime overriding of TESTS and TEST_LOGS.  Sadly,
+# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)",
+# since that might cause problem with VPATH rewrites for suffix-less tests.
+# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'.
+am__set_TESTS_bases = \
+  bases='$(TEST_LOGS)'; \
+  bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
+  bases=`echo $$bases`
+RECHECK_LOGS = $(TEST_LOGS)
+TEST_SUITE_LOG = test-suite.log
+TEST_EXTENSIONS = @EXEEXT@ .test
+LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS)
+am__set_b = \
+  case '$@' in \
+    */*) \
+      case '$*' in \
+        */*) b='$*';; \
+          *) b=`echo '$@' | sed 's/\.log$$//'`; \
+       esac;; \
+    *) \
+      b='$*';; \
+  esac
+am__test_logs1 = $(TESTS:=.log)
+am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log)
+TEST_LOGS = $(am__test_logs2:.test.log=.log)
+TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/build-aux/test-driver
+TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \
+	$(TEST_LOG_FLAGS)
 DIST_SUBDIRS = $(SUBDIRS)
 am__DIST_COMMON = $(srcdir)/Makefile.in \
-	$(top_srcdir)/build-aux/depcomp
+	$(top_srcdir)/build-aux/depcomp \
+	$(top_srcdir)/build-aux/test-driver
 DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
 am__relativize = \
   dir0=`pwd`; \
@@ -290,6 +491,7 @@ ACLOCAL = @ACLOCAL@
 ALL_LINGUAS = @ALL_LINGUAS@
 AMTAR = @AMTAR@
 AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AM_TESTS_FD_REDIRECT = @AM_TESTS_FD_REDIRECT@
 AR = @AR@
 AUTOCONF = @AUTOCONF@
 AUTOHEADER = @AUTOHEADER@
@@ -403,6 +605,7 @@ LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
 LIPO = @LIPO@
 LN_S = @LN_S@
+LOG_DRIVER = @LOG_DRIVER@
 LTLIBICONV = @LTLIBICONV@
 LTLIBINTL = @LTLIBINTL@
 LTLIBOBJS = @LTLIBOBJS@
@@ -579,6 +782,34 @@ test_ip6_config_LDADD = \
 	$(top_builddir)/src/libNetworkManager.la
 
 
+####### route manager test #######
+test_route_manager_fake_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-I$(top_srcdir)/src/platform/tests \
+	-DSETUP=nm_fake_platform_setup \
+	-DKERNEL_HACKS=0
+
+test_route_manager_fake_SOURCES = \
+	$(top_srcdir)/src/platform/tests/test-common.c \
+	test-route-manager.c
+
+test_route_manager_fake_LDADD = \
+	$(top_builddir)/src/libNetworkManager.la
+
+test_route_manager_linux_SOURCES = \
+	$(top_srcdir)/src/platform/tests/test-common.c \
+	test-route-manager.c
+
+test_route_manager_linux_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-I$(top_srcdir)/src/platform/tests \
+	-DSETUP=nm_linux_platform_setup \
+	-DKERNEL_HACKS=1
+
+test_route_manager_linux_LDADD = \
+	$(top_builddir)/src/libNetworkManager.la
+
+
 ####### DCB test #######
 test_dcb_SOURCES = \
 	test-dcb.c
@@ -624,7 +855,7 @@ EXTRA_DIST = test-secret-agent.py
 all: all-recursive
 
 .SUFFIXES:
-.SUFFIXES: .c .lo .o .obj
+.SUFFIXES: .c .lo .log .o .obj .test .test$(EXEEXT) .trs
 $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am  $(am__configure_deps)
 	@for dep in $?; do \
 	  case '$(am__configure_deps)' in \
@@ -688,6 +919,14 @@ test-resolvconf-capture$(EXEEXT): $(test_resolvconf_capture_OBJECTS) $(test_reso
 	@rm -f test-resolvconf-capture$(EXEEXT)
 	$(AM_V_CCLD)$(LINK) $(test_resolvconf_capture_OBJECTS) $(test_resolvconf_capture_LDADD) $(LIBS)
 
+test-route-manager-fake$(EXEEXT): $(test_route_manager_fake_OBJECTS) $(test_route_manager_fake_DEPENDENCIES) $(EXTRA_test_route_manager_fake_DEPENDENCIES) 
+	@rm -f test-route-manager-fake$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(test_route_manager_fake_OBJECTS) $(test_route_manager_fake_LDADD) $(LIBS)
+
+test-route-manager-linux$(EXEEXT): $(test_route_manager_linux_OBJECTS) $(test_route_manager_linux_DEPENDENCIES) $(EXTRA_test_route_manager_linux_DEPENDENCIES) 
+	@rm -f test-route-manager-linux$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(test_route_manager_linux_OBJECTS) $(test_route_manager_linux_LDADD) $(LIBS)
+
 test-wired-defname$(EXEEXT): $(test_wired_defname_OBJECTS) $(test_wired_defname_DEPENDENCIES) $(EXTRA_test_wired_defname_DEPENDENCIES) 
 	@rm -f test-wired-defname$(EXEEXT)
 	$(AM_V_CCLD)$(LINK) $(test_wired_defname_OBJECTS) $(test_wired_defname_LDADD) $(LIBS)
@@ -705,6 +944,10 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-ip6-config.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-resolvconf-capture.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-wired-defname.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_route_manager_fake-test-common.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_route_manager_fake-test-route-manager.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_route_manager_linux-test-common.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_route_manager_linux-test-route-manager.Po@am__quote@
 
 .c.o:
 @am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
@@ -727,6 +970,62 @@ distclean-compile:
 @AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
 @am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
 
+test_route_manager_fake-test-common.o: $(top_srcdir)/src/platform/tests/test-common.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_route_manager_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_route_manager_fake-test-common.o -MD -MP -MF $(DEPDIR)/test_route_manager_fake-test-common.Tpo -c -o test_route_manager_fake-test-common.o `test -f '$(top_srcdir)/src/platform/tests/test-common.c' || echo '$(srcdir)/'`$(top_srcdir)/src/platform/tests/test-common.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/test_route_manager_fake-test-common.Tpo $(DEPDIR)/test_route_manager_fake-test-common.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$(top_srcdir)/src/platform/tests/test-common.c' object='test_route_manager_fake-test-common.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_route_manager_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_route_manager_fake-test-common.o `test -f '$(top_srcdir)/src/platform/tests/test-common.c' || echo '$(srcdir)/'`$(top_srcdir)/src/platform/tests/test-common.c
+
+test_route_manager_fake-test-common.obj: $(top_srcdir)/src/platform/tests/test-common.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_route_manager_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_route_manager_fake-test-common.obj -MD -MP -MF $(DEPDIR)/test_route_manager_fake-test-common.Tpo -c -o test_route_manager_fake-test-common.obj `if test -f '$(top_srcdir)/src/platform/tests/test-common.c'; then $(CYGPATH_W) '$(top_srcdir)/src/platform/tests/test-common.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/platform/tests/test-common.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/test_route_manager_fake-test-common.Tpo $(DEPDIR)/test_route_manager_fake-test-common.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$(top_srcdir)/src/platform/tests/test-common.c' object='test_route_manager_fake-test-common.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_route_manager_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_route_manager_fake-test-common.obj `if test -f '$(top_srcdir)/src/platform/tests/test-common.c'; then $(CYGPATH_W) '$(top_srcdir)/src/platform/tests/test-common.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/platform/tests/test-common.c'; fi`
+
+test_route_manager_fake-test-route-manager.o: test-route-manager.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_route_manager_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_route_manager_fake-test-route-manager.o -MD -MP -MF $(DEPDIR)/test_route_manager_fake-test-route-manager.Tpo -c -o test_route_manager_fake-test-route-manager.o `test -f 'test-route-manager.c' || echo '$(srcdir)/'`test-route-manager.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/test_route_manager_fake-test-route-manager.Tpo $(DEPDIR)/test_route_manager_fake-test-route-manager.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='test-route-manager.c' object='test_route_manager_fake-test-route-manager.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_route_manager_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_route_manager_fake-test-route-manager.o `test -f 'test-route-manager.c' || echo '$(srcdir)/'`test-route-manager.c
+
+test_route_manager_fake-test-route-manager.obj: test-route-manager.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_route_manager_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_route_manager_fake-test-route-manager.obj -MD -MP -MF $(DEPDIR)/test_route_manager_fake-test-route-manager.Tpo -c -o test_route_manager_fake-test-route-manager.obj `if test -f 'test-route-manager.c'; then $(CYGPATH_W) 'test-route-manager.c'; else $(CYGPATH_W) '$(srcdir)/test-route-manager.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/test_route_manager_fake-test-route-manager.Tpo $(DEPDIR)/test_route_manager_fake-test-route-manager.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='test-route-manager.c' object='test_route_manager_fake-test-route-manager.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_route_manager_fake_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_route_manager_fake-test-route-manager.obj `if test -f 'test-route-manager.c'; then $(CYGPATH_W) 'test-route-manager.c'; else $(CYGPATH_W) '$(srcdir)/test-route-manager.c'; fi`
+
+test_route_manager_linux-test-common.o: $(top_srcdir)/src/platform/tests/test-common.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_route_manager_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_route_manager_linux-test-common.o -MD -MP -MF $(DEPDIR)/test_route_manager_linux-test-common.Tpo -c -o test_route_manager_linux-test-common.o `test -f '$(top_srcdir)/src/platform/tests/test-common.c' || echo '$(srcdir)/'`$(top_srcdir)/src/platform/tests/test-common.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/test_route_manager_linux-test-common.Tpo $(DEPDIR)/test_route_manager_linux-test-common.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$(top_srcdir)/src/platform/tests/test-common.c' object='test_route_manager_linux-test-common.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_route_manager_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_route_manager_linux-test-common.o `test -f '$(top_srcdir)/src/platform/tests/test-common.c' || echo '$(srcdir)/'`$(top_srcdir)/src/platform/tests/test-common.c
+
+test_route_manager_linux-test-common.obj: $(top_srcdir)/src/platform/tests/test-common.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_route_manager_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_route_manager_linux-test-common.obj -MD -MP -MF $(DEPDIR)/test_route_manager_linux-test-common.Tpo -c -o test_route_manager_linux-test-common.obj `if test -f '$(top_srcdir)/src/platform/tests/test-common.c'; then $(CYGPATH_W) '$(top_srcdir)/src/platform/tests/test-common.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/platform/tests/test-common.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/test_route_manager_linux-test-common.Tpo $(DEPDIR)/test_route_manager_linux-test-common.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$(top_srcdir)/src/platform/tests/test-common.c' object='test_route_manager_linux-test-common.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_route_manager_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_route_manager_linux-test-common.obj `if test -f '$(top_srcdir)/src/platform/tests/test-common.c'; then $(CYGPATH_W) '$(top_srcdir)/src/platform/tests/test-common.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/platform/tests/test-common.c'; fi`
+
+test_route_manager_linux-test-route-manager.o: test-route-manager.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_route_manager_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_route_manager_linux-test-route-manager.o -MD -MP -MF $(DEPDIR)/test_route_manager_linux-test-route-manager.Tpo -c -o test_route_manager_linux-test-route-manager.o `test -f 'test-route-manager.c' || echo '$(srcdir)/'`test-route-manager.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/test_route_manager_linux-test-route-manager.Tpo $(DEPDIR)/test_route_manager_linux-test-route-manager.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='test-route-manager.c' object='test_route_manager_linux-test-route-manager.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_route_manager_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_route_manager_linux-test-route-manager.o `test -f 'test-route-manager.c' || echo '$(srcdir)/'`test-route-manager.c
+
+test_route_manager_linux-test-route-manager.obj: test-route-manager.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_route_manager_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_route_manager_linux-test-route-manager.obj -MD -MP -MF $(DEPDIR)/test_route_manager_linux-test-route-manager.Tpo -c -o test_route_manager_linux-test-route-manager.obj `if test -f 'test-route-manager.c'; then $(CYGPATH_W) 'test-route-manager.c'; else $(CYGPATH_W) '$(srcdir)/test-route-manager.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/test_route_manager_linux-test-route-manager.Tpo $(DEPDIR)/test_route_manager_linux-test-route-manager.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='test-route-manager.c' object='test_route_manager_linux-test-route-manager.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_route_manager_linux_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_route_manager_linux-test-route-manager.obj `if test -f 'test-route-manager.c'; then $(CYGPATH_W) 'test-route-manager.c'; else $(CYGPATH_W) '$(srcdir)/test-route-manager.c'; fi`
+
 mostlyclean-libtool:
 	-rm -f *.lo
 
@@ -832,98 +1131,224 @@ cscopelist-am: $(am__tagged_files)
 distclean-tags:
 	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
 
-check-TESTS: $(TESTS)
-	@failed=0; all=0; xfail=0; xpass=0; skip=0; \
-	srcdir=$(srcdir); export srcdir; \
-	list=' $(TESTS) '; \
-	$(am__tty_colors); \
-	if test -n "$$list"; then \
-	  for tst in $$list; do \
-	    if test -f ./$$tst; then dir=./; \
-	    elif test -f $$tst; then dir=; \
-	    else dir="$(srcdir)/"; fi; \
-	    if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \
-	      all=`expr $$all + 1`; \
-	      case " $(XFAIL_TESTS) " in \
-	      *[\ \	]$$tst[\ \	]*) \
-		xpass=`expr $$xpass + 1`; \
-		failed=`expr $$failed + 1`; \
-		col=$$red; res=XPASS; \
-	      ;; \
-	      *) \
-		col=$$grn; res=PASS; \
-	      ;; \
-	      esac; \
-	    elif test $$? -ne 77; then \
-	      all=`expr $$all + 1`; \
-	      case " $(XFAIL_TESTS) " in \
-	      *[\ \	]$$tst[\ \	]*) \
-		xfail=`expr $$xfail + 1`; \
-		col=$$lgn; res=XFAIL; \
-	      ;; \
-	      *) \
-		failed=`expr $$failed + 1`; \
-		col=$$red; res=FAIL; \
-	      ;; \
-	      esac; \
-	    else \
-	      skip=`expr $$skip + 1`; \
-	      col=$$blu; res=SKIP; \
-	    fi; \
-	    echo "$${col}$$res$${std}: $$tst"; \
-	  done; \
-	  if test "$$all" -eq 1; then \
-	    tests="test"; \
-	    All=""; \
-	  else \
-	    tests="tests"; \
-	    All="All "; \
+# Recover from deleted '.trs' file; this should ensure that
+# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create
+# both 'foo.log' and 'foo.trs'.  Break the recipe in two subshells
+# to avoid problems with "make -n".
+.log.trs:
+	rm -f $< $@
+	$(MAKE) $(AM_MAKEFLAGS) $<
+
+# Leading 'am--fnord' is there to ensure the list of targets does not
+# expand to empty, as could happen e.g. with make check TESTS=''.
+am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
+am--force-recheck:
+	@:
+
+$(TEST_SUITE_LOG): $(TEST_LOGS)
+	@$(am__set_TESTS_bases); \
+	am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
+	redo_bases=`for i in $$bases; do \
+	              am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \
+	            done`; \
+	if test -n "$$redo_bases"; then \
+	  redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \
+	  redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \
+	  if $(am__make_dryrun); then :; else \
+	    rm -f $$redo_logs && rm -f $$redo_results || exit 1; \
 	  fi; \
-	  if test "$$failed" -eq 0; then \
-	    if test "$$xfail" -eq 0; then \
-	      banner="$$All$$all $$tests passed"; \
-	    else \
-	      if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \
-	      banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \
-	    fi; \
-	  else \
-	    if test "$$xpass" -eq 0; then \
-	      banner="$$failed of $$all $$tests failed"; \
+	fi; \
+	if test -n "$$am__remaking_logs"; then \
+	  echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \
+	       "recursion detected" >&2; \
+	elif test -n "$$redo_logs"; then \
+	  am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \
+	fi; \
+	if $(am__make_dryrun); then :; else \
+	  st=0;  \
+	  errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \
+	  for i in $$redo_bases; do \
+	    test -f $$i.trs && test -r $$i.trs \
+	      || { echo "$$errmsg $$i.trs" >&2; st=1; }; \
+	    test -f $$i.log && test -r $$i.log \
+	      || { echo "$$errmsg $$i.log" >&2; st=1; }; \
+	  done; \
+	  test $$st -eq 0 || exit 1; \
+	fi
+	@$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \
+	ws='[ 	]'; \
+	results=`for b in $$bases; do echo $$b.trs; done`; \
+	test -n "$$results" || results=/dev/null; \
+	all=`  grep "^$$ws*:test-result:"           $$results | wc -l`; \
+	pass=` grep "^$$ws*:test-result:$$ws*PASS"  $$results | wc -l`; \
+	fail=` grep "^$$ws*:test-result:$$ws*FAIL"  $$results | wc -l`; \
+	skip=` grep "^$$ws*:test-result:$$ws*SKIP"  $$results | wc -l`; \
+	xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \
+	xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \
+	error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \
+	if test `expr $$fail + $$xpass + $$error` -eq 0; then \
+	  success=true; \
+	else \
+	  success=false; \
+	fi; \
+	br='==================='; br=$$br$$br$$br$$br; \
+	result_count () \
+	{ \
+	    if test x"$$1" = x"--maybe-color"; then \
+	      maybe_colorize=yes; \
+	    elif test x"$$1" = x"--no-color"; then \
+	      maybe_colorize=no; \
 	    else \
-	      if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \
-	      banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \
+	      echo "$@: invalid 'result_count' usage" >&2; exit 4; \
 	    fi; \
-	  fi; \
-	  dashes="$$banner"; \
-	  skipped=""; \
-	  if test "$$skip" -ne 0; then \
-	    if test "$$skip" -eq 1; then \
-	      skipped="($$skip test was not run)"; \
+	    shift; \
+	    desc=$$1 count=$$2; \
+	    if test $$maybe_colorize = yes && test $$count -gt 0; then \
+	      color_start=$$3 color_end=$$std; \
 	    else \
-	      skipped="($$skip tests were not run)"; \
+	      color_start= color_end=; \
 	    fi; \
-	    test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \
-	      dashes="$$skipped"; \
-	  fi; \
-	  report=""; \
-	  if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \
-	    report="Please report to $(PACKAGE_BUGREPORT)"; \
-	    test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \
-	      dashes="$$report"; \
-	  fi; \
-	  dashes=`echo "$$dashes" | sed s/./=/g`; \
-	  if test "$$failed" -eq 0; then \
-	    col="$$grn"; \
-	  else \
-	    col="$$red"; \
-	  fi; \
-	  echo "$${col}$$dashes$${std}"; \
-	  echo "$${col}$$banner$${std}"; \
-	  test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \
-	  test -z "$$report" || echo "$${col}$$report$${std}"; \
-	  echo "$${col}$$dashes$${std}"; \
-	  test "$$failed" -eq 0; \
-	else :; fi
+	    echo "$${color_start}# $$desc $$count$${color_end}"; \
+	}; \
+	create_testsuite_report () \
+	{ \
+	  result_count $$1 "TOTAL:" $$all   "$$brg"; \
+	  result_count $$1 "PASS: " $$pass  "$$grn"; \
+	  result_count $$1 "SKIP: " $$skip  "$$blu"; \
+	  result_count $$1 "XFAIL:" $$xfail "$$lgn"; \
+	  result_count $$1 "FAIL: " $$fail  "$$red"; \
+	  result_count $$1 "XPASS:" $$xpass "$$red"; \
+	  result_count $$1 "ERROR:" $$error "$$mgn"; \
+	}; \
+	{								\
+	  echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" |	\
+	    $(am__rst_title);						\
+	  create_testsuite_report --no-color;				\
+	  echo;								\
+	  echo ".. contents:: :depth: 2";				\
+	  echo;								\
+	  for b in $$bases; do echo $$b; done				\
+	    | $(am__create_global_log);					\
+	} >$(TEST_SUITE_LOG).tmp || exit 1;				\
+	mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG);			\
+	if $$success; then						\
+	  col="$$grn";							\
+	 else								\
+	  col="$$red";							\
+	  test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG);		\
+	fi;								\
+	echo "$${col}$$br$${std}"; 					\
+	echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}";	\
+	echo "$${col}$$br$${std}"; 					\
+	create_testsuite_report --maybe-color;				\
+	echo "$$col$$br$$std";						\
+	if $$success; then :; else					\
+	  echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}";		\
+	  if test -n "$(PACKAGE_BUGREPORT)"; then			\
+	    echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}";	\
+	  fi;								\
+	  echo "$$col$$br$$std";					\
+	fi;								\
+	$$success || exit 1
+
+check-TESTS:
+	@list='$(RECHECK_LOGS)';           test -z "$$list" || rm -f $$list
+	@list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
+	@test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+	@set +e; $(am__set_TESTS_bases); \
+	log_list=`for i in $$bases; do echo $$i.log; done`; \
+	trs_list=`for i in $$bases; do echo $$i.trs; done`; \
+	log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
+	$(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
+	exit $$?;
+recheck: all 
+	@test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+	@set +e; $(am__set_TESTS_bases); \
+	bases=`for i in $$bases; do echo $$i; done \
+	         | $(am__list_recheck_tests)` || exit 1; \
+	log_list=`for i in $$bases; do echo $$i.log; done`; \
+	log_list=`echo $$log_list`; \
+	$(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \
+	        am__force_recheck=am--force-recheck \
+	        TEST_LOGS="$$log_list"; \
+	exit $$?
+test-ip4-config.log: test-ip4-config$(EXEEXT)
+	@p='test-ip4-config$(EXEEXT)'; \
+	b='test-ip4-config'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-ip6-config.log: test-ip6-config$(EXEEXT)
+	@p='test-ip6-config$(EXEEXT)'; \
+	b='test-ip6-config'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-route-manager-fake.log: test-route-manager-fake$(EXEEXT)
+	@p='test-route-manager-fake$(EXEEXT)'; \
+	b='test-route-manager-fake'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-route-manager-linux.log: test-route-manager-linux$(EXEEXT)
+	@p='test-route-manager-linux$(EXEEXT)'; \
+	b='test-route-manager-linux'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-dcb.log: test-dcb$(EXEEXT)
+	@p='test-dcb$(EXEEXT)'; \
+	b='test-dcb'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-resolvconf-capture.log: test-resolvconf-capture$(EXEEXT)
+	@p='test-resolvconf-capture$(EXEEXT)'; \
+	b='test-resolvconf-capture'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-general.log: test-general$(EXEEXT)
+	@p='test-general$(EXEEXT)'; \
+	b='test-general'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-general-with-expect.log: test-general-with-expect$(EXEEXT)
+	@p='test-general-with-expect$(EXEEXT)'; \
+	b='test-general-with-expect'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-wired-defname.log: test-wired-defname$(EXEEXT)
+	@p='test-wired-defname$(EXEEXT)'; \
+	b='test-wired-defname'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+.test.log:
+	@p='$<'; \
+	$(am__set_b); \
+	$(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+@am__EXEEXT_TRUE@.test$(EXEEXT).log:
+@am__EXEEXT_TRUE@	@p='$<'; \
+@am__EXEEXT_TRUE@	$(am__set_b); \
+@am__EXEEXT_TRUE@	$(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+@am__EXEEXT_TRUE@	--log-file $$b.log --trs-file $$b.trs \
+@am__EXEEXT_TRUE@	$(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+@am__EXEEXT_TRUE@	"$$tst" $(AM_TESTS_FD_REDIRECT)
 
 distdir: $(DISTFILES)
 	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
@@ -1007,6 +1432,9 @@ install-strip:
 	    "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
 	fi
 mostlyclean-generic:
+	-test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
+	-test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
+	-test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
 
 clean-generic:
 
@@ -1103,7 +1531,7 @@ uninstall-am:
 	installcheck-am installdirs installdirs-am maintainer-clean \
 	maintainer-clean-generic mostlyclean mostlyclean-compile \
 	mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
-	tags tags-am uninstall uninstall-am
+	recheck tags tags-am uninstall uninstall-am
 
 .PRECIOUS: Makefile
 
diff --git a/src/tests/config/Makefile.in b/src/tests/config/Makefile.in
index 73ff9d48..6d1dd4bc 100644
--- a/src/tests/config/Makefile.in
+++ b/src/tests/config/Makefile.in
@@ -203,13 +203,196 @@ am__tty_colors = { \
     std=''; \
   fi; \
 }
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+    $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+    *) f=$$p;; \
+  esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+  srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+  for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+  for p in $$list; do echo "$$p $$p"; done | \
+  sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+  $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+    if (++n[$$2] == $(am__install_max)) \
+      { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+    END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+  sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+  sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+  test -z "$$files" \
+    || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+    || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+         $(am__cd) "$$dir" && rm -f $$files; }; \
+  }
+am__recheck_rx = ^[ 	]*:recheck:[ 	]*
+am__global_test_result_rx = ^[ 	]*:global-test-result:[ 	]*
+am__copy_in_global_log_rx = ^[ 	]*:copy-in-global-log:[ 	]*
+# A command that, given a newline-separated list of test names on the
+# standard input, print the name of the tests that are to be re-run
+# upon "make recheck".
+am__list_recheck_tests = $(AWK) '{ \
+  recheck = 1; \
+  while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+    { \
+      if (rc < 0) \
+        { \
+          if ((getline line2 < ($$0 ".log")) < 0) \
+	    recheck = 0; \
+          break; \
+        } \
+      else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \
+        { \
+          recheck = 0; \
+          break; \
+        } \
+      else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \
+        { \
+          break; \
+        } \
+    }; \
+  if (recheck) \
+    print $$0; \
+  close ($$0 ".trs"); \
+  close ($$0 ".log"); \
+}'
+# A command that, given a newline-separated list of test names on the
+# standard input, create the global log from their .trs and .log files.
+am__create_global_log = $(AWK) ' \
+function fatal(msg) \
+{ \
+  print "fatal: making $@: " msg | "cat >&2"; \
+  exit 1; \
+} \
+function rst_section(header) \
+{ \
+  print header; \
+  len = length(header); \
+  for (i = 1; i <= len; i = i + 1) \
+    printf "="; \
+  printf "\n\n"; \
+} \
+{ \
+  copy_in_global_log = 1; \
+  global_test_result = "RUN"; \
+  while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+    { \
+      if (rc < 0) \
+         fatal("failed to read from " $$0 ".trs"); \
+      if (line ~ /$(am__global_test_result_rx)/) \
+        { \
+          sub("$(am__global_test_result_rx)", "", line); \
+          sub("[ 	]*$$", "", line); \
+          global_test_result = line; \
+        } \
+      else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \
+        copy_in_global_log = 0; \
+    }; \
+  if (copy_in_global_log) \
+    { \
+      rst_section(global_test_result ": " $$0); \
+      while ((rc = (getline line < ($$0 ".log"))) != 0) \
+      { \
+        if (rc < 0) \
+          fatal("failed to read from " $$0 ".log"); \
+        print line; \
+      }; \
+      printf "\n"; \
+    }; \
+  close ($$0 ".trs"); \
+  close ($$0 ".log"); \
+}'
+# Restructured Text title.
+am__rst_title = { sed 's/.*/   &   /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; }
+# Solaris 10 'make', and several other traditional 'make' implementations,
+# pass "-e" to $(SHELL), and POSIX 2008 even requires this.  Work around it
+# by disabling -e (using the XSI extension "set +e") if it's set.
+am__sh_e_setup = case $$- in *e*) set +e;; esac
+# Default flags passed to test drivers.
+am__common_driver_flags = \
+  --color-tests "$$am__color_tests" \
+  --enable-hard-errors "$$am__enable_hard_errors" \
+  --expect-failure "$$am__expect_failure"
+# To be inserted before the command running the test.  Creates the
+# directory for the log if needed.  Stores in $dir the directory
+# containing $f, in $tst the test, in $log the log.  Executes the
+# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and
+# passes TESTS_ENVIRONMENT.  Set up options for the wrapper that
+# will run the test scripts (or their associated LOG_COMPILER, if
+# thy have one).
+am__check_pre = \
+$(am__sh_e_setup);					\
+$(am__vpath_adj_setup) $(am__vpath_adj)			\
+$(am__tty_colors);					\
+srcdir=$(srcdir); export srcdir;			\
+case "$@" in						\
+  */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;;	\
+    *) am__odir=.;; 					\
+esac;							\
+test "x$$am__odir" = x"." || test -d "$$am__odir" 	\
+  || $(MKDIR_P) "$$am__odir" || exit $$?;		\
+if test -f "./$$f"; then dir=./;			\
+elif test -f "$$f"; then dir=;				\
+else dir="$(srcdir)/"; fi;				\
+tst=$$dir$$f; log='$@'; 				\
+if test -n '$(DISABLE_HARD_ERRORS)'; then		\
+  am__enable_hard_errors=no; 				\
+else							\
+  am__enable_hard_errors=yes; 				\
+fi; 							\
+case " $(XFAIL_TESTS) " in				\
+  *[\ \	]$$f[\ \	]* | *[\ \	]$$dir$$f[\ \	]*) \
+    am__expect_failure=yes;;				\
+  *)							\
+    am__expect_failure=no;;				\
+esac; 							\
+$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT)
+# A shell command to get the names of the tests scripts with any registered
+# extension removed (i.e., equivalently, the names of the test logs, with
+# the '.log' extension removed).  The result is saved in the shell variable
+# '$bases'.  This honors runtime overriding of TESTS and TEST_LOGS.  Sadly,
+# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)",
+# since that might cause problem with VPATH rewrites for suffix-less tests.
+# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'.
+am__set_TESTS_bases = \
+  bases='$(TEST_LOGS)'; \
+  bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
+  bases=`echo $$bases`
+RECHECK_LOGS = $(TEST_LOGS)
+AM_RECURSIVE_TARGETS = check recheck
+TEST_SUITE_LOG = test-suite.log
+TEST_EXTENSIONS = @EXEEXT@ .test
+LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS)
+am__set_b = \
+  case '$@' in \
+    */*) \
+      case '$*' in \
+        */*) b='$*';; \
+          *) b=`echo '$@' | sed 's/\.log$$//'`; \
+       esac;; \
+    *) \
+      b='$*';; \
+  esac
+am__test_logs1 = $(TESTS:=.log)
+am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log)
+TEST_LOGS = $(am__test_logs2:.test.log=.log)
+TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/build-aux/test-driver
+TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \
+	$(TEST_LOG_FLAGS)
 am__DIST_COMMON = $(srcdir)/Makefile.in \
-	$(top_srcdir)/build-aux/depcomp
+	$(top_srcdir)/build-aux/depcomp \
+	$(top_srcdir)/build-aux/test-driver
 DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
 ACLOCAL = @ACLOCAL@
 ALL_LINGUAS = @ALL_LINGUAS@
 AMTAR = @AMTAR@
 AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AM_TESTS_FD_REDIRECT = @AM_TESTS_FD_REDIRECT@
 AR = @AR@
 AUTOCONF = @AUTOCONF@
 AUTOHEADER = @AUTOHEADER@
@@ -323,6 +506,7 @@ LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
 LIPO = @LIPO@
 LN_S = @LN_S@
+LOG_DRIVER = @LOG_DRIVER@
 LTLIBICONV = @LTLIBICONV@
 LTLIBINTL = @LTLIBINTL@
 LTLIBOBJS = @LTLIBOBJS@
@@ -498,7 +682,7 @@ EXTRA_DIST = \
 all: all-am
 
 .SUFFIXES:
-.SUFFIXES: .c .lo .o .obj
+.SUFFIXES: .c .lo .log .o .obj .test .test$(EXEEXT) .trs
 $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am  $(am__configure_deps)
 	@for dep in $?; do \
 	  case '$(am__configure_deps)' in \
@@ -630,98 +814,168 @@ cscopelist-am: $(am__tagged_files)
 distclean-tags:
 	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
 
-check-TESTS: $(TESTS)
-	@failed=0; all=0; xfail=0; xpass=0; skip=0; \
-	srcdir=$(srcdir); export srcdir; \
-	list=' $(TESTS) '; \
-	$(am__tty_colors); \
-	if test -n "$$list"; then \
-	  for tst in $$list; do \
-	    if test -f ./$$tst; then dir=./; \
-	    elif test -f $$tst; then dir=; \
-	    else dir="$(srcdir)/"; fi; \
-	    if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \
-	      all=`expr $$all + 1`; \
-	      case " $(XFAIL_TESTS) " in \
-	      *[\ \	]$$tst[\ \	]*) \
-		xpass=`expr $$xpass + 1`; \
-		failed=`expr $$failed + 1`; \
-		col=$$red; res=XPASS; \
-	      ;; \
-	      *) \
-		col=$$grn; res=PASS; \
-	      ;; \
-	      esac; \
-	    elif test $$? -ne 77; then \
-	      all=`expr $$all + 1`; \
-	      case " $(XFAIL_TESTS) " in \
-	      *[\ \	]$$tst[\ \	]*) \
-		xfail=`expr $$xfail + 1`; \
-		col=$$lgn; res=XFAIL; \
-	      ;; \
-	      *) \
-		failed=`expr $$failed + 1`; \
-		col=$$red; res=FAIL; \
-	      ;; \
-	      esac; \
-	    else \
-	      skip=`expr $$skip + 1`; \
-	      col=$$blu; res=SKIP; \
-	    fi; \
-	    echo "$${col}$$res$${std}: $$tst"; \
-	  done; \
-	  if test "$$all" -eq 1; then \
-	    tests="test"; \
-	    All=""; \
-	  else \
-	    tests="tests"; \
-	    All="All "; \
+# Recover from deleted '.trs' file; this should ensure that
+# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create
+# both 'foo.log' and 'foo.trs'.  Break the recipe in two subshells
+# to avoid problems with "make -n".
+.log.trs:
+	rm -f $< $@
+	$(MAKE) $(AM_MAKEFLAGS) $<
+
+# Leading 'am--fnord' is there to ensure the list of targets does not
+# expand to empty, as could happen e.g. with make check TESTS=''.
+am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
+am--force-recheck:
+	@:
+
+$(TEST_SUITE_LOG): $(TEST_LOGS)
+	@$(am__set_TESTS_bases); \
+	am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
+	redo_bases=`for i in $$bases; do \
+	              am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \
+	            done`; \
+	if test -n "$$redo_bases"; then \
+	  redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \
+	  redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \
+	  if $(am__make_dryrun); then :; else \
+	    rm -f $$redo_logs && rm -f $$redo_results || exit 1; \
 	  fi; \
-	  if test "$$failed" -eq 0; then \
-	    if test "$$xfail" -eq 0; then \
-	      banner="$$All$$all $$tests passed"; \
-	    else \
-	      if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \
-	      banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \
-	    fi; \
-	  else \
-	    if test "$$xpass" -eq 0; then \
-	      banner="$$failed of $$all $$tests failed"; \
+	fi; \
+	if test -n "$$am__remaking_logs"; then \
+	  echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \
+	       "recursion detected" >&2; \
+	elif test -n "$$redo_logs"; then \
+	  am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \
+	fi; \
+	if $(am__make_dryrun); then :; else \
+	  st=0;  \
+	  errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \
+	  for i in $$redo_bases; do \
+	    test -f $$i.trs && test -r $$i.trs \
+	      || { echo "$$errmsg $$i.trs" >&2; st=1; }; \
+	    test -f $$i.log && test -r $$i.log \
+	      || { echo "$$errmsg $$i.log" >&2; st=1; }; \
+	  done; \
+	  test $$st -eq 0 || exit 1; \
+	fi
+	@$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \
+	ws='[ 	]'; \
+	results=`for b in $$bases; do echo $$b.trs; done`; \
+	test -n "$$results" || results=/dev/null; \
+	all=`  grep "^$$ws*:test-result:"           $$results | wc -l`; \
+	pass=` grep "^$$ws*:test-result:$$ws*PASS"  $$results | wc -l`; \
+	fail=` grep "^$$ws*:test-result:$$ws*FAIL"  $$results | wc -l`; \
+	skip=` grep "^$$ws*:test-result:$$ws*SKIP"  $$results | wc -l`; \
+	xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \
+	xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \
+	error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \
+	if test `expr $$fail + $$xpass + $$error` -eq 0; then \
+	  success=true; \
+	else \
+	  success=false; \
+	fi; \
+	br='==================='; br=$$br$$br$$br$$br; \
+	result_count () \
+	{ \
+	    if test x"$$1" = x"--maybe-color"; then \
+	      maybe_colorize=yes; \
+	    elif test x"$$1" = x"--no-color"; then \
+	      maybe_colorize=no; \
 	    else \
-	      if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \
-	      banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \
+	      echo "$@: invalid 'result_count' usage" >&2; exit 4; \
 	    fi; \
-	  fi; \
-	  dashes="$$banner"; \
-	  skipped=""; \
-	  if test "$$skip" -ne 0; then \
-	    if test "$$skip" -eq 1; then \
-	      skipped="($$skip test was not run)"; \
+	    shift; \
+	    desc=$$1 count=$$2; \
+	    if test $$maybe_colorize = yes && test $$count -gt 0; then \
+	      color_start=$$3 color_end=$$std; \
 	    else \
-	      skipped="($$skip tests were not run)"; \
+	      color_start= color_end=; \
 	    fi; \
-	    test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \
-	      dashes="$$skipped"; \
-	  fi; \
-	  report=""; \
-	  if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \
-	    report="Please report to $(PACKAGE_BUGREPORT)"; \
-	    test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \
-	      dashes="$$report"; \
-	  fi; \
-	  dashes=`echo "$$dashes" | sed s/./=/g`; \
-	  if test "$$failed" -eq 0; then \
-	    col="$$grn"; \
-	  else \
-	    col="$$red"; \
-	  fi; \
-	  echo "$${col}$$dashes$${std}"; \
-	  echo "$${col}$$banner$${std}"; \
-	  test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \
-	  test -z "$$report" || echo "$${col}$$report$${std}"; \
-	  echo "$${col}$$dashes$${std}"; \
-	  test "$$failed" -eq 0; \
-	else :; fi
+	    echo "$${color_start}# $$desc $$count$${color_end}"; \
+	}; \
+	create_testsuite_report () \
+	{ \
+	  result_count $$1 "TOTAL:" $$all   "$$brg"; \
+	  result_count $$1 "PASS: " $$pass  "$$grn"; \
+	  result_count $$1 "SKIP: " $$skip  "$$blu"; \
+	  result_count $$1 "XFAIL:" $$xfail "$$lgn"; \
+	  result_count $$1 "FAIL: " $$fail  "$$red"; \
+	  result_count $$1 "XPASS:" $$xpass "$$red"; \
+	  result_count $$1 "ERROR:" $$error "$$mgn"; \
+	}; \
+	{								\
+	  echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" |	\
+	    $(am__rst_title);						\
+	  create_testsuite_report --no-color;				\
+	  echo;								\
+	  echo ".. contents:: :depth: 2";				\
+	  echo;								\
+	  for b in $$bases; do echo $$b; done				\
+	    | $(am__create_global_log);					\
+	} >$(TEST_SUITE_LOG).tmp || exit 1;				\
+	mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG);			\
+	if $$success; then						\
+	  col="$$grn";							\
+	 else								\
+	  col="$$red";							\
+	  test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG);		\
+	fi;								\
+	echo "$${col}$$br$${std}"; 					\
+	echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}";	\
+	echo "$${col}$$br$${std}"; 					\
+	create_testsuite_report --maybe-color;				\
+	echo "$$col$$br$$std";						\
+	if $$success; then :; else					\
+	  echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}";		\
+	  if test -n "$(PACKAGE_BUGREPORT)"; then			\
+	    echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}";	\
+	  fi;								\
+	  echo "$$col$$br$$std";					\
+	fi;								\
+	$$success || exit 1
+
+check-TESTS:
+	@list='$(RECHECK_LOGS)';           test -z "$$list" || rm -f $$list
+	@list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
+	@test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+	@set +e; $(am__set_TESTS_bases); \
+	log_list=`for i in $$bases; do echo $$i.log; done`; \
+	trs_list=`for i in $$bases; do echo $$i.trs; done`; \
+	log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
+	$(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
+	exit $$?;
+recheck: all 
+	@test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+	@set +e; $(am__set_TESTS_bases); \
+	bases=`for i in $$bases; do echo $$i; done \
+	         | $(am__list_recheck_tests)` || exit 1; \
+	log_list=`for i in $$bases; do echo $$i.log; done`; \
+	log_list=`echo $$log_list`; \
+	$(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \
+	        am__force_recheck=am--force-recheck \
+	        TEST_LOGS="$$log_list"; \
+	exit $$?
+test-config.log: test-config$(EXEEXT)
+	@p='test-config$(EXEEXT)'; \
+	b='test-config'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+.test.log:
+	@p='$<'; \
+	$(am__set_b); \
+	$(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+@am__EXEEXT_TRUE@.test$(EXEEXT).log:
+@am__EXEEXT_TRUE@	@p='$<'; \
+@am__EXEEXT_TRUE@	$(am__set_b); \
+@am__EXEEXT_TRUE@	$(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+@am__EXEEXT_TRUE@	--log-file $$b.log --trs-file $$b.trs \
+@am__EXEEXT_TRUE@	$(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+@am__EXEEXT_TRUE@	"$$tst" $(AM_TESTS_FD_REDIRECT)
 
 distdir: $(DISTFILES)
 	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
@@ -778,6 +1032,9 @@ install-strip:
 	    "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
 	fi
 mostlyclean-generic:
+	-test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
+	-test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
+	-test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
 
 clean-generic:
 
@@ -873,7 +1130,7 @@ uninstall-am:
 	installcheck-am installdirs maintainer-clean \
 	maintainer-clean-generic mostlyclean mostlyclean-compile \
 	mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
-	tags tags-am uninstall uninstall-am
+	recheck tags tags-am uninstall uninstall-am
 
 .PRECIOUS: Makefile
 
diff --git a/src/tests/config/NetworkManager.conf b/src/tests/config/NetworkManager.conf
index 401ba48a..a750c801 100644
--- a/src/tests/config/NetworkManager.conf
+++ b/src/tests/config/NetworkManager.conf
@@ -13,3 +13,71 @@ response=Hello
 
 [extra-section]
 extra-key=some value
+
+
+
+[connection]
+ipv4.route-metric=50
+ipv6.ip6_privacy=0
+dummy.test1=no
+dummy.test2=no
+
+ord.key00=A-0.0.00
+ord.key01=A-0.0.01
+ord.key02=A-0.0.02
+ord.key03=A-0.0.03
+ord.key04=A-0.0.04
+ord.key05=A-0.0.05
+ord.key06=A-0.0.06
+ord.key07=A-0.0.07
+ord.key08=A-0.0.08
+ord.key09=A-0.0.09
+
+[connection.dev51]
+match-device=mac:00:00:00:00:00:51
+stop-match=yes
+ipv4.route-metric=51
+dummy.test1=yes
+
+[connection.dev52]
+match-device=mac:00:00:00:00:00:52
+ipv4.route-metric=52
+
+[connection.public]
+match-device=interface-name:wlan1
+# match-wifi is not yet implemented. Just an idea what could be useful.
+match-wifi=ssid:*[Ss]tarbucks*|*University*
+ipv6.ip6_privacy=2
+
+
+# the following sections are tested for their order across
+# multiple files.
+[connection.ord.0.1]
+ord.key03=A-0.1.03
+ord.key04=A-0.1.04
+ord.key05=A-0.1.05
+ord.key06=A-0.1.06
+ord.key07=A-0.1.07
+ord.key08=A-0.1.08
+ord.key09=A-0.1.09
+ord.ovw01=A-0.1.ovw01
+[connection.ord.0.2]
+ord.key02=A-0.2.02
+ord.key03=A-0.2.03
+ord.key04=A-0.2.04
+ord.key05=A-0.2.05
+ord.key06=A-0.2.06
+ord.key07=A-0.2.07
+ord.key08=A-0.2.08
+ord.key09=A-0.2.09
+[connection.ord.0.3]
+ord.key01=A-0.3.01
+ord.key02=A-0.3.02
+ord.key03=A-0.3.03
+ord.key04=A-0.3.04
+ord.key05=A-0.3.05
+ord.key06=A-0.3.06
+ord.key07=A-0.3.07
+ord.key08=A-0.3.08
+ord.key09=A-0.3.09
+ord.ovw01=A-0.3.ovw01
diff --git a/src/tests/config/conf.d/00-overrides.conf b/src/tests/config/conf.d/00-overrides.conf
index 0aa19d48..f26ed93b 100644
--- a/src/tests/config/conf.d/00-overrides.conf
+++ b/src/tests/config/conf.d/00-overrides.conf
@@ -1,11 +1,57 @@
 [main]
 dhcp=dhcpcd
 
+no-auto-default=spec1,spec2
+ignore-carrier=\s space1 \s
+
 [logging]
 domains=PLATFORM,DNS,WIFI
 
+[appendable-test]
+non-appendable-key1+=i-will-be-dropped
+non-appendable-key2-=i-will-be-dropped
+
 [order]
 a=0
 b=0
 c=0
 
+
+# the following sections are tested for their order across
+# multiple files.
+[connection.ord.1.1]
+ord.key06=B-1.1.06
+ord.key07=B-1.1.07
+ord.key08=B-1.1.08
+ord.key09=B-1.1.09
+[connection.ord.1.2]
+ord.key05=B-1.2.05
+ord.key06=B-1.2.06
+ord.key07=B-1.2.07
+ord.key08=B-1.2.08
+ord.key09=B-1.2.09
+[connection.ord.1.3]
+ord.key04=B-1.3.04
+ord.key05=B-1.3.05
+ord.key06=B-1.3.06
+ord.key07=B-1.3.07
+ord.key08=B-1.3.08
+ord.key09=B-1.3.09
+
+
+[.test-append-stringlist.1]
+val1=a,b
+
+val2-=VAL2
+val2=VAL2
+
+val3=VAL3
+val3-=VAL3
+
+val4=VAL4
+val4+=VAL4,va,vb,va,vb
+val4-=VAL4,va
+
+val5=VAL5
+val5-=VAL5
+val5+=VAL5
diff --git a/src/tests/config/conf.d/10-more.conf b/src/tests/config/conf.d/10-more.conf
index b1424a4b..eadb7f96 100644
--- a/src/tests/config/conf.d/10-more.conf
+++ b/src/tests/config/conf.d/10-more.conf
@@ -1,5 +1,12 @@
 [main]
 extra=hello
+
+no-auto-default-=spec1
+no-auto-default+=spec3
+
+ignore-carrier+=\sspace2\t	
+
+[.test-append-stringlist.0]
 new+=something
 
 [connectivity]
@@ -9,3 +16,25 @@ uri=http://example.net
 a=10
 b=10
 
+# the following sections are tested for their order across
+# multiple files.
+[connection.ord.2.1]
+ord.key09=C-2.1.09
+[connection.ord.2.2]
+ord.key08=C-2.2.08
+ord.key09=C-2.2.09
+[connection.ord.2.3]
+ord.key07=C-2.3.07
+ord.key08=C-2.3.08
+ord.key09=C-2.3.09
+
+# you can overwrite individual settings in a file loaded
+# previously. But note that this does not bump the priority
+# of the section, i.e. [connection.ord.0.1] still has a pretty
+# low priority and is shadowed by [connection.ord.2.1].
+[connection.ord.0.1]
+ord.ovw01=C-0.1.ovw01
+
+
+[.test-append-stringlist.1]
+val1-=b
diff --git a/src/tests/config/conf.d/90-last.conf b/src/tests/config/conf.d/90-last.conf
index dc1de394..7d078788 100644
--- a/src/tests/config/conf.d/90-last.conf
+++ b/src/tests/config/conf.d/90-last.conf
@@ -3,3 +3,6 @@ plugins+=one,two
 
 [order]
 a=90
+
+[.test-append-stringlist.1]
+val1+=c,a
diff --git a/src/tests/config/nm-test-device.c b/src/tests/config/nm-test-device.c
index 512085e9..b5f65ded 100644
--- a/src/tests/config/nm-test-device.c
+++ b/src/tests/config/nm-test-device.c
@@ -61,7 +61,7 @@ dispose (GObject *object)
 	g_object_class->dispose (object);
 }
 
-static guint32
+static NMDeviceCapabilities
 get_generic_capabilities (NMDevice *device)
 {
 	return NM_DEVICE_CAP_IS_NON_KERNEL;
diff --git a/src/tests/config/test-config.c b/src/tests/config/test-config.c
index e027eab8..8b8028e6 100644
--- a/src/tests/config/test-config.c
+++ b/src/tests/config/test-config.c
@@ -32,8 +32,8 @@
 
 #include "nm-test-utils.h"
 
-static void
-setup_config (const char *config_file, const char *config_dir, ...)
+static NMConfig *
+setup_config (GError **error, const char *config_file, const char *config_dir, ...)
 {
 	va_list ap;
 	GPtrArray *args;
@@ -41,6 +41,11 @@ setup_config (const char *config_file, const char *config_dir, ...)
 	int argc;
 	GOptionContext *context;
 	gboolean success;
+	NMConfig *config;
+	GError *local_error = NULL;
+	NMConfigCmdLineOptions *cli;
+
+	g_assert (!error || !*error);
 
 	args = g_ptr_array_new ();
 	g_ptr_array_add (args, "test-config");
@@ -57,8 +62,10 @@ setup_config (const char *config_file, const char *config_dir, ...)
 	argv = (char **)args->pdata;
 	argc = args->len;
 
+	cli = nm_config_cmd_line_options_new ();
+
 	context = g_option_context_new (NULL);
-	g_option_context_add_main_entries (context, nm_config_get_options (), NULL);
+	nm_config_cmd_line_options_add_to_entries (cli, context);
 	success = g_option_context_parse (context, &argc, &argv, NULL);
 	g_option_context_free (context);
 
@@ -66,24 +73,36 @@ setup_config (const char *config_file, const char *config_dir, ...)
 		g_printerr ("Invalid options.\n");
 
 	g_ptr_array_free (args, TRUE);
+
+	config = nm_config_setup (cli, &local_error);
+	if (error) {
+		g_assert (!config);
+		g_assert (local_error);
+		g_propagate_error (error, local_error);
+	} else {
+		g_assert (config);
+		g_assert_no_error (local_error);
+	}
+	nm_config_cmd_line_options_free (cli);
+	return config;
 }
 
 static void
 test_config_simple (void)
 {
 	NMConfig *config;
-	GError *error = NULL;
 	const char **plugins;
 	char *value;
+	gs_unref_object NMDevice *dev50 = nm_test_device_new ("00:00:00:00:00:50");
+	gs_unref_object NMDevice *dev51 = nm_test_device_new ("00:00:00:00:00:51");
+	gs_unref_object NMDevice *dev52 = nm_test_device_new ("00:00:00:00:00:52");
 
-	setup_config (SRCDIR "/NetworkManager.conf", "/no/such/dir", NULL);
-	config = nm_config_new (&error);
-	g_assert_no_error (error);
+	config = setup_config (NULL, SRCDIR "/NetworkManager.conf", "/no/such/dir", NULL);
 
-	g_assert_cmpstr (nm_config_get_path (config), ==, SRCDIR "/NetworkManager.conf");
+	g_assert_cmpstr (nm_config_data_get_config_main_file (nm_config_get_data_orig (config)), ==, SRCDIR "/NetworkManager.conf");
 	g_assert_cmpstr (nm_config_get_dhcp_client (config), ==, "dhclient");
 	g_assert_cmpstr (nm_config_get_log_level (config), ==, "INFO");
-	g_assert_cmpint (nm_config_get_connectivity_interval (config), ==, 100);
+	g_assert_cmpint (nm_config_data_get_connectivity_interval (nm_config_get_data_orig (config)), ==, 100);
 
 	plugins = nm_config_get_plugins (config);
 	g_assert_cmpint (g_strv_length ((char **)plugins), ==, 3);
@@ -91,19 +110,63 @@ test_config_simple (void)
 	g_assert_cmpstr (plugins[1], ==, "bar");
 	g_assert_cmpstr (plugins[2], ==, "baz");
 
-	value = nm_config_get_value (config, "extra-section", "extra-key", NULL);
+	value = nm_config_data_get_value (nm_config_get_data_orig (config), "extra-section", "extra-key", NM_CONFIG_GET_VALUE_NONE);
 	g_assert_cmpstr (value, ==, "some value");
 	g_free (value);
 
-	value = nm_config_get_value (config, "extra-section", "no-key", &error);
+	value = nm_config_data_get_value (nm_config_get_data_orig (config), "extra-section", "no-key", NM_CONFIG_GET_VALUE_NONE);
 	g_assert (!value);
-	g_assert_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND);
-	g_clear_error (&error);
 
-	value = nm_config_get_value (config, "no-section", "no-key", &error);
+	value = nm_config_data_get_value (nm_config_get_data_orig (config), "no-section", "no-key", NM_CONFIG_GET_VALUE_NONE);
 	g_assert (!value);
-	g_assert_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND);
-	g_clear_error (&error);
+
+	value = nm_config_data_get_value (nm_config_get_data_orig (config), "connection", "ipv6.ip6_privacy", NM_CONFIG_GET_VALUE_NONE);
+	g_assert_cmpstr (value, ==, "0");
+	g_free (value);
+
+	value = nm_config_data_get_value (nm_config_get_data_orig (config), "connection.dev51", "ipv4.route-metric", NM_CONFIG_GET_VALUE_NONE);
+	g_assert_cmpstr (value, ==, "51");
+	g_free (value);
+
+
+	value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "ipv6.route-metric", NULL);
+	g_assert_cmpstr (value, ==, NULL);
+	g_free (value);
+
+
+	value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "ipv4.route-metric", NULL);
+	g_assert_cmpstr (value, ==, "50");
+	g_free (value);
+
+	value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "ipv4.route-metric", dev50);
+	g_assert_cmpstr (value, ==, "50");
+	g_free (value);
+
+	value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "ipv4.route-metric", dev51);
+	g_assert_cmpstr (value, ==, "51");
+	g_free (value);
+
+	value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "ipv4.route-metric", dev52);
+	g_assert_cmpstr (value, ==, "52");
+	g_free (value);
+
+
+	value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "dummy.test1", dev51);
+	g_assert_cmpstr (value, ==, "yes");
+	g_free (value);
+
+	value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "dummy.test1", dev50);
+	g_assert_cmpstr (value, ==, "no");
+	g_free (value);
+
+	value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "dummy.test2", dev51);
+	g_assert_cmpstr (value, ==, NULL);
+	g_free (value);
+
+	value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "dummy.test2", dev50);
+	g_assert_cmpstr (value, ==, "no");
+	g_free (value);
+
 
 	g_object_unref (config);
 }
@@ -111,45 +174,38 @@ test_config_simple (void)
 static void
 test_config_non_existent (void)
 {
-	NMConfig *config;
 	gs_free_error GError *error = NULL;
 
-	setup_config (SRCDIR "/no-such-file", "/no/such/dir", NULL);
-	config = nm_config_new (&error);
-	g_assert (!config);
+	setup_config (&error, SRCDIR "/no-such-file", "/no/such/dir", NULL);
 	g_assert_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND);
+	g_clear_error (&error);
 }
 
 static void
 test_config_parse_error (void)
 {
-	NMConfig *config;
 	gs_free_error GError *error = NULL;
 
-	setup_config (SRCDIR "/bad.conf", "/no/such/dir", NULL);
-	config = nm_config_new (&error);
-	g_assert (!config);
+	setup_config (&error, SRCDIR "/bad.conf", "/no/such/dir", NULL);
 	g_assert_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE);
+	g_clear_error (&error);
 }
 
 static void
 test_config_override (void)
 {
 	NMConfig *config;
-	GError *error = NULL;
 	const char **plugins;
 
-	setup_config (SRCDIR "/NetworkManager.conf", "/no/such/dir",
-	              "--plugins", "alpha,beta,gamma,delta",
-	              "--connectivity-interval", "12",
-	              NULL);
-	config = nm_config_new (&error);
-	g_assert_no_error (error);
+	config = setup_config (NULL, SRCDIR "/NetworkManager.conf", "/no/such/dir",
+	                       "--plugins", "alpha,beta,gamma,delta",
+	                       "--connectivity-interval", "12",
+	                       NULL);
 
-	g_assert_cmpstr (nm_config_get_path (config), ==, SRCDIR "/NetworkManager.conf");
+	g_assert_cmpstr (nm_config_data_get_config_main_file (nm_config_get_data_orig (config)), ==, SRCDIR "/NetworkManager.conf");
 	g_assert_cmpstr (nm_config_get_dhcp_client (config), ==, "dhclient");
 	g_assert_cmpstr (nm_config_get_log_level (config), ==, "INFO");
-	g_assert_cmpint (nm_config_get_connectivity_interval (config), ==, 12);
+	g_assert_cmpint (nm_config_data_get_connectivity_interval (nm_config_get_data_orig (config)), ==, 12);
 
 	plugins = nm_config_get_plugins (config);
 	g_assert_cmpint (g_strv_length ((char **)plugins), ==, 4);
@@ -179,37 +235,36 @@ test_config_no_auto_default (void)
 	g_assert_cmpint (nwrote, ==, 18);
 	close (fd);
 
-	setup_config (SRCDIR "/NetworkManager.conf", "/no/such/dir",
-	              "--no-auto-default", state_file,
-	              NULL);
-	config = nm_config_new (&error);
-	g_assert_no_error (error);
+	config = setup_config (NULL, SRCDIR "/NetworkManager.conf", "/no/such/dir",
+	                       "--no-auto-default", state_file,
+	                       NULL);
 
 	dev1 = nm_test_device_new ("11:11:11:11:11:11");
 	dev2 = nm_test_device_new ("22:22:22:22:22:22");
 	dev3 = nm_test_device_new ("33:33:33:33:33:33");
 	dev4 = nm_test_device_new ("44:44:44:44:44:44");
 
-	g_assert (!nm_config_get_ethernet_can_auto_default (config, dev1));
-	g_assert (!nm_config_get_ethernet_can_auto_default (config, dev2));
-	g_assert (nm_config_get_ethernet_can_auto_default (config, dev3));
-	g_assert (!nm_config_get_ethernet_can_auto_default (config, dev4));
+	g_assert (nm_config_get_no_auto_default_for_device (config, dev1));
+	g_assert (nm_config_get_no_auto_default_for_device (config, dev2));
+	g_assert (!nm_config_get_no_auto_default_for_device (config, dev3));
+	g_assert (nm_config_get_no_auto_default_for_device (config, dev4));
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*config: update * (no-auto-default)*");
+	nm_config_set_no_auto_default_for_device (config, dev3);
+	g_test_assert_expected_messages ();
 
-	nm_config_set_ethernet_no_auto_default (config, dev3);
-	g_assert (!nm_config_get_ethernet_can_auto_default (config, dev3));
+	g_assert (nm_config_get_no_auto_default_for_device (config, dev3));
 
 	g_object_unref (config);
 
-	setup_config (SRCDIR "/NetworkManager.conf", "/no/such/dir",
-	              "--no-auto-default", state_file,
-	              NULL);
-	config = nm_config_new (&error);
-	g_assert_no_error (error);
+	config = setup_config (NULL, SRCDIR "/NetworkManager.conf", "/no/such/dir",
+	                       "--no-auto-default", state_file,
+	                       NULL);
 
-	g_assert (!nm_config_get_ethernet_can_auto_default (config, dev1));
-	g_assert (!nm_config_get_ethernet_can_auto_default (config, dev2));
-	g_assert (!nm_config_get_ethernet_can_auto_default (config, dev3));
-	g_assert (!nm_config_get_ethernet_can_auto_default (config, dev4));
+	g_assert (nm_config_get_no_auto_default_for_device (config, dev1));
+	g_assert (nm_config_get_no_auto_default_for_device (config, dev2));
+	g_assert (nm_config_get_no_auto_default_for_device (config, dev3));
+	g_assert (nm_config_get_no_auto_default_for_device (config, dev4));
 
 	g_object_unref (config);
 
@@ -226,20 +281,18 @@ static void
 test_config_confdir (void)
 {
 	NMConfig *config;
-	GError *error = NULL;
 	const char **plugins;
 	char *value;
+	GSList *specs;
 
-	setup_config (SRCDIR "/NetworkManager.conf", SRCDIR "/conf.d", NULL);
-	config = nm_config_new (&error);
-	g_assert_no_error (error);
+	config = setup_config (NULL, SRCDIR "/NetworkManager.conf", SRCDIR "/conf.d", NULL);
 
-	g_assert_cmpstr (nm_config_get_path (config), ==, SRCDIR "/NetworkManager.conf");
+	g_assert_cmpstr (nm_config_data_get_config_main_file (nm_config_get_data_orig (config)), ==, SRCDIR "/NetworkManager.conf");
 	g_assert_cmpstr (nm_config_get_dhcp_client (config), ==, "dhcpcd");
 	g_assert_cmpstr (nm_config_get_log_level (config), ==, "INFO");
 	g_assert_cmpstr (nm_config_get_log_domains (config), ==, "PLATFORM,DNS,WIFI");
-	g_assert_cmpstr (nm_config_get_connectivity_uri (config), ==, "http://example.net");
-	g_assert_cmpint (nm_config_get_connectivity_interval (config), ==, 100);
+	g_assert_cmpstr (nm_config_data_get_connectivity_uri (nm_config_get_data_orig (config)), ==, "http://example.net");
+	g_assert_cmpint (nm_config_data_get_connectivity_interval (nm_config_get_data_orig (config)), ==, 100);
 
 	plugins = nm_config_get_plugins (config);
 	g_assert_cmpint (g_strv_length ((char **)plugins), ==, 5);
@@ -249,38 +302,98 @@ test_config_confdir (void)
 	g_assert_cmpstr (plugins[3], ==, "one");
 	g_assert_cmpstr (plugins[4], ==, "two");
 
-	value = nm_config_get_value (config, "main", "extra", NULL);
+	value = nm_config_data_get_value (nm_config_get_data_orig (config), "main", "extra", NM_CONFIG_GET_VALUE_NONE);
 	g_assert_cmpstr (value, ==, "hello");
 	g_free (value);
 
-	value = nm_config_get_value (config, "main", "new", NULL);
+	value = nm_config_data_get_value (nm_config_get_data_orig (config), "main", "no-auto-default", NM_CONFIG_GET_VALUE_TYPE_SPEC);
+	specs = nm_match_spec_split (value);
+	g_free (value);
+	g_assert_cmpint (g_slist_length (specs), ==, 2);
+	g_assert_cmpstr (g_slist_nth_data (specs, 0), ==, "spec2");
+	g_assert_cmpstr (g_slist_nth_data (specs, 1), ==, "spec3");
+	g_slist_free_full (specs, g_free);
+
+	value = nm_config_data_get_value (nm_config_get_data_orig (config), "main", "ignore-carrier", NM_CONFIG_GET_VALUE_TYPE_SPEC);
+	specs = nm_match_spec_split (value);
+	g_free (value);
+	g_assert_cmpint (g_slist_length (specs), ==, 2);
+	g_assert_cmpstr (g_slist_nth_data (specs, 0), ==, "  space1  ");
+	g_assert_cmpstr (g_slist_nth_data (specs, 1), ==, " space2\t");
+	g_slist_free_full (specs, g_free);
+
+	value = nm_config_data_get_value (nm_config_get_data_orig (config), NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST".0", "new", NM_CONFIG_GET_VALUE_NONE);
 	g_assert_cmpstr (value, ==, "something"); /* not ",something" */
 	g_free (value);
 
-	value = nm_config_get_value (config, "order", "a", NULL);
+	value = nm_config_data_get_value (nm_config_get_data_orig (config), "order", "a", NM_CONFIG_GET_VALUE_NONE);
 	g_assert_cmpstr (value, ==, "90");
 	g_free (value);
-	value = nm_config_get_value (config, "order", "b", NULL);
+	value = nm_config_data_get_value (nm_config_get_data_orig (config), "order", "b", NM_CONFIG_GET_VALUE_NONE);
 	g_assert_cmpstr (value, ==, "10");
 	g_free (value);
-	value = nm_config_get_value (config, "order", "c", NULL);
+	value = nm_config_data_get_value (nm_config_get_data_orig (config), "order", "c", NM_CONFIG_GET_VALUE_NONE);
 	g_assert_cmpstr (value, ==, "0");
 	g_free (value);
 
+	g_assert (!nm_config_data_has_value (nm_config_get_data_orig (config), "appendable-test", "non-appendable-key1", NM_CONFIG_GET_VALUE_RAW));
+	g_assert (!nm_config_data_has_value (nm_config_get_data_orig (config), "appendable-test", "non-appendable-key1+", NM_CONFIG_GET_VALUE_RAW));
+	g_assert (!nm_config_data_has_value (nm_config_get_data_orig (config), "appendable-test", "non-appendable-key1-", NM_CONFIG_GET_VALUE_RAW));
+	g_assert (!nm_config_data_has_value (nm_config_get_data_orig (config), "appendable-test", "non-appendable-key2", NM_CONFIG_GET_VALUE_RAW));
+	g_assert (!nm_config_data_has_value (nm_config_get_data_orig (config), "appendable-test", "non-appendable-key2+", NM_CONFIG_GET_VALUE_RAW));
+	g_assert (!nm_config_data_has_value (nm_config_get_data_orig (config), "appendable-test", "non-appendable-key2-", NM_CONFIG_GET_VALUE_RAW));
+
+#define ASSERT_GET_CONN_DEFAULT(xconfig, xname, xvalue) \
+	G_STMT_START { \
+		gs_free char *_value = nm_config_data_get_connection_default (nm_config_get_data_orig (xconfig), (xname), NULL); \
+		g_assert_cmpstr (_value, ==, (xvalue)); \
+	} G_STMT_END
+	ASSERT_GET_CONN_DEFAULT (config, "ord.key00", "A-0.0.00");
+	ASSERT_GET_CONN_DEFAULT (config, "ord.key01", "A-0.3.01");
+	ASSERT_GET_CONN_DEFAULT (config, "ord.key02", "A-0.2.02");
+	ASSERT_GET_CONN_DEFAULT (config, "ord.key03", "A-0.1.03");
+	ASSERT_GET_CONN_DEFAULT (config, "ord.key04", "B-1.3.04");
+	ASSERT_GET_CONN_DEFAULT (config, "ord.key05", "B-1.2.05");
+	ASSERT_GET_CONN_DEFAULT (config, "ord.key06", "B-1.1.06");
+	ASSERT_GET_CONN_DEFAULT (config, "ord.key07", "C-2.3.07");
+	ASSERT_GET_CONN_DEFAULT (config, "ord.key08", "C-2.2.08");
+	ASSERT_GET_CONN_DEFAULT (config, "ord.key09", "C-2.1.09");
+	ASSERT_GET_CONN_DEFAULT (config, "ord.ovw01", "C-0.1.ovw01");
+
+	value = nm_config_data_get_value (nm_config_get_data_orig (config), NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST".1", "val1", NM_CONFIG_GET_VALUE_NONE);
+	g_assert_cmpstr (value, ==, "a,c");
+	g_free (value);
+
+	value = nm_config_data_get_value (nm_config_get_data_orig (config), NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST".1", "val2", NM_CONFIG_GET_VALUE_NONE);
+	g_assert_cmpstr (value, ==, "VAL2");
+	g_free (value);
+
+	value = nm_config_data_get_value (nm_config_get_data_orig (config), NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST".1", "val3", NM_CONFIG_GET_VALUE_NONE);
+	g_assert_cmpstr (value, ==, NULL);
+	g_free (value);
+
+	value = nm_config_data_get_value (nm_config_get_data_orig (config), NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST".1", "val4", NM_CONFIG_GET_VALUE_NONE);
+	g_assert_cmpstr (value, ==, "vb,vb");
+	g_free (value);
+
+	value = nm_config_data_get_value (nm_config_get_data_orig (config), NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST".1", "val5", NM_CONFIG_GET_VALUE_NONE);
+	g_assert_cmpstr (value, ==, "VAL5");
+	g_free (value);
+
+	nm_config_data_log (nm_config_get_data_orig (config), ">>> TEST: ");
+
 	g_object_unref (config);
 }
 
 static void
 test_config_confdir_parse_error (void)
 {
-	NMConfig *config;
-	gs_free_error GError *error = NULL;
+	GError *error = NULL;
 
 	/* Using SRCDIR as the conf dir will pick up bad.conf */
-	setup_config (SRCDIR "/NetworkManager.conf", SRCDIR, NULL);
-	config = nm_config_new (&error);
-	g_assert (!config);
+	setup_config (&error, SRCDIR "/NetworkManager.conf", SRCDIR, NULL);
 	g_assert_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE);
+	g_clear_error (&error);
 }
 
 NMTST_DEFINE ();
diff --git a/src/tests/test-general-with-expect.c b/src/tests/test-general-with-expect.c
index 4a0627c9..bb52bde2 100644
--- a/src/tests/test-general-with-expect.c
+++ b/src/tests/test-general-with-expect.c
@@ -29,6 +29,7 @@
 
 #include "NetworkManagerUtils.h"
 #include "nm-logging.h"
+#include "nm-multi-index.h"
 
 #include "nm-test-utils.h"
 
@@ -330,11 +331,109 @@ test_nm_utils_kill_child (void)
 	g_test_assert_expected_messages ();
 }
 
+/*******************************************/
+
+static void
+_remove_at_indexes_init_random_idx (GArray *idx, guint array_len, guint idx_len)
+{
+	GRand *rand = nmtst_get_rand ();
+	gs_free char *mask = NULL;
+	guint i, max_test_idx;
+
+	g_assert (idx);
+	g_assert (array_len > 0);
+	g_assert (idx_len >= 1 && idx_len <= array_len);
+
+	mask = g_new0 (char, array_len);
+
+	max_test_idx = array_len - 1;
+	for (i = 0; i < idx_len; i++) {
+		guint itest;
+
+		/* find a index itest that is not yet taken */
+		if (max_test_idx == 0)
+			itest = 0;
+		else
+			itest = g_rand_int_range (rand, 0, max_test_idx);
+		while (itest < array_len && mask[itest])
+			itest++;
+		g_assert (itest <= max_test_idx);
+		g_assert (!mask[itest]);
+
+		mask[itest] = TRUE;
+		if (itest == max_test_idx) {
+			g_assert (max_test_idx > 0 || i == idx_len - 1);
+
+			if (max_test_idx == 0)
+				g_assert_cmpint (i, ==, idx_len - 1);
+			else {
+				max_test_idx--;
+				while (max_test_idx > 0 && mask[max_test_idx])
+					max_test_idx--;
+				if (mask[max_test_idx])
+					g_assert_cmpint (i, ==, idx_len - 1);
+			}
+		}
+	}
+
+	g_array_set_size (idx, 0);
+	for (i = 0; i < array_len; i++) {
+		if (mask[i])
+			g_array_append_val (idx, i);
+	}
+	g_assert_cmpint (idx->len, ==, idx_len);
+}
+
+static void
+test_nm_utils_array_remove_at_indexes ()
+{
+	gs_unref_array GArray *idx = NULL, *array = NULL;
+	gs_unref_hashtable GHashTable *unique = NULL;
+	guint i_len, i_idx_len, i_rnd, i;
+
+	idx = g_array_new (FALSE, FALSE, sizeof (guint));
+	array = g_array_new (FALSE, FALSE, sizeof (gssize));
+	unique = g_hash_table_new (NULL, NULL);
+	for (i_len = 1; i_len < 20; i_len++) {
+		for (i_idx_len = 1; i_idx_len <= i_len; i_idx_len++) {
+			for (i_rnd = 0; i_rnd < 20; i_rnd++) {
+
+				_remove_at_indexes_init_random_idx (idx, i_len, i_idx_len);
+				g_array_set_size (array, i_len);
+				for (i = 0; i < i_len; i++)
+					g_array_index (array, gssize, i) = i;
+
+				nm_utils_array_remove_at_indexes (array, &g_array_index (idx, guint, 0), i_idx_len);
+
+				g_hash_table_remove_all (unique);
+				/* ensure that all the indexes are still unique */
+				for (i = 0; i < array->len; i++)
+					g_hash_table_add (unique, GUINT_TO_POINTER (g_array_index (array, gssize, i)));
+				g_assert_cmpint (g_hash_table_size (unique), ==, array->len);
+
+				for (i = 0; i < idx->len; i++)
+					g_hash_table_add (unique, GUINT_TO_POINTER (g_array_index (idx, guint, i)));
+				g_assert_cmpint (g_hash_table_size (unique), ==, i_len);
+
+				/* ensure proper sort order in array */
+				for (i = 0; i < array->len; i++) {
+					gssize i1 = g_array_index (array, gssize, i);
+
+					g_assert (i1 >= 0 && i1 < i_len);
+					if (i > 0) {
+						gsize i0 = g_array_index (array, gssize, i - 1);
+						g_assert_cmpint (i0, <, i1);
+					}
+				}
+			}
+		}
+	}
+}
 
 /*******************************************/
 
 static void
-test_nm_ethernet_address_is_valid ()
+test_nm_ethernet_address_is_valid (void)
 {
 	g_assert (!nm_ethernet_address_is_valid (NULL, -1));
 	g_assert (!nm_ethernet_address_is_valid (NULL, ETH_ALEN));
@@ -360,6 +459,362 @@ test_nm_ethernet_address_is_valid ()
 
 /*******************************************/
 
+typedef struct {
+	union {
+		NMMultiIndexId id_base;
+		guint bucket;
+	};
+} NMMultiIndexIdTest;
+
+typedef struct {
+	guint64 buckets;
+	gpointer ptr_value;
+} NMMultiIndexTestValue;
+
+static gboolean
+_mi_value_bucket_has (const NMMultiIndexTestValue *value, guint bucket)
+{
+	g_assert (value);
+	g_assert (bucket < 64);
+
+	return (value->buckets & (((guint64) 0x01) << bucket)) != 0;
+}
+
+static gboolean
+_mi_value_bucket_set (NMMultiIndexTestValue *value, guint bucket)
+{
+	g_assert (value);
+	g_assert (bucket < 64);
+
+	if (_mi_value_bucket_has (value, bucket))
+		return FALSE;
+
+	value->buckets |= (((guint64) 0x01) << bucket);
+	return TRUE;
+}
+
+static gboolean
+_mi_value_bucket_unset (NMMultiIndexTestValue *value, guint bucket)
+{
+	g_assert (value);
+	g_assert (bucket < 64);
+
+	if (!_mi_value_bucket_has (value, bucket))
+		return FALSE;
+
+	value->buckets &= ~(((guint64) 0x01) << bucket);
+	return TRUE;
+}
+
+static guint
+_mi_idx_hash (const NMMultiIndexIdTest *id)
+{
+	g_assert (id && id->bucket < 64);
+	return id->bucket;
+}
+
+static gboolean
+_mi_idx_equal (const NMMultiIndexIdTest *a, const NMMultiIndexIdTest *b)
+{
+	g_assert (a && a->bucket < 64);
+	g_assert (b && b->bucket < 64);
+
+	return a->bucket == b->bucket;
+}
+
+static NMMultiIndexIdTest *
+_mi_idx_clone (const NMMultiIndexIdTest *id)
+{
+	NMMultiIndexIdTest *n;
+
+	g_assert (id && id->bucket < 64);
+
+	n = g_new0 (NMMultiIndexIdTest, 1);
+	n->bucket = id->bucket;
+	return n;
+}
+
+static void
+_mi_idx_destroy (NMMultiIndexIdTest *id)
+{
+	g_assert (id && id->bucket < 64);
+	g_free (id);
+}
+
+static NMMultiIndexTestValue *
+_mi_create_array (guint num_values)
+{
+	NMMultiIndexTestValue *array = g_new0 (NMMultiIndexTestValue, num_values);
+	guint i;
+
+	g_assert (num_values > 0);
+
+	for (i = 0; i < num_values; i++) {
+		array[i].buckets = 0;
+		array[i].ptr_value = GUINT_TO_POINTER (i + 1);
+	}
+	return array;
+}
+
+typedef struct {
+	guint num_values;
+	guint num_buckets;
+	NMMultiIndexTestValue *array;
+	int test_idx;
+} NMMultiIndexAssertData;
+
+static gboolean
+_mi_assert_index_equals_array_cb (const NMMultiIndexIdTest *id, void *const* values, guint len, NMMultiIndexAssertData *data)
+{
+	guint i;
+	gboolean has_test_idx = FALSE;
+
+	g_assert (id && id->bucket < 64);
+	g_assert (data);
+	g_assert (values);
+	g_assert (len > 0);
+	g_assert (values[len] == NULL);
+	g_assert (data->test_idx >= -1 || data->test_idx < data->num_buckets);
+
+	g_assert (id->bucket < data->num_buckets);
+
+	for (i = 0; i < data->num_values; i++)
+		g_assert (!_mi_value_bucket_has (&data->array[i], id->bucket));
+
+	for (i = 0; i < len; i++) {
+		guint vi = GPOINTER_TO_UINT (values[i]);
+
+		g_assert (vi >= 1);
+		g_assert (vi <= data->num_values);
+		vi--;
+		if (data->test_idx == vi)
+			has_test_idx = TRUE;
+		g_assert (data->array[vi].ptr_value == values[i]);
+		if (!_mi_value_bucket_set (&data->array[vi], id->bucket))
+			g_assert_not_reached ();
+	}
+	g_assert ((data->test_idx == -1 && !has_test_idx) || has_test_idx);
+	return TRUE;
+}
+
+static void
+_mi_assert_index_equals_array (guint num_values, guint num_buckets, int test_idx, const NMMultiIndexTestValue *array, const NMMultiIndex *index)
+{
+	NMMultiIndexAssertData data = {
+		.num_values = num_values,
+		.num_buckets = num_buckets,
+		.test_idx = test_idx,
+	};
+	NMMultiIndexIter iter;
+	const NMMultiIndexIdTest *id;
+	void *const* values;
+	guint len;
+	NMMultiIndexTestValue *v;
+
+	data.array = _mi_create_array (num_values);
+	v = test_idx >= 0 ? data.array[test_idx].ptr_value : NULL;
+	nm_multi_index_foreach (index, v, (NMMultiIndexFuncForeach) _mi_assert_index_equals_array_cb, &data);
+	if (test_idx >= 0)
+		g_assert (memcmp (&data.array[test_idx], &array[test_idx], sizeof (NMMultiIndexTestValue)) == 0);
+	else
+		g_assert (memcmp (data.array, array, sizeof (NMMultiIndexTestValue) * num_values) == 0);
+	g_free (data.array);
+
+
+	data.array = _mi_create_array (num_values);
+	v = test_idx >= 0 ? data.array[test_idx].ptr_value : NULL;
+	nm_multi_index_iter_init (&iter, index, v);
+	while (nm_multi_index_iter_next (&iter, (gpointer) &id, &values, &len))
+		_mi_assert_index_equals_array_cb (id, values, len, &data);
+	if (test_idx >= 0)
+		g_assert (memcmp (&data.array[test_idx], &array[test_idx], sizeof (NMMultiIndexTestValue)) == 0);
+	else
+		g_assert (memcmp (data.array, array, sizeof (NMMultiIndexTestValue) * num_values) == 0);
+	g_free (data.array);
+}
+
+typedef enum {
+	MI_OP_ADD,
+	MI_OP_REMOVE,
+	MI_OP_MOVE,
+} NMMultiIndexOperation;
+
+static void
+_mi_rebucket (GRand *rand, guint num_values, guint num_buckets, NMMultiIndexOperation op, guint bucket, guint bucket_old, guint array_idx, NMMultiIndexTestValue *array, NMMultiIndex *index)
+{
+	NMMultiIndexTestValue *v;
+	NMMultiIndexIdTest id, id_old;
+	const NMMultiIndexIdTest *id_reverse;
+	guint64 buckets_old;
+	guint i;
+	gboolean had_bucket, had_bucket_old;
+
+	g_assert (array_idx < num_values);
+	g_assert (bucket < (int) num_buckets);
+
+	v = &array[array_idx];
+
+	buckets_old = v->buckets;
+	if (op == MI_OP_MOVE)
+		had_bucket_old = _mi_value_bucket_has (v, bucket_old);
+	else
+		had_bucket_old = FALSE;
+	had_bucket = _mi_value_bucket_has (v, bucket);
+
+	switch (op) {
+
+	case MI_OP_ADD:
+		_mi_value_bucket_set (v, bucket);
+		id.bucket = bucket;
+		if (nm_multi_index_add (index, &id.id_base, v->ptr_value))
+			g_assert (!had_bucket);
+		else
+			g_assert (had_bucket);
+		break;
+
+	case MI_OP_REMOVE:
+		_mi_value_bucket_unset (v, bucket);
+		id.bucket = bucket;
+		if (nm_multi_index_remove (index, &id.id_base, v->ptr_value))
+			g_assert (had_bucket);
+		else
+			g_assert (!had_bucket);
+		break;
+
+	case MI_OP_MOVE:
+
+		_mi_value_bucket_unset (v, bucket_old);
+		_mi_value_bucket_set (v, bucket);
+
+		id.bucket = bucket;
+		id_old.bucket = bucket_old;
+
+		if (nm_multi_index_move (index, &id_old.id_base, &id.id_base, v->ptr_value)) {
+			if (bucket == bucket_old)
+				g_assert (had_bucket_old && had_bucket);
+			else
+				g_assert (had_bucket_old && !had_bucket);
+		} else {
+			if (bucket == bucket_old)
+				g_assert (!had_bucket_old && !had_bucket);
+			else
+				g_assert (!had_bucket_old || had_bucket);
+		}
+		break;
+
+	default:
+		g_assert_not_reached ();
+	}
+
+#if 0
+	g_print (">>> rebucket: idx=%3u, op=%3s, bucket=%3i%c -> %3i%c, buckets=%08llx -> %08llx %s\n", array_idx,
+	         op == MI_OP_ADD ? "ADD" : (op == MI_OP_REMOVE ? "REM" : "MOV"),
+	         bucket_old, had_bucket_old ? '*' : ' ',
+	         bucket, had_bucket ? '*' : ' ',
+	         (long long unsigned) buckets_old, (long long unsigned) v->buckets,
+	         buckets_old != v->buckets ? "(changed)" : "(unchanged)");
+#endif
+
+	id_reverse = (const NMMultiIndexIdTest *) nm_multi_index_lookup_first_by_value (index, v->ptr_value);
+	if (id_reverse)
+		g_assert (_mi_value_bucket_has (v, id_reverse->bucket));
+	else
+		g_assert (v->buckets == 0);
+
+	for (i = 0; i < 64; i++) {
+		id.bucket = i;
+		if (nm_multi_index_contains (index, &id.id_base, v->ptr_value))
+			g_assert (_mi_value_bucket_has (v, i));
+		else
+			g_assert (!_mi_value_bucket_has (v, i));
+	}
+
+	_mi_assert_index_equals_array (num_values, num_buckets, -1, array, index);
+	_mi_assert_index_equals_array (num_values, num_buckets, array_idx, array, index);
+	_mi_assert_index_equals_array (num_values, num_buckets, g_rand_int_range (rand, 0, num_values), array, index);
+}
+
+static void
+_mi_test_run (guint num_values, guint num_buckets)
+{
+	NMMultiIndex *index = nm_multi_index_new ((NMMultiIndexFuncHash) _mi_idx_hash,
+	                                          (NMMultiIndexFuncEqual) _mi_idx_equal,
+	                                          (NMMultiIndexFuncClone) _mi_idx_clone,
+	                                          (NMMultiIndexFuncDestroy) _mi_idx_destroy);
+	gs_free NMMultiIndexTestValue *array = _mi_create_array (num_values);
+	GRand *rand = nmtst_get_rand ();
+	guint i, i_rd, i_idx, i_bucket;
+	guint num_buckets_all = num_values * num_buckets;
+
+	g_assert (array[0].ptr_value == GUINT_TO_POINTER (1));
+
+	_mi_assert_index_equals_array (num_values, num_buckets, -1, array, index);
+
+	_mi_rebucket (rand, num_values, num_buckets, MI_OP_ADD, 0, 0, 0, array, index);
+	_mi_rebucket (rand, num_values, num_buckets, MI_OP_REMOVE, 0, 0, 0, array, index);
+
+	if (num_buckets >= 3) {
+		_mi_rebucket (rand, num_values, num_buckets, MI_OP_ADD, 0, 0, 0, array, index);
+		_mi_rebucket (rand, num_values, num_buckets, MI_OP_MOVE, 2, 0, 0, array, index);
+		_mi_rebucket (rand, num_values, num_buckets, MI_OP_REMOVE, 2, 0, 0, array, index);
+	}
+
+	g_assert (nm_multi_index_get_num_groups (index) == 0);
+
+	/* randomly change the bucket of entries. */
+	for (i = 0; i < 5 * num_values; i++) {
+		guint array_idx = g_rand_int_range (rand, 0, num_values);
+		guint bucket = g_rand_int_range (rand, 0, num_buckets);
+		NMMultiIndexOperation op = g_rand_int_range (rand, 0, MI_OP_MOVE + 1);
+		guint bucket_old = 0;
+
+		if (op == MI_OP_MOVE) {
+			if ((g_rand_int (rand) % 2) && array[array_idx].buckets != 0) {
+				guint64 b;
+
+				/* choose the highest (existing) bucket. */
+				bucket_old = 0;
+				for (b = array[array_idx].buckets; b; b >>= 1)
+					bucket_old++;
+			} else {
+				/* choose a random bucket (even if the item is currently not in that bucket). */
+				bucket_old = g_rand_int_range (rand, 0, num_buckets);
+			}
+		}
+
+		_mi_rebucket (rand, num_values, num_buckets, op, bucket, bucket_old, array_idx, array, index);
+	}
+
+	/* remove all elements from all buckets */
+	i_rd = g_rand_int (rand);
+	for (i = 0; i < num_buckets_all; i++) {
+		i_rd = (i_rd + 101) % num_buckets_all;
+		i_idx = i_rd / num_buckets;
+		i_bucket = i_rd % num_buckets;
+
+		if (_mi_value_bucket_has (&array[i_idx], i_bucket))
+			_mi_rebucket (rand, num_values, num_buckets, MI_OP_REMOVE, i_bucket, 0, i_idx, array, index);
+	}
+
+	g_assert (nm_multi_index_get_num_groups (index) == 0);
+	nm_multi_index_free (index);
+}
+
+static void
+test_nm_multi_index (void)
+{
+	guint i, j;
+
+	for (i = 1; i < 7; i++) {
+		for (j = 1; j < 6; j++)
+			_mi_test_run (i, j);
+	}
+	_mi_test_run (50, 3);
+	_mi_test_run (50, 18);
+}
+
+/*******************************************/
+
 NMTST_DEFINE ();
 
 int
@@ -369,6 +824,8 @@ main (int argc, char **argv)
 
 	g_test_add_func ("/general/nm_utils_kill_child", test_nm_utils_kill_child);
 	g_test_add_func ("/general/nm_ethernet_address_is_valid", test_nm_ethernet_address_is_valid);
+	g_test_add_func ("/general/nm_multi_index", test_nm_multi_index);
+	g_test_add_func ("/general/nm_utils_array_remove_at_indexes", test_nm_utils_array_remove_at_indexes);
 
 	return g_test_run ();
 }
diff --git a/src/tests/test-general.c b/src/tests/test-general.c
index f118156b..de65d50f 100644
--- a/src/tests/test-general.c
+++ b/src/tests/test-general.c
@@ -30,135 +30,6 @@
 
 #include "nm-test-utils.h"
 
-static void
-test_nm_utils_ascii_str_to_int64_check (const char *str, guint base, gint64 min,
-                                        gint64 max, gint64 fallback, int exp_errno,
-                                        gint64 exp_val)
-{
-	gint64 v;
-
-	errno = 1;
-	v = nm_utils_ascii_str_to_int64 (str, base, min, max, fallback);
-	g_assert_cmpint (errno, ==, exp_errno);
-	g_assert_cmpint (v, ==, exp_val);
-}
-
-static void
-test_nm_utils_ascii_str_to_int64_do (const char *str, guint base, gint64 min,
-                                     gint64 max, gint64 fallback, int exp_errno,
-                                     gint64 exp_val)
-{
-	const char *sign = "";
-	const char *val;
-	static const char *whitespaces[] = {
-		"",
-		" ",
-		"\r\n\t",
-		" \r\n\t ",
-		" \r\n\t \t\r\n\t",
-		NULL,
-	};
-	static const char *nulls[] = {
-		"",
-		"0",
-		"00",
-		"0000",
-		"0000000000000000",
-		"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
-		"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
-		NULL,
-	};
-	const char **ws_pre, **ws_post, **null;
-	guint i;
-
-	if (str == NULL || exp_errno != 0) {
-		test_nm_utils_ascii_str_to_int64_check (str, base, min, max, fallback, exp_errno, exp_val);
-		return;
-	}
-
-	if (strncmp (str, "-", 1) == 0)
-		sign = "-";
-
-	val = str + strlen (sign);
-
-	for (ws_pre = whitespaces; *ws_pre; ws_pre++) {
-		for (ws_post = whitespaces; *ws_post; ws_post++) {
-			for (null = nulls; *null; null++) {
-				for (i = 0; ; i++) {
-					char *s;
-					const char *str_base = "";
-
-					if (base == 16) {
-						if (i == 1)
-							str_base = "0x";
-						else if (i > 1)
-							break;
-					} else if (base == 8) {
-						if (i == 1)
-							str_base = "0";
-						else if (i > 1)
-							break;
-					} else if (base == 0) {
-						if (i > 0)
-							break;
-						/* with base==0, a leading zero would be interpreted as octal. Only test without *null */
-						if ((*null)[0])
-							break;
-					} else {
-						if (i > 0)
-							break;
-					}
-
-					s = g_strdup_printf ("%s%s%s%s%s%s", *ws_pre, sign, str_base, *null, val, *ws_post);
-
-					test_nm_utils_ascii_str_to_int64_check (s, base, min, max, fallback, exp_errno, exp_val);
-					g_free (s);
-				}
-			}
-		}
-	}
-}
-
-static void
-test_nm_utils_ascii_str_to_int64 (void)
-{
-	test_nm_utils_ascii_str_to_int64_do (NULL, 10, 0, 10000, -1, EINVAL, -1);
-	test_nm_utils_ascii_str_to_int64_do ("", 10, 0, 10000, -1, EINVAL, -1);
-	test_nm_utils_ascii_str_to_int64_do ("1x", 10, 0, 10000, -1, EINVAL, -1);
-	test_nm_utils_ascii_str_to_int64_do ("4711", 10, 0, 10000, -1, 0, 4711);
-	test_nm_utils_ascii_str_to_int64_do ("10000", 10, 0, 10000, -1, 0, 10000);
-	test_nm_utils_ascii_str_to_int64_do ("10001", 10, 0, 10000, -1, ERANGE, -1);
-	test_nm_utils_ascii_str_to_int64_do ("FF", 16, 0, 10000, -1, 0, 255);
-	test_nm_utils_ascii_str_to_int64_do ("FF", 10, 0, 10000, -2, EINVAL, -2);
-	test_nm_utils_ascii_str_to_int64_do ("9223372036854775807", 10, 0, G_MAXINT64, -2, 0, G_MAXINT64);
-	test_nm_utils_ascii_str_to_int64_do ("7FFFFFFFFFFFFFFF", 16, 0, G_MAXINT64, -2, 0, G_MAXINT64);
-	test_nm_utils_ascii_str_to_int64_do ("9223372036854775808", 10, 0, G_MAXINT64, -2, ERANGE, -2);
-	test_nm_utils_ascii_str_to_int64_do ("-9223372036854775808", 10, G_MININT64, 0, -2, 0, G_MININT64);
-	test_nm_utils_ascii_str_to_int64_do ("-9223372036854775808", 10, G_MININT64+1, 0, -2, ERANGE, -2);
-	test_nm_utils_ascii_str_to_int64_do ("-9223372036854775809", 10, G_MININT64, 0, -2, ERANGE, -2);
-	test_nm_utils_ascii_str_to_int64_do ("1.0", 10, 1, 1, -1, EINVAL, -1);
-	test_nm_utils_ascii_str_to_int64_do ("1x0", 16, -10, 10, -100, EINVAL, -100);
-	test_nm_utils_ascii_str_to_int64_do ("0", 16, -10, 10, -100, 0, 0);
-	test_nm_utils_ascii_str_to_int64_do ("10001111", 2, -1000, 1000, -100000, 0, 0x8F);
-	test_nm_utils_ascii_str_to_int64_do ("-10001111", 2, -1000, 1000, -100000, 0, -0x8F);
-	test_nm_utils_ascii_str_to_int64_do ("1111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7F);
-	test_nm_utils_ascii_str_to_int64_do ("111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFF);
-	test_nm_utils_ascii_str_to_int64_do ("11111111111111111111111111111111111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFF);
-	test_nm_utils_ascii_str_to_int64_do ("111111111111111111111111111111111111111111111111111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFFFFFF);
-	test_nm_utils_ascii_str_to_int64_do ("100000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, 0,  0x4000000000000000);
-	test_nm_utils_ascii_str_to_int64_do ("1000000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, ERANGE, -1);
-	test_nm_utils_ascii_str_to_int64_do ("-100000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, 0,  -0x4000000000000000);
-	test_nm_utils_ascii_str_to_int64_do ("111111111111111111111111111111111111111111111111111111111111111",  2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFFFFFF);
-	test_nm_utils_ascii_str_to_int64_do ("-100000000000000000000000000000000000000000000000000000000000000",  2, G_MININT64, G_MAXINT64, -1, 0,  -0x4000000000000000);
-	test_nm_utils_ascii_str_to_int64_do ("0x70",  10, G_MININT64, G_MAXINT64, -1, EINVAL, -1);
-	test_nm_utils_ascii_str_to_int64_do ("4711",  0, G_MININT64, G_MAXINT64, -1, 0, 4711);
-	test_nm_utils_ascii_str_to_int64_do ("04711",  0, G_MININT64, G_MAXINT64, -1, 0, 04711);
-	test_nm_utils_ascii_str_to_int64_do ("0x4711",  0, G_MININT64, G_MAXINT64, -1, 0, 0x4711);
-	test_nm_utils_ascii_str_to_int64_do ("080",  0, G_MININT64, G_MAXINT64, -1, EINVAL, -1);
-	test_nm_utils_ascii_str_to_int64_do ("070",  0, G_MININT64, G_MAXINT64, -1, 0, 7*8);
-	test_nm_utils_ascii_str_to_int64_do ("0x70",  0, G_MININT64, G_MAXINT64, -1, 0, 0x70);
-}
-
 /* Reference implementation for nm_utils_ip6_address_clear_host_address.
  * Taken originally from set_address_masked(), src/rdisc/nm-lndp-rdisc.c
  **/
@@ -727,49 +598,134 @@ test_connection_sort_autoconnect_priority (void)
 
 /*******************************************/
 
-static void
-__test_uuid (const char *expected_uuid, const char *str, gssize slen, char *uuid_test)
+static const char *_test_match_spec_all[] = {
+	"e",
+	"em",
+	"em*",
+	"em\\",
+	"em\\*",
+	"em\\1",
+	"em\\11",
+	"em\\2",
+	"em1",
+	"em11",
+	"em2",
+	"=em*",
+	NULL
+};
+
+static gboolean
+_test_match_spec_contains (const char **matches, const char *match)
 {
-	g_assert (uuid_test);
-	g_assert (nm_utils_is_uuid (uuid_test));
+	guint i;
 
-	if (strcmp (uuid_test, expected_uuid)) {
-		g_error ("UUID test failed (1): text=%s, len=%lld, expected=%s, uuid_test=%s",
-		         str, (long long) slen, expected_uuid, uuid_test);
+	for (i = 0; matches && matches[i]; i++) {
+		if (strcmp (match, matches[i]) == 0)
+			return TRUE;
 	}
-	g_free (uuid_test);
+	return FALSE;
+}
+
+static void
+test_match_spec_ifname (const char *spec_str, const char **matches, const char **neg_matches)
+{
+	const char *m;
+	GSList *specs, *specs_reverse = NULL, *specs_resplit, *specs_i, *specs_j;
+	guint i;
+	gs_free char *specs_joined = NULL;
 
-	uuid_test = nm_utils_uuid_generate_from_string (str, slen, NM_UTILS_UUID_TYPE_VARIANT3, NM_UTILS_UUID_NS);
+	g_assert (spec_str);
 
-	g_assert (uuid_test);
-	g_assert (nm_utils_is_uuid (uuid_test));
+	specs = nm_match_spec_split (spec_str);
 
-	if (strcmp (uuid_test, expected_uuid)) {
-		g_error ("UUID test failed (2): text=%s; len=%lld, expected=%s, uuid2=%s",
-		         str, (long long) slen, expected_uuid, uuid_test);
+	/* assert that split(join(specs)) == specs */
+	specs_joined = nm_match_spec_join (specs);
+	specs_resplit = nm_match_spec_split (specs_joined);
+	specs_i = specs;
+	specs_j = specs_resplit;
+	while (specs_i && specs_j && g_strcmp0 (specs_i->data, specs_j->data) == 0) {
+		specs_i = specs_i->next;
+		specs_j = specs_j->next;
 	}
-	g_free (uuid_test);
-}
+	g_assert (!specs_i);
+	g_assert (!specs_j);
+	g_slist_free_full (specs_resplit, g_free);
+
+	/* also check the matches in the reverse order. They must yield the same result because
+	 * matches are inclusive -- except "except:" which always wins. */
+	specs_reverse = g_slist_reverse (g_slist_copy (specs));
 
-#define _test_uuid(expected_uuid, str, strlen, ...) __test_uuid (expected_uuid, str, strlen, nm_utils_uuid_generate_from_strings(__VA_ARGS__, NULL))
+	for (i = 0; matches && matches[i]; i++) {
+		g_assert (nm_match_spec_interface_name (specs, matches[i]) == NM_MATCH_SPEC_MATCH);
+		g_assert (nm_match_spec_interface_name (specs_reverse, matches[i]) == NM_MATCH_SPEC_MATCH);
+	}
+	for (i = 0; neg_matches && neg_matches[i]; i++) {
+		g_assert (nm_match_spec_interface_name (specs, neg_matches[i]) == NM_MATCH_SPEC_NEG_MATCH);
+		g_assert (nm_match_spec_interface_name (specs_reverse, neg_matches[i]) == NM_MATCH_SPEC_NEG_MATCH);
+	}
+	for (i = 0; (m = _test_match_spec_all[i]); i++) {
+		if (_test_match_spec_contains (matches, m))
+			continue;
+		if (_test_match_spec_contains (neg_matches, m))
+			continue;
+		g_assert (nm_match_spec_interface_name (specs, m) == NM_MATCH_SPEC_NO_MATCH);
+		g_assert (nm_match_spec_interface_name (specs_reverse, m) == NM_MATCH_SPEC_NO_MATCH);
+	}
+
+	g_slist_free (specs_reverse);
+	g_slist_free_full (specs, g_free);
+}
 
 static void
-test_nm_utils_uuid_generate_from_strings (void)
+test_nm_match_spec_interface_name (void)
 {
-	_test_uuid ("b07c334a-399b-32de-8d50-58e4e08f98e3", "",         0, NULL);
-	_test_uuid ("b8a426cb-bcb5-30a3-bd8f-6786fea72df9", "\0",       1, "");
-	_test_uuid ("12a4a982-7aae-39e1-951e-41aeb1250959", "a\0",      2, "a");
-	_test_uuid ("69e22c7e-f89f-3a43-b239-1cb52ed8db69", "aa\0",     3, "aa");
-	_test_uuid ("59829fd3-5ad5-3d90-a7b0-4911747e4088", "\0\0",     2, "",   "");
-	_test_uuid ("01ad0e06-6c50-3384-8d86-ddab81421425", "a\0\0",    3, "a",  "");
-	_test_uuid ("e1ed8647-9ed3-3ec8-8c6d-e8204524d71d", "aa\0\0",   4, "aa", "");
-	_test_uuid ("fb1c7cd6-275c-3489-9382-83b900da8af0", "\0a\0",    3, "",   "a");
-	_test_uuid ("5d79494e-c4ba-31a6-80a2-d6016ccd7e17", "a\0a\0",   4, "a",  "a");
-	_test_uuid ("fd698d86-1b60-3ebe-855f-7aada9950a8d", "aa\0a\0",  5, "aa", "a");
-	_test_uuid ("8c573b48-0f01-30ba-bb94-c5f59f4fe517", "\0aa\0",   4, "",   "aa");
-	_test_uuid ("2bdd3d46-eb83-3c53-a41b-a724d04b5544", "a\0aa\0",  5, "a",  "aa");
-	_test_uuid ("13d4b780-07c1-3ba7-b449-81c4844ef039", "aa\0aa\0", 6, "aa", "aa");
-	_test_uuid ("dd265bf7-c05a-3037-9939-b9629858a477", "a\0b\0",   4, "a",  "b");
+#define S(...) ((const char *[]) { __VA_ARGS__, NULL } )
+	test_match_spec_ifname ("em1",
+	                        S ("em1"),
+	                        NULL);
+	test_match_spec_ifname ("em1,em2",
+	                        S ("em1", "em2"),
+	                        NULL);
+	test_match_spec_ifname ("em1,em2,interface-name:em2",
+	                        S ("em1", "em2"),
+	                        NULL);
+	test_match_spec_ifname ("interface-name:em1",
+	                        S ("em1"),
+	                        NULL);
+	test_match_spec_ifname ("interface-name:em*",
+	                        S ("em", "em*", "em\\", "em\\*", "em\\1", "em\\11", "em\\2", "em1", "em11", "em2", "em3"),
+	                        NULL);
+	test_match_spec_ifname ("interface-name:em\\*",
+	                        S ("em\\", "em\\*", "em\\1", "em\\11", "em\\2"),
+	                        NULL);
+	test_match_spec_ifname ("interface-name:~em\\*",
+	                        S ("em\\", "em\\*", "em\\1", "em\\11", "em\\2"),
+	                        NULL);
+	test_match_spec_ifname ("interface-name:=em*",
+	                        S ("em*"),
+	                        NULL);
+	test_match_spec_ifname ("interface-name:em*,except:interface-name:em1*",
+	                        S ("em", "em*", "em\\", "em\\*", "em\\1", "em\\11", "em\\2", "em2", "em3"),
+	                        S ("em1", "em11"));
+	test_match_spec_ifname ("interface-name:em*,except:interface-name:=em*",
+	                        S ("em", "em\\", "em\\*", "em\\1", "em\\11", "em\\2", "em1", "em11", "em2", "em3"),
+	                        S ("em*"));
+	test_match_spec_ifname ("aa,bb,cc\\,dd,e,,",
+	                        S ("aa", "bb", "cc,dd", "e"),
+	                        NULL);
+	test_match_spec_ifname ("aa;bb;cc\\;dd;e,;",
+	                        S ("aa", "bb", "cc;dd", "e"),
+	                        NULL);
+	test_match_spec_ifname ("interface-name:em\\;1,em\\,2,\\,,\\\\,,em\\\\x",
+	                        S ("em;1", "em,2", ",", "\\", "em\\x"),
+	                        NULL);
+	test_match_spec_ifname ("\\s\\s,\\sinterface-name:a,\\s,",
+	                        S ("  ", " ", " interface-name:a"),
+	                        NULL);
+	test_match_spec_ifname (" aa ;  bb   ; cc\\;dd  ;e , ; \t\\t  , ",
+	                        S ("aa", "bb", "cc;dd", "e", "\t"),
+	                        NULL);
+#undef S
 }
 
 /*******************************************/
@@ -781,7 +737,6 @@ main (int argc, char **argv)
 {
 	nmtst_init_with_logging (&argc, &argv, NULL, "ALL");
 
-	g_test_add_func ("/general/nm_utils_ascii_str_to_int64", test_nm_utils_ascii_str_to_int64);
 	g_test_add_func ("/general/nm_utils_ip6_address_clear_host_address", test_nm_utils_ip6_address_clear_host_address);
 	g_test_add_func ("/general/nm_utils_log_connection_diff", test_nm_utils_log_connection_diff);
 
@@ -796,7 +751,7 @@ main (int argc, char **argv)
 
 	g_test_add_func ("/general/connection-sort/autoconnect-priority", test_connection_sort_autoconnect_priority);
 
-	g_test_add_func ("/general/nm_utils_uuid_generate_from_strings", test_nm_utils_uuid_generate_from_strings);
+	g_test_add_func ("/general/nm_match_spec_interface_name", test_nm_match_spec_interface_name);
 
 	return g_test_run ();
 }
diff --git a/src/tests/test-ip4-config.c b/src/tests/test-ip4-config.c
index 657f7e05..c8bdc75f 100644
--- a/src/tests/test-ip4-config.c
+++ b/src/tests/test-ip4-config.c
@@ -322,11 +322,11 @@ test_merge_subtract_mss_mtu (void)
 	g_assert_cmpuint (nm_ip4_config_get_mtu (cfg1), ==, expected_mtu2);
 
 	nm_ip4_config_merge (cfg1, cfg3);
-	/* ensure again the same MSS and MTU are in cfg1 */
-	g_assert_cmpuint (nm_ip4_config_get_mss (cfg1), ==, expected_mss2);
-	g_assert_cmpuint (nm_ip4_config_get_mtu (cfg1), ==, expected_mtu2);
+	/* ensure again the MSS and MTU in cfg1 got overriden */
+	g_assert_cmpuint (nm_ip4_config_get_mss (cfg1), ==, expected_mss3);
+	g_assert_cmpuint (nm_ip4_config_get_mtu (cfg1), ==, expected_mtu3);
 
-	nm_ip4_config_subtract (cfg1, cfg2);
+	nm_ip4_config_subtract (cfg1, cfg3);
 	/* ensure MSS and MTU are zero in cfg1 */
 	g_assert_cmpuint (nm_ip4_config_get_mss (cfg1), ==, 0);
 	g_assert_cmpuint (nm_ip4_config_get_mtu (cfg1), ==, 0);
diff --git a/src/tests/test-route-manager.c b/src/tests/test-route-manager.c
new file mode 100644
index 00000000..0164930d
--- /dev/null
+++ b/src/tests/test-route-manager.c
@@ -0,0 +1,917 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <arpa/inet.h>
+#include <linux/rtnetlink.h>
+
+#include "test-common.h"
+
+#include "nm-platform.h"
+#include "nm-route-manager.h"
+#include "nm-logging.h"
+
+#include "nm-test-utils.h"
+
+typedef struct {
+	int ifindex0, ifindex1;
+} test_fixture;
+
+/*****************************************************************************/
+
+static void
+setup_dev0_ip4 (int ifindex, guint mss_of_first_route, guint32 metric_of_second_route)
+{
+	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
+	NMPlatformIP4Route route = { 0 };
+
+	route.ifindex = ifindex;
+	route.mss = 0;
+
+	route.source = NM_IP_CONFIG_SOURCE_USER;
+	inet_pton (AF_INET, "6.6.6.0", &route.network);
+	route.plen = 24;
+	route.gateway = INADDR_ANY;
+	route.metric = 20;
+	route.mss = mss_of_first_route;
+	g_array_append_val (routes, route);
+
+	route.source = NM_IP_CONFIG_SOURCE_USER;
+	inet_pton (AF_INET, "7.0.0.0", &route.network);
+	route.plen = 8;
+	inet_pton (AF_INET, "6.6.6.1", &route.gateway);
+	route.metric = metric_of_second_route;
+	route.mss = 0;
+	g_array_append_val (routes, route);
+
+	nm_route_manager_ip4_route_sync (nm_route_manager_get (), ifindex, routes, TRUE, TRUE);
+	g_array_free (routes, TRUE);
+}
+
+static void
+setup_dev1_ip4 (int ifindex)
+{
+	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
+	NMPlatformIP4Route route = { 0 };
+
+	route.ifindex = ifindex;
+	route.mss = 0;
+
+	/* Add some route outside of route manager. The route manager
+	 * should get rid of it upon sync. */
+	nm_platform_ip4_route_add (NM_PLATFORM_GET,
+	                           route.ifindex,
+	                           NM_IP_CONFIG_SOURCE_USER,
+	                           nmtst_inet4_from_string ("9.0.0.0"),
+	                           8,
+	                           INADDR_ANY,
+	                           0,
+	                           10,
+	                           route.mss);
+
+	route.source = NM_IP_CONFIG_SOURCE_USER;
+	inet_pton (AF_INET, "6.6.6.0", &route.network);
+	route.plen = 24;
+	route.gateway = INADDR_ANY;
+	route.metric = 20;
+	g_array_append_val (routes, route);
+
+	route.source = NM_IP_CONFIG_SOURCE_USER;
+	inet_pton (AF_INET, "7.0.0.0", &route.network);
+	route.plen = 8;
+	route.gateway = INADDR_ANY;
+	route.metric = 22;
+	g_array_append_val (routes, route);
+
+	route.source = NM_IP_CONFIG_SOURCE_USER;
+	inet_pton (AF_INET, "8.0.0.0", &route.network);
+	route.plen = 8;
+	inet_pton (AF_INET, "6.6.6.2", &route.gateway);
+	route.metric = 22;
+	g_array_append_val (routes, route);
+
+	nm_route_manager_ip4_route_sync (nm_route_manager_get (), ifindex, routes, TRUE, TRUE);
+	g_array_free (routes, TRUE);
+}
+
+static void
+update_dev0_ip4 (int ifindex)
+{
+	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
+	NMPlatformIP4Route route = { 0 };
+
+	route.ifindex = ifindex;
+	route.mss = 0;
+
+	route.source = NM_IP_CONFIG_SOURCE_USER;
+	inet_pton (AF_INET, "6.6.6.0", &route.network);
+	route.plen = 24;
+	route.gateway = INADDR_ANY;
+	route.metric = 20;
+	g_array_append_val (routes, route);
+
+	route.source = NM_IP_CONFIG_SOURCE_USER;
+	inet_pton (AF_INET, "7.0.0.0", &route.network);
+	route.plen = 8;
+	route.gateway = INADDR_ANY;
+	route.metric = 21;
+	g_array_append_val (routes, route);
+
+	nm_route_manager_ip4_route_sync (nm_route_manager_get (), ifindex, routes, TRUE, TRUE);
+	g_array_free (routes, TRUE);
+}
+
+
+static GArray *
+ip4_routes (test_fixture *fixture)
+{
+	GArray *routes = nm_platform_ip4_route_get_all (NM_PLATFORM_GET,
+	                                                fixture->ifindex0,
+	                                                NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
+	GArray *routes1 = nm_platform_ip4_route_get_all (NM_PLATFORM_GET,
+	                                                 fixture->ifindex1,
+	                                                 NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
+
+	g_array_append_vals (routes, routes1->data, routes1->len);
+	g_array_free (routes1, TRUE);
+
+	return routes;
+}
+
+static void
+test_ip4 (test_fixture *fixture, gconstpointer user_data)
+{
+	GArray *routes;
+
+	NMPlatformIP4Route state1[] = {
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("6.6.6.0"),
+			.plen = 24,
+			.ifindex = fixture->ifindex0,
+			.gateway = INADDR_ANY,
+			.metric = 20,
+			.mss = 1000,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("7.0.0.0"),
+			.plen = 8,
+			.ifindex = fixture->ifindex0,
+			.gateway = nmtst_inet4_from_string ("6.6.6.1"),
+			.metric = 21021,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_UNIVERSE),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("7.0.0.0"),
+			.plen = 8,
+			.ifindex = fixture->ifindex1,
+			.gateway = INADDR_ANY,
+			.metric = 22,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("6.6.6.0"),
+			.plen = 24,
+			.ifindex = fixture->ifindex1,
+			.gateway = INADDR_ANY,
+			.metric = 21,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("8.0.0.0"),
+			.plen = 8,
+			.ifindex = fixture->ifindex1,
+			.gateway = nmtst_inet4_from_string ("6.6.6.2"),
+			.metric = 22,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_UNIVERSE),
+		},
+	};
+
+	NMPlatformIP4Route state2[] = {
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("6.6.6.0"),
+			.plen = 24,
+			.ifindex = fixture->ifindex0,
+			.gateway = INADDR_ANY,
+			.metric = 20,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("7.0.0.0"),
+			.plen = 8,
+			.ifindex = fixture->ifindex0,
+			.gateway = INADDR_ANY,
+			.metric = 21,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("7.0.0.0"),
+			.plen = 8,
+			.ifindex = fixture->ifindex1,
+			.gateway = INADDR_ANY,
+			.metric = 22,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("6.6.6.0"),
+			.plen = 24,
+			.ifindex = fixture->ifindex1,
+			.gateway = INADDR_ANY,
+			.metric = 21,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("8.0.0.0"),
+			.plen = 8,
+			.ifindex = fixture->ifindex1,
+			.gateway = nmtst_inet4_from_string ("6.6.6.2"),
+			.metric = 22,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_UNIVERSE),
+		},
+	};
+
+	NMPlatformIP4Route state3[] = {
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("7.0.0.0"),
+			.plen = 8,
+			.ifindex = fixture->ifindex1,
+			.gateway = INADDR_ANY,
+			.metric = 22,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("6.6.6.0"),
+			.plen = 24,
+			.ifindex = fixture->ifindex1,
+			.gateway = INADDR_ANY,
+			.metric = 20,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("8.0.0.0"),
+			.plen = 8,
+			.ifindex = fixture->ifindex1,
+			.gateway = nmtst_inet4_from_string ("6.6.6.2"),
+			.metric = 22,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_UNIVERSE),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("6.6.6.0"),
+			.plen = 24,
+			.ifindex = fixture->ifindex1,
+			.gateway = INADDR_ANY,
+			/* this is a ghost entry because we synced ifindex0 and restore the route
+			 * with metric 20 (above). But we don't remove the metric 21. */
+			.metric = 21,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+	};
+
+	setup_dev0_ip4 (fixture->ifindex0, 1000, 21021);
+	setup_dev1_ip4 (fixture->ifindex1);
+	g_test_assert_expected_messages ();
+
+	/* - 6.6.6.0/24 on dev0 won over 6.6.6.0/24 on dev1
+	 * - 6.6.6.0/24 on dev1 has metric bumped.
+	 * - 7.0.0.0/8 route, metric 21021 added
+	 * - 7.0.0.0/8 route, metric 22 added
+	 * - 8.0.0.0/8 could be added. */
+	routes = ip4_routes (fixture);
+	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state1));
+	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, state1, routes->len, TRUE);
+	g_array_free (routes, TRUE);
+
+	setup_dev1_ip4 (fixture->ifindex1);
+	g_test_assert_expected_messages ();
+
+	setup_dev0_ip4 (fixture->ifindex0, 0, 21);
+
+	/* Ensure nothing changed. */
+	routes = ip4_routes (fixture);
+	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state1));
+	state1[0].mss = 0;
+	state1[1].metric = 21;
+	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, state1, routes->len, TRUE);
+	g_array_free (routes, TRUE);
+
+	update_dev0_ip4 (fixture->ifindex0);
+
+	/* minor changes in the routes. Quite similar to state1. */
+	routes = ip4_routes (fixture);
+	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state2));
+	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, state2, routes->len, TRUE);
+	g_array_free (routes, TRUE);
+
+	nm_route_manager_route_flush (nm_route_manager_get (), fixture->ifindex0);
+
+	/* 6.6.6.0/24 is now on dev1
+	 * 6.6.6.0/24 is also still on dev1 with bumped metric 21.
+	 * 7.0.0.0/8 gone from dev0, still present on dev1
+	 * 8.0.0.0/8 is present on dev1
+	 * No dev0 routes left. */
+	routes = ip4_routes (fixture);
+	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state3));
+	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, state3, routes->len, TRUE);
+	g_array_free (routes, TRUE);
+
+	nm_route_manager_route_flush (nm_route_manager_get (), fixture->ifindex1);
+
+	/* No routes left. */
+	routes = ip4_routes (fixture);
+	g_assert_cmpint (routes->len, ==, 0);
+	g_array_free (routes, TRUE);
+}
+
+static void
+setup_dev0_ip6 (int ifindex)
+{
+	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP6Route));
+	NMPlatformIP6Route *route;
+
+	/* Add an address so that a route to the gateway below gets added. */
+	nm_platform_ip6_address_add (NM_PLATFORM_GET,
+	                             ifindex,
+	                             *nmtst_inet6_from_string ("2001:db8:8086::2"),
+	                             in6addr_any,
+	                             64,
+	                             3600,
+	                             3600,
+	                             0);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:8086::",
+	                                       48,
+	                                       NULL,
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       20,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:1337::",
+	                                       48,
+	                                       NULL,
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       0,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:abad:c0de::",
+	                                       64,
+	                                       "2001:db8:8086::1",
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       21,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	nm_route_manager_ip6_route_sync (nm_route_manager_get (), ifindex, routes, TRUE, TRUE);
+	g_array_free (routes, TRUE);
+}
+
+static void
+setup_dev1_ip6 (int ifindex)
+{
+	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP6Route));
+	NMPlatformIP6Route *route;
+
+	/* Add some route outside of route manager. The route manager
+	 * should get rid of it upon sync. */
+	nm_platform_ip6_route_add (NM_PLATFORM_GET,
+	                           ifindex,
+	                           NM_IP_CONFIG_SOURCE_USER,
+	                           *nmtst_inet6_from_string ("2001:db8:8088::"),
+	                           48,
+	                           in6addr_any,
+	                           10,
+	                           0);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:8086::",
+	                                       48,
+	                                       NULL,
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       20,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:1337::",
+	                                       48,
+	                                       NULL,
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       1024,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:d34d::",
+	                                       64,
+	                                       "2001:db8:8086::2",
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       20,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:abad:c0de::",
+	                                       64,
+	                                       NULL,
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       22,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	nm_route_manager_ip6_route_sync (nm_route_manager_get (), ifindex, routes, TRUE, TRUE);
+	g_array_free (routes, TRUE);
+}
+
+static void
+update_dev0_ip6 (int ifindex)
+{
+	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP6Route));
+	NMPlatformIP6Route *route;
+
+	/* Add an address so that a route to the gateway below gets added. */
+	nm_platform_ip6_address_add (NM_PLATFORM_GET,
+	                             ifindex,
+	                             *nmtst_inet6_from_string ("2001:db8:8086::2"),
+	                             in6addr_any,
+	                             64,
+	                             3600,
+	                             3600,
+	                             0);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:8086::",
+	                                       48,
+	                                       NULL,
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       20,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:1337::",
+	                                       48,
+	                                       NULL,
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       0,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:abad:c0de::",
+	                                       64,
+	                                       NULL,
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       21,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	nm_route_manager_ip6_route_sync (nm_route_manager_get (), ifindex, routes, TRUE, TRUE);
+	g_array_free (routes, TRUE);
+}
+
+static GArray *
+ip6_routes (test_fixture *fixture)
+{
+	GArray *routes = nm_platform_ip6_route_get_all (NM_PLATFORM_GET,
+	                                                fixture->ifindex0,
+	                                                NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
+	GArray *routes1 = nm_platform_ip6_route_get_all (NM_PLATFORM_GET,
+	                                                 fixture->ifindex1,
+	                                                 NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
+
+	g_array_append_vals (routes, routes1->data, routes1->len);
+	g_array_free (routes1, TRUE);
+
+	return routes;
+}
+
+static void
+test_ip6 (test_fixture *fixture, gconstpointer user_data)
+{
+	GArray *routes;
+
+	NMPlatformIP6Route state1[] = {
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex0,
+			.gateway = in6addr_any,
+			.metric = 20,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex0,
+			.gateway = in6addr_any,
+			.metric = 1024,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:abad:c0de::"),
+			.plen = 64,
+			.ifindex = fixture->ifindex0,
+			.gateway = *nmtst_inet6_from_string ("2001:db8:8086::1"),
+			.metric = 21,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:abad:c0de::"),
+			.plen = 64,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 22,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 1025,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 21,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:d34d::"),
+			.plen = 64,
+			.ifindex = fixture->ifindex1,
+			.gateway = *nmtst_inet6_from_string ("2001:db8:8086::2"),
+			.metric = 20,
+			.mss = 0,
+		},
+	};
+
+	NMPlatformIP6Route state2[] = {
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex0,
+			.gateway = in6addr_any,
+			.metric = 20,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex0,
+			.gateway = in6addr_any,
+			.metric = 1024,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:abad:c0de::"),
+			.plen = 64,
+			.ifindex = fixture->ifindex0,
+			.gateway = in6addr_any,
+			.metric = 21,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:abad:c0de::"),
+			.plen = 64,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 22,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 1025,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 21,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:d34d::"),
+			.plen = 64,
+			.ifindex = fixture->ifindex1,
+			.gateway = *nmtst_inet6_from_string ("2001:db8:8086::2"),
+			.metric = 20,
+			.mss = 0,
+		},
+	};
+
+	NMPlatformIP6Route state3[] = {
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:abad:c0de::"),
+			.plen = 64,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 22,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 20,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 1024,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 1025,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 21,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:d34d::"),
+			.plen = 64,
+			.ifindex = fixture->ifindex1,
+			.gateway = *nmtst_inet6_from_string ("2001:db8:8086::2"),
+			.metric = 20,
+			.mss = 0,
+		},
+	};
+
+	setup_dev0_ip6 (fixture->ifindex0);
+	setup_dev1_ip6 (fixture->ifindex1);
+	g_test_assert_expected_messages ();
+
+	/* 2001:db8:8086::/48 on dev0 won over 2001:db8:8086::/48 on dev1
+	 * 2001:db8:d34d::/64 on dev1 could not be added
+	 * 2001:db8:1337::/48 on dev0 won over 2001:db8:1337::/48 on dev1 and has metric 1024
+	 * 2001:db8:abad:c0de::/64 routes did not clash */
+	routes = ip6_routes (fixture);
+	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state1));
+	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, state1, routes->len, TRUE);
+	g_array_free (routes, TRUE);
+
+
+	setup_dev1_ip6 (fixture->ifindex1);
+	g_test_assert_expected_messages ();
+	setup_dev0_ip6 (fixture->ifindex0);
+
+	/* Ensure nothing changed. */
+	routes = ip6_routes (fixture);
+	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state1));
+	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, state1, routes->len, TRUE);
+	g_array_free (routes, TRUE);
+
+	update_dev0_ip6 (fixture->ifindex0);
+
+	/* 2001:db8:abad:c0de::/64 on dev0 was updated for gateway removal*/
+	routes = ip6_routes (fixture);
+	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state2));
+	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, state2, routes->len, TRUE);
+	g_array_free (routes, TRUE);
+
+	nm_route_manager_route_flush (nm_route_manager_get (), fixture->ifindex0);
+
+	/* 2001:db8:abad:c0de::/64 on dev1 is still there, went away from dev0
+	 * 2001:db8:8086::/48 is now on dev1
+	 * 2001:db8:1337::/48 is now on dev1, metric of 1024 still applies
+	 * 2001:db8:d34d::/64 is present now that 2001:db8:8086::/48 is on dev1
+	 * No dev0 routes left. */
+	routes = ip6_routes (fixture);
+	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state3));
+	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, state3, routes->len, TRUE);
+	g_array_free (routes, TRUE);
+
+	nm_route_manager_route_flush (nm_route_manager_get (), fixture->ifindex1);
+
+	/* No routes left. */
+	routes = ip6_routes (fixture);
+	g_assert_cmpint (routes->len, ==, 0);
+	g_array_free (routes, TRUE);
+}
+
+/*****************************************************************************/
+
+static void
+_assert_route_check (const NMPlatformVTableRoute *vtable, gboolean has, const NMPlatformIPXRoute *route)
+{
+	const NMPlatformIPXRoute *r;
+
+	g_assert (route);
+
+	if (vtable->is_ip4)
+		r = (const NMPlatformIPXRoute *) nm_platform_ip4_route_get (NM_PLATFORM_GET, route->rx.ifindex, route->r4.network, route->rx.plen, route->rx.metric);
+	else
+		r = (const NMPlatformIPXRoute *) nm_platform_ip6_route_get (NM_PLATFORM_GET, route->rx.ifindex, route->r6.network, route->rx.plen, route->rx.metric);
+
+	if (!has) {
+		g_assert (!r);
+	} else {
+		if (!r || vtable->route_cmp (route, r) != 0)
+			g_error ("Invalid route. Expect %s, has %s",
+			         nmtst_static_1024_01 (vtable->route_to_string (route)),
+			         nmtst_static_1024_02 (vtable->route_to_string (r)));
+		g_assert (r);
+	}
+}
+
+static void
+test_ip4_full_sync (test_fixture *fixture, gconstpointer user_data)
+{
+	const NMPlatformVTableRoute *vtable = &nm_platform_vtable_route_v4;
+	gs_unref_array GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
+	NMPlatformIP4Route r01, r02, r03;
+
+	nm_log_dbg (LOGD_CORE, "TEST start test_ip4_full_sync(): start");
+
+	r01 = *nmtst_platform_ip4_route_full ("12.3.4.0", 24, NULL,
+	                                      fixture->ifindex0, NM_IP_CONFIG_SOURCE_USER,
+	                                      100, 0, RT_SCOPE_LINK, NULL);
+	r02 = *nmtst_platform_ip4_route_full ("13.4.5.6", 32, "12.3.4.1",
+	                                      fixture->ifindex0, NM_IP_CONFIG_SOURCE_USER,
+	                                      100, 0, RT_SCOPE_UNIVERSE, NULL);
+	r03 = *nmtst_platform_ip4_route_full ("14.5.6.7", 32, "12.3.4.1",
+	                                      fixture->ifindex0, NM_IP_CONFIG_SOURCE_USER,
+	                                      110, 0, RT_SCOPE_UNIVERSE, NULL);
+	g_array_set_size (routes, 2);
+	g_array_index (routes, NMPlatformIP4Route, 0) = r01;
+	g_array_index (routes, NMPlatformIP4Route, 1) = r02;
+	nm_route_manager_ip4_route_sync (nm_route_manager_get (), fixture->ifindex0, routes, TRUE, TRUE);
+
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r01);
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r02);
+	_assert_route_check (vtable, FALSE, (const NMPlatformIPXRoute *) &r03);
+
+	vtable->route_add (NM_PLATFORM_GET, 0, (const NMPlatformIPXRoute *) &r03, -1);
+
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r01);
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r02);
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r03);
+
+	nm_route_manager_ip4_route_sync (nm_route_manager_get (), fixture->ifindex0, routes, TRUE, FALSE);
+
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r01);
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r02);
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r03);
+
+	g_array_set_size (routes, 1);
+
+	nm_route_manager_ip4_route_sync (nm_route_manager_get (), fixture->ifindex0, routes, TRUE, FALSE);
+
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r01);
+	_assert_route_check (vtable, FALSE, (const NMPlatformIPXRoute *) &r02);
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r03);
+
+	nm_route_manager_ip4_route_sync (nm_route_manager_get (), fixture->ifindex0, routes, TRUE, TRUE);
+
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r01);
+	_assert_route_check (vtable, FALSE, (const NMPlatformIPXRoute *) &r02);
+	_assert_route_check (vtable, FALSE, (const NMPlatformIPXRoute *) &r03);
+
+	nm_log_dbg (LOGD_CORE, "TEST test_ip4_full_sync(): done");
+}
+
+/*****************************************************************************/
+
+static void
+fixture_setup (test_fixture *fixture, gconstpointer user_data)
+{
+	SignalData *link_added;
+
+	link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED,
+	                                NM_PLATFORM_SIGNAL_ADDED,
+	                                link_callback,
+	                                "nm-test-device0");
+	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, "nm-test-device0"));
+	g_assert (nm_platform_dummy_add (NM_PLATFORM_GET, "nm-test-device0", NULL) == NM_PLATFORM_ERROR_SUCCESS);
+	accept_signal (link_added);
+	free_signal (link_added);
+	fixture->ifindex0 = nm_platform_link_get_ifindex (NM_PLATFORM_GET, "nm-test-device0");
+	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, fixture->ifindex0, NULL));
+
+	link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED,
+	                                NM_PLATFORM_SIGNAL_ADDED,
+	                                link_callback,
+	                                "nm-test-device1");
+	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, "nm-test-device1"));
+	g_assert (nm_platform_dummy_add (NM_PLATFORM_GET, "nm-test-device1", NULL) == NM_PLATFORM_ERROR_SUCCESS);
+	accept_signal (link_added);
+	free_signal (link_added);
+	fixture->ifindex1 = nm_platform_link_get_ifindex (NM_PLATFORM_GET, "nm-test-device1");
+	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, fixture->ifindex1, NULL));
+}
+
+static void
+fixture_teardown (test_fixture *fixture, gconstpointer user_data)
+{
+	nm_platform_link_delete (NM_PLATFORM_GET, fixture->ifindex0);
+	nm_platform_link_delete (NM_PLATFORM_GET, fixture->ifindex1);
+}
+
+/*****************************************************************************/
+
+void
+init_tests (int *argc, char ***argv)
+{
+	nmtst_init_assert_logging (argc, argv, "WARN", "ALL");
+}
+
+void
+setup_tests (void)
+{
+	g_test_add ("/route-manager/ip4", test_fixture, NULL, fixture_setup, test_ip4, fixture_teardown);
+	g_test_add ("/route-manager/ip6", test_fixture, NULL, fixture_setup, test_ip6, fixture_teardown);
+
+	g_test_add ("/route-manager/ip4-full-sync", test_fixture, NULL, fixture_setup, test_ip4_full_sync, fixture_teardown);
+}