summary refs log tree commit diff
path: root/libnm-core/tests
diff options
context:
space:
mode:
Diffstat (limited to 'libnm-core/tests')
-rw-r--r--libnm-core/tests/meson.build3
-rw-r--r--libnm-core/tests/test-general.c430
-rw-r--r--libnm-core/tests/test-keyfile.c6
-rw-r--r--libnm-core/tests/test-setting.c175
4 files changed, 558 insertions, 56 deletions
diff --git a/libnm-core/tests/meson.build b/libnm-core/tests/meson.build
index 292eeaa0..58615e4d 100644
--- a/libnm-core/tests/meson.build
+++ b/libnm-core/tests/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 # test-cert.p12 created with:
 #
 # openssl pkcs12 -export \
@@ -16,6 +18,7 @@ enum_sources = gnome.mkenums_simple(
 )
 
 deps = [
+  libnm_keyfile_dep,
   libnm_core_dep,
   libnm_core_nm_default_dep,
 ]
diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c
index 0ccdd5d2..138702e4 100644
--- a/libnm-core/tests/test-general.c
+++ b/libnm-core/tests/test-general.c
@@ -10,7 +10,6 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
-#include <glib-unix.h>
 
 #include "nm-std-aux/c-list-util.h"
 #include "nm-glib-aux/nm-enum-utils.h"
@@ -52,7 +51,7 @@
 #include "nm-setting-wireless-security.h"
 #include "nm-setting-wpan.h"
 #include "nm-simple-connection.h"
-#include "nm-keyfile-internal.h"
+#include "nm-keyfile/nm-keyfile-internal.h"
 #include "nm-glib-aux/nm-dedup-multi.h"
 #include "nm-libnm-core-intern/nm-ethtool-utils.h"
 
@@ -660,6 +659,333 @@ test_nm_utils_strsplit_set (void)
 
 /*****************************************************************************/
 
+static char *
+_escaped_tokens_create_random_word_full (const char *const*tokens,
+                                         gsize n_tokens,
+                                         gsize len)
+{
+	GString *gstr = g_string_new (NULL);
+	gsize i;
+	char random_token[2] = { 0 };
+
+	for (i = 0; i < len; i++) {
+		const char *token = tokens[nmtst_get_rand_uint32 () % n_tokens];
+
+		if (!token[0]) {
+			do {
+				random_token[0] = nmtst_get_rand_uint32 ();
+			} while (random_token[0] == '\0');
+			token = random_token;
+		}
+		g_string_append (gstr, token);
+	}
+
+	/* reallocate the string, so that we don't have any excess memory from
+	 * the GString buffer. This is so that valgrind may better detect an out
+	 * or range access. */
+	return nm_str_realloc (g_string_free (gstr, FALSE));
+}
+
+/* set to 1 to exclude characters that are annoying to see in the debugger
+ * and printf() output. */
+#define ESCAPED_TOKENS_ONLY_NICE_CHARS 0
+
+static char *
+_escaped_tokens_create_random_whitespace (void)
+{
+	static const char *tokens[] = {
+		" ",
+#if !ESCAPED_TOKENS_ONLY_NICE_CHARS
+		"\n",
+		"\t",
+		"\r",
+		"\f",
+#endif
+	};
+
+	return _escaped_tokens_create_random_word_full (tokens, G_N_ELEMENTS (tokens), nmtst_get_rand_word_length (NULL) / 4u);
+}
+
+static char *
+_escaped_tokens_create_random_word (void)
+{
+	static const char *tokens[] = {
+		"a",
+		"b",
+		"c",
+		" ",
+		",",
+		"=",
+		"\\",
+#if !ESCAPED_TOKENS_ONLY_NICE_CHARS
+		"\n",
+		"\f",
+		":",
+		"",
+#endif
+	};
+
+	return _escaped_tokens_create_random_word_full (tokens, G_N_ELEMENTS (tokens), nmtst_get_rand_word_length (NULL));
+}
+
+static void
+_escaped_tokens_str_append_delimiter (GString *str,
+                                      gboolean strict,
+                                      gboolean needs_delimiter)
+{
+	guint len = nmtst_get_rand_word_length (NULL) / 10u;
+	char *s;
+
+again:
+	if (!strict) {
+		g_string_append (str, (s = _escaped_tokens_create_random_whitespace ()));
+		nm_clear_g_free (&s);
+	}
+
+	if (needs_delimiter)
+		g_string_append_c (str, ',');
+
+	if (!strict) {
+		g_string_append (str, (s = _escaped_tokens_create_random_whitespace ()));
+		nm_clear_g_free (&s);
+		if (len-- > 0) {
+			needs_delimiter = TRUE;
+			goto again;
+		}
+	}
+}
+
+static void
+_escaped_tokens_split (char *str, const char **out_key, const char **out_val)
+{
+	const char *key;
+	const char *val;
+	gsize len = strlen (str);
+
+	g_assert (str);
+
+	nm_utils_escaped_tokens_options_split (str, &key, &val);
+	g_assert (key);
+	g_assert (key == str);
+	if (val) {
+		g_assert (val > str);
+		g_assert (val > key);
+		g_assert (val <= &str[len]);
+	}
+	NM_SET_OUT (out_key, key);
+	NM_SET_OUT (out_val, val);
+}
+
+static void
+_escaped_tokens_combine (GString *combined,
+                         const char *key,
+                         const char *val,
+                         gboolean strict,
+                         gboolean allow_append_delimiter_before,
+                         gboolean needs_delimiter_after)
+{
+	gs_free char *escaped_key = NULL;
+	gs_free char *escaped_val = NULL;
+
+	if (allow_append_delimiter_before)
+		_escaped_tokens_str_append_delimiter (combined, strict, FALSE);
+	g_string_append (combined, nm_utils_escaped_tokens_options_escape_key (key, &escaped_key));
+	if (val) {
+		char *s;
+
+		if (!strict) {
+			g_string_append (combined, (s = _escaped_tokens_create_random_whitespace ()));
+			nm_clear_g_free (&s);
+		}
+		g_string_append_c (combined, '=');
+		if (!strict) {
+			g_string_append (combined, (s = _escaped_tokens_create_random_whitespace ()));
+			nm_clear_g_free (&s);
+		}
+		g_string_append (combined, nm_utils_escaped_tokens_options_escape_val (val, &escaped_val));
+	}
+	_escaped_tokens_str_append_delimiter (combined, strict, needs_delimiter_after);
+}
+
+static void
+_escaped_tokens_check_one_impl (const char *expected_key,
+                                const char *expected_val,
+                                const char *expected_combination,
+                                const char *const*other,
+                                gsize n_other)
+{
+	nm_auto_free_gstring GString *combined = g_string_new (NULL);
+	gsize i;
+
+	g_assert (expected_key);
+	g_assert (expected_combination);
+	g_assert (other);
+
+	_escaped_tokens_combine (combined,
+	                         expected_key,
+	                         expected_val,
+	                         TRUE,
+	                         TRUE,
+	                         FALSE);
+
+	g_assert_cmpstr (combined->str, ==, expected_combination);
+
+	for (i = 0; i < n_other + 2u; i++) {
+		nm_auto_free_gstring GString *str0 = NULL;
+		gs_free const char **strv_split = NULL;
+		gs_free char *strv_split0 = NULL;
+		const char *comb;
+		const char *key;
+		const char *val;
+
+		if (i == 0)
+			comb = expected_combination;
+		else if (i == 1) {
+			_escaped_tokens_combine (nm_gstring_prepare (&str0),
+			                         expected_key,
+			                         expected_val,
+			                         FALSE,
+			                         TRUE,
+			                         FALSE);
+			comb = str0->str;
+		} else
+			comb = other[i - 2];
+
+		strv_split = nm_utils_escaped_tokens_options_split_list (comb);
+		if (!strv_split) {
+			g_assert_cmpstr (expected_key, ==, "");
+			g_assert_cmpstr (expected_val, ==, NULL);
+			continue;
+		}
+		g_assert (expected_val || expected_key[0]);
+
+		g_assert_cmpuint (NM_PTRARRAY_LEN (strv_split), ==, 1u);
+
+		strv_split0 = g_strdup (strv_split[0]);
+
+		_escaped_tokens_split (strv_split0, &key, &val);
+		g_assert_cmpstr (key, ==, expected_key);
+		g_assert_cmpstr (val, ==, expected_val);
+	}
+}
+
+#define _escaped_tokens_check_one(expected_key, expected_val, expected_combination, ...) \
+	_escaped_tokens_check_one_impl (expected_key, expected_val, expected_combination, NM_MAKE_STRV (__VA_ARGS__), NM_NARG (__VA_ARGS__))
+
+static void
+test_nm_utils_escaped_tokens (void)
+{
+	int i_run;
+
+	for (i_run = 0; i_run < 1000; i_run++) {
+		const guint num_options = nmtst_get_rand_word_length (NULL);
+		gs_unref_ptrarray GPtrArray *options = g_ptr_array_new_with_free_func (g_free);
+		nm_auto_free_gstring GString *combined = g_string_new (NULL);
+		gs_free const char **strv_split = NULL;
+		guint i_option;
+		guint i;
+
+		/* Generate a list of random words for option key-value pairs. */
+		for (i_option = 0; i_option < 2u * num_options; i_option++) {
+			char *word = NULL;
+
+			if (   i_option % 2u == 1
+			    && nmtst_get_rand_uint32 () % 5 == 0
+			    && strlen (options->pdata[options->len - 1]) > 0u) {
+				/* For some options, leave the value unset and only generate a key.
+				 *
+				 * If key is "", then we cannot do that, because the test below would try
+				 * to append "" to the combined list, which the parser then would drop.
+				 * Only test omitting the value, if strlen() of the key is positive. */
+			} else
+				word = _escaped_tokens_create_random_word ();
+			g_ptr_array_add (options, word);
+		}
+
+		/* Combine the options in one comma separated list, with proper escaping. */
+		for (i_option = 0; i_option < num_options; i_option++) {
+			_escaped_tokens_combine (combined,
+			                         options->pdata[2u*i_option + 0u],
+			                         options->pdata[2u*i_option + 1u],
+			                         FALSE,
+			                         i_option == 0,
+			                         i_option != num_options - 1);
+		}
+
+		/* ensure that we can split and parse the options without difference. */
+		strv_split = nm_utils_escaped_tokens_options_split_list (combined->str);
+		for (i_option = 0; i_option < num_options; i_option++) {
+			const char *expected_key = options->pdata[2u*i_option + 0u];
+			const char *expected_val = options->pdata[2u*i_option + 1u];
+			gs_free char *s_split = i_option < NM_PTRARRAY_LEN (strv_split) ? g_strdup (strv_split[i_option]) : NULL;
+			const char *key = NULL;
+			const char *val = NULL;
+
+			if (s_split)
+				_escaped_tokens_split (s_split, &key, &val);
+
+			if (   !nm_streq0 (key, expected_key)
+			    || !nm_streq0 (val, expected_val)) {
+				g_print (">>> ASSERTION IS ABOUT TO FAIL for item %5d of %5d\n", i_option, num_options);
+				g_print (">>> combined =  \"%s\"\n", combined->str);
+				g_print (">>> %c   parsed[%5d].key = \"%s\"\n", nm_streq (key, expected_key) ? ' ' : 'X', i_option, key);
+				g_print (">>> %c   parsed[%5d].val = %s%s%s\n", nm_streq0 (val, expected_val) ? ' ' : 'X', i_option, NM_PRINT_FMT_QUOTE_STRING (val));
+				for (i = 0; i < num_options; i++) {
+					g_print (">>> %c original[%5d].key = \"%s\"\n", i == i_option ? '*' : ' ', i, (char *) options->pdata[2u*i + 0u]);
+					g_print (">>> %c original[%5d].val = %s%s%s\n", i == i_option ? '*' : ' ', i, NM_PRINT_FMT_QUOTE_STRING ((char *) options->pdata[2u*i + 1u]));
+				}
+				for (i = 0; i < NM_PTRARRAY_LEN (strv_split); i++)
+					g_print (">>>      split[%5d]     = \"%s\"\n", i, strv_split[i]);
+			}
+
+			g_assert_cmpstr (key, ==, expected_key);
+			g_assert_cmpstr (val, ==, expected_val);
+		}
+		g_assert_cmpint (NM_PTRARRAY_LEN (strv_split), ==, num_options);
+
+		/* Above we show a full round-trip of random option key-value pairs, that they can
+		 * without loss escape, concatenate, split-list, and split. This proofed that every
+		 * option key-value pair can be represented as a combined string and parsed back.
+		 *
+		 * Now, just check that we can also parse arbitrary random words in nm_utils_escaped_tokens_options_split().
+		 * split() is a non-injective surjective function. As we check the round-trip above for random words, where
+		 * options-split() is the last step, we show that every random word can be the output of the function
+		 * (which shows, the surjective part).
+		 *
+		 * But multiple random input arguments, may map to the same output argument (non-injective).
+		 * Just test whether we can handle random input words without crashing. For that, just use the
+		 * above generate list of random words.
+		 */
+		for (i = 0; i < 1u + 2u * i_option; i++) {
+			gs_free char *str = NULL;
+			const char *cstr;
+
+			if (i == 0)
+				cstr = combined->str;
+			else
+				cstr = options->pdata[i - 1u];
+			if (!cstr)
+				continue;
+
+			str = g_strdup (cstr);
+			_escaped_tokens_split (str, NULL, NULL);
+		}
+	}
+
+	_escaped_tokens_check_one ("", NULL, "");
+	_escaped_tokens_check_one ("", "", "=", " =");
+	_escaped_tokens_check_one ("a", "b", "a=b", "a = b");
+	_escaped_tokens_check_one ("a\\=", "b\\=", "a\\\\\\==b\\\\=", "a\\\\\\==b\\\\\\=");
+	_escaped_tokens_check_one ("\\=", "\\=", "\\\\\\==\\\\=", "\\\\\\==\\\\\\=");
+	_escaped_tokens_check_one (" ", "bb=", "\\ =bb=", "\\ =bb\\=");
+	_escaped_tokens_check_one (" ", "bb\\=", "\\ =bb\\\\=", "\\ =bb\\\\\\=");
+	_escaped_tokens_check_one ("a b", "a  b", "a b=a  b");
+	_escaped_tokens_check_one ("a b", "a  b", "a b=a  b");
+	_escaped_tokens_check_one ("a = b", "a = b", "a \\= b=a = b", "a \\= b=a \\= b");
+}
+
+/*****************************************************************************/
+
 typedef struct {
 	int val;
 	CList lst;
@@ -1173,13 +1499,11 @@ test_setting_vpn_items (void)
 	nm_setting_vpn_add_data_item (s_vpn, "", "");
 	g_test_assert_expected_messages ();
 
-	NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (item && item[0]));
-	nm_setting_vpn_add_data_item (s_vpn, "foobar1", NULL);
-	g_test_assert_expected_messages ();
-
-	NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (item && item[0]));
 	nm_setting_vpn_add_data_item (s_vpn, "foobar1", "");
-	g_test_assert_expected_messages ();
+	g_assert_cmpstr (nm_setting_vpn_get_data_item (s_vpn, "foobar1"), ==, "");
+
+	nm_setting_vpn_add_data_item (s_vpn, "foobar1", NULL);
+	g_assert_cmpstr (nm_setting_vpn_get_data_item (s_vpn, "foobar1"), ==, NULL);
 
 	NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key && key[0]));
 	nm_setting_vpn_add_data_item (s_vpn, NULL, "blahblah1");
@@ -1200,13 +1524,9 @@ test_setting_vpn_items (void)
 	nm_setting_vpn_add_secret (s_vpn, "", "");
 	g_test_assert_expected_messages ();
 
-	NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (secret && secret[0]));
-	nm_setting_vpn_add_secret (s_vpn, "foobar1", NULL);
-	g_test_assert_expected_messages ();
-
-	NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (secret && secret[0]));
 	nm_setting_vpn_add_secret (s_vpn, "foobar1", "");
-	g_test_assert_expected_messages ();
+
+	nm_setting_vpn_add_secret (s_vpn, "foobar1", NULL);
 
 	NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key && key[0]));
 	nm_setting_vpn_add_secret (s_vpn, NULL, "blahblah1");
@@ -3494,14 +3814,14 @@ test_setting_compare_addresses (void)
 	nm_ip_address_unref (a);
 
 	if (nmtst_get_rand_uint32 () % 2)
-		NMTST_SWAP (s1, s2);
+		NM_SWAP (s1, s2);
 
 	success = nm_setting_compare (s1, s2, NM_SETTING_COMPARE_FLAG_EXACT);
 	g_assert (!success);
 
 	success = nm_setting_diff (s1, s2, NM_SETTING_COMPARE_FLAG_EXACT, FALSE, &result);
 	g_assert (!success);
-	g_clear_pointer (&result, g_hash_table_unref);
+	nm_clear_pointer (&result, g_hash_table_unref);
 }
 
 static void
@@ -3526,14 +3846,14 @@ test_setting_compare_routes (void)
 	nm_ip_route_unref (r);
 
 	if (nmtst_get_rand_uint32 () % 2)
-		NMTST_SWAP (s1, s2);
+		NM_SWAP (s1, s2);
 
 	success = nm_setting_compare (s1, s2, NM_SETTING_COMPARE_FLAG_EXACT);
 	g_assert (!success);
 
 	success = nm_setting_diff (s1, s2, NM_SETTING_COMPARE_FLAG_EXACT, FALSE, &result);
 	g_assert (!success);
-	g_clear_pointer (&result, g_hash_table_unref);
+	nm_clear_pointer (&result, g_hash_table_unref);
 }
 
 static void
@@ -3551,7 +3871,7 @@ test_setting_compare_wired_cloned_mac_address (void)
 	g_assert_cmpstr ("stable", ==, nm_setting_wired_get_cloned_mac_address ((NMSettingWired *) old));
 	g_object_get (old, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, &str1, NULL);
 	g_assert_cmpstr ("stable", ==, str1);
-	g_clear_pointer (&str1, g_free);
+	nm_clear_g_free (&str1);
 
 	new = nm_setting_duplicate (old);
 	g_object_set (new, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, "11:22:33:44:55:66", NULL);
@@ -3559,7 +3879,7 @@ test_setting_compare_wired_cloned_mac_address (void)
 	g_assert_cmpstr ("11:22:33:44:55:66", ==, nm_setting_wired_get_cloned_mac_address ((NMSettingWired *) new));
 	g_object_get (new, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, &str1, NULL);
 	g_assert_cmpstr ("11:22:33:44:55:66", ==, str1);
-	g_clear_pointer (&str1, g_free);
+	nm_clear_g_free (&str1);
 
 	success = nm_setting_compare (old, new, NM_SETTING_COMPARE_FLAG_EXACT);
 	g_assert (!success);
@@ -3571,7 +3891,7 @@ test_setting_compare_wired_cloned_mac_address (void)
 	g_assert_cmpstr ("stable-bia", ==, nm_setting_wired_get_cloned_mac_address ((NMSettingWired *) new));
 	g_object_get (new, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, &str1, NULL);
 	g_assert_cmpstr ("stable-bia", ==, str1);
-	g_clear_pointer (&str1, g_free);
+	nm_clear_g_free (&str1);
 
 	success = nm_setting_compare (old, new, NM_SETTING_COMPARE_FLAG_EXACT);
 	g_assert (!success);
@@ -3593,7 +3913,7 @@ test_setting_compare_wireless_cloned_mac_address (void)
 	g_assert_cmpstr ("stable", ==, nm_setting_wireless_get_cloned_mac_address ((NMSettingWireless *) old));
 	g_object_get (old, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, &str1, NULL);
 	g_assert_cmpstr ("stable", ==, str1);
-	g_clear_pointer (&str1, g_free);
+	nm_clear_g_free (&str1);
 
 	new = nm_setting_duplicate (old);
 	g_object_set (new, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, "11:22:33:44:55:66", NULL);
@@ -3601,7 +3921,7 @@ test_setting_compare_wireless_cloned_mac_address (void)
 	g_assert_cmpstr ("11:22:33:44:55:66", ==, nm_setting_wireless_get_cloned_mac_address ((NMSettingWireless *) new));
 	g_object_get (new, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, &str1, NULL);
 	g_assert_cmpstr ("11:22:33:44:55:66", ==, str1);
-	g_clear_pointer (&str1, g_free);
+	nm_clear_g_free (&str1);
 
 	success = nm_setting_compare (old, new, NM_SETTING_COMPARE_FLAG_EXACT);
 	g_assert (!success);
@@ -4648,7 +4968,7 @@ test_connection_normalize_virtual_iface_name (void)
 	g_variant_unref (setting_dict);
 	g_variant_unref (var);
 
-	/* If vlan.interface-name is invalid, deserialization will fail. */
+	/* If vlan.interface-name will be ignored. */
 	NMTST_VARIANT_EDITOR (connection_dict,
 	                      NMTST_VARIANT_CHANGE_PROPERTY (NM_SETTING_VLAN_SETTING_NAME,
 	                                                     "interface-name",
@@ -4657,8 +4977,9 @@ test_connection_normalize_virtual_iface_name (void)
 	                      );
 
 	con = _connection_new_from_dbus (connection_dict, &error);
-	g_assert_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY);
-	g_clear_error (&error);
+	nmtst_assert_success (con, error);
+	g_assert_cmpstr (nm_connection_get_interface_name (con), ==, IFACE_NAME);
+	g_clear_object (&con);
 
 	/* If vlan.interface-name is valid, but doesn't match, it will be ignored. */
 	NMTST_VARIANT_EDITOR (connection_dict,
@@ -6820,7 +7141,7 @@ _team_config_equal_check (const char *conf1,
 	gboolean is_same;
 
 	if (nmtst_get_rand_bool ())
-		NMTST_SWAP (conf1, conf2);
+		NM_SWAP (conf1, conf2);
 
 	if (!nm_streq0 (conf1, conf2)) {
 		_team_config_equal_check (conf1, conf1, port_config, TRUE);
@@ -7545,10 +7866,11 @@ _do_test_utils_str_utf8safe (const char *str, gsize str_len, const char *expecte
 	gs_free char *str_free_7 = NULL;
 	gs_free char *str_free_8 = NULL;
 	gboolean str_has_nul = FALSE;
+#define RND_FLAG ((nmtst_get_rand_bool ()) ? NM_UTILS_STR_UTF8_SAFE_FLAG_NONE : NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET)
 
-	buf_safe = nm_utils_buf_utf8safe_escape (str, str_len, flags, &str_free_1);
+	buf_safe = nm_utils_buf_utf8safe_escape (str, str_len, flags | RND_FLAG, &str_free_1);
 
-	str_safe = nm_utils_str_utf8safe_escape (str, flags, &str_free_2);
+	str_safe = nm_utils_str_utf8safe_escape (str, flags | RND_FLAG, &str_free_2);
 
 	if (str_len == 0) {
 		g_assert (buf_safe == NULL);
@@ -7568,7 +7890,7 @@ _do_test_utils_str_utf8safe (const char *str, gsize str_len, const char *expecte
 	} else
 		str_has_nul = TRUE;
 
-	str_free_3 = nm_utils_str_utf8safe_escape_cp (str, flags);
+	str_free_3 = nm_utils_str_utf8safe_escape_cp (str, flags | RND_FLAG);
 	g_assert_cmpstr (str_free_3, ==, str_safe);
 	g_assert ((!str && !str_free_3) || (str != str_free_3));
 
@@ -8201,8 +8523,12 @@ _test_integrate_cb_idle_2 (gpointer user_data)
 	g_assert (d->extra_sources[0]);
 	g_assert (!d->extra_sources[1]);
 
-	extra_source = g_unix_fd_source_new (d->fd_2, G_IO_IN);
-	g_source_set_callback (extra_source, G_SOURCE_FUNC (_test_integrate_cb_fd_2), d, NULL);
+	extra_source = nm_g_unix_fd_source_new (d->fd_2,
+	                                        G_IO_IN,
+	                                        G_PRIORITY_DEFAULT,
+	                                        _test_integrate_cb_fd_2,
+	                                        d,
+	                                        NULL);
 	g_source_attach (extra_source, d->c2);
 
 	d->extra_sources[1] = extra_source;
@@ -8269,7 +8595,7 @@ test_integrate_maincontext (gconstpointer test_data)
 		g_source_set_callback (idle_source_1, _test_integrate_maincontext_cb_idle1, &count, NULL);
 		g_source_attach (idle_source_1, c2);
 
-		nmtst_main_context_iterate_until (c1, 2000, count == 5);
+		nmtst_main_context_iterate_until_assert (c1, 2000, count == 5);
 	}
 
 	if (TEST_IDX == 2) {
@@ -8296,8 +8622,12 @@ test_integrate_maincontext (gconstpointer test_data)
 
 		fd_1 = open ("/dev/null", O_RDONLY | O_CLOEXEC);
 		g_assert (fd_1 >= 0);
-		fd_source_1 = g_unix_fd_source_new (fd_1, G_IO_IN);
-		g_source_set_callback (fd_source_1, G_SOURCE_FUNC (_test_integrate_cb_fd_1), &d, NULL);
+		fd_source_1 = nm_g_unix_fd_source_new (fd_1,
+		                                       G_IO_IN,
+		                                       G_PRIORITY_DEFAULT,
+		                                       _test_integrate_cb_fd_1,
+		                                       &d,
+		                                       NULL);
 		g_source_attach (fd_source_1, c2);
 
 		fd_2 = open ("/dev/null", O_RDONLY | O_CLOEXEC);
@@ -8321,6 +8651,33 @@ test_integrate_maincontext (gconstpointer test_data)
 	}
 }
 
+/*****************************************************************************/
+
+static void
+test_nm_ip_addr_zero (void)
+{
+	in_addr_t a4 = nmtst_inet4_from_string ("0.0.0.0");
+	struct in6_addr a6 = *nmtst_inet6_from_string ("::");
+	char buf[NM_UTILS_INET_ADDRSTRLEN];
+	NMIPAddr a = NM_IP_ADDR_INIT;
+
+	g_assert (memcmp (&a, &nm_ip_addr_zero, sizeof (a)) == 0);
+
+	g_assert (IN6_IS_ADDR_UNSPECIFIED (&nm_ip_addr_zero.addr6));
+	g_assert (memcmp (&nm_ip_addr_zero.addr6, &in6addr_any, sizeof (in6addr_any)) == 0);
+
+	g_assert (memcmp (&nm_ip_addr_zero, &a4, sizeof (a4)) == 0);
+	g_assert (memcmp (&nm_ip_addr_zero, &a6, sizeof (a6)) == 0);
+
+	g_assert_cmpstr (_nm_utils_inet4_ntop (nm_ip_addr_zero.addr4, buf), ==, "0.0.0.0");
+	g_assert_cmpstr (_nm_utils_inet6_ntop (&nm_ip_addr_zero.addr6, buf), ==, "::");
+
+	g_assert_cmpstr (nm_utils_inet_ntop (AF_INET, &nm_ip_addr_zero, buf), ==, "0.0.0.0");
+	g_assert_cmpstr (nm_utils_inet_ntop (AF_INET6, &nm_ip_addr_zero, buf), ==, "::");
+
+	G_STATIC_ASSERT_EXPR (sizeof (a) == sizeof (a.array));
+}
+
 static void
 test_connection_ovs_ifname (gconstpointer test_data)
 {
@@ -8523,6 +8880,7 @@ int main (int argc, char **argv)
 	g_test_add_func ("/core/general/test_dedup_multi", test_dedup_multi);
 	g_test_add_func ("/core/general/test_utils_str_utf8safe", test_utils_str_utf8safe);
 	g_test_add_func ("/core/general/test_nm_utils_strsplit_set", test_nm_utils_strsplit_set);
+	g_test_add_func ("/core/general/test_nm_utils_escaped_tokens", test_nm_utils_escaped_tokens);
 	g_test_add_func ("/core/general/test_nm_in_set", test_nm_in_set);
 	g_test_add_func ("/core/general/test_nm_in_strset", test_nm_in_strset);
 	g_test_add_func ("/core/general/test_setting_vpn_items", test_setting_vpn_items);
@@ -8681,5 +9039,7 @@ int main (int argc, char **argv)
 	g_test_add_data_func ("/core/general/test_integrate_maincontext/1", GUINT_TO_POINTER (1), test_integrate_maincontext);
 	g_test_add_data_func ("/core/general/test_integrate_maincontext/2", GUINT_TO_POINTER (2), test_integrate_maincontext);
 
+	g_test_add_func ("/core/general/test_nm_ip_addr_zero", test_nm_ip_addr_zero);
+
 	return g_test_run ();
 }
diff --git a/libnm-core/tests/test-keyfile.c b/libnm-core/tests/test-keyfile.c
index 574d671f..47a27571 100644
--- a/libnm-core/tests/test-keyfile.c
+++ b/libnm-core/tests/test-keyfile.c
@@ -5,8 +5,8 @@
 
 #include "nm-default.h"
 
-#include "nm-keyfile-utils.h"
-#include "nm-keyfile-internal.h"
+#include "nm-keyfile/nm-keyfile-utils.h"
+#include "nm-keyfile/nm-keyfile-internal.h"
 #include "nm-simple-connection.h"
 #include "nm-setting-connection.h"
 #include "nm-setting-wired.h"
@@ -99,7 +99,7 @@ test_encode_key (void)
 		GKeyFile **_keyfile = (keyfile); \
 		\
 		g_clear_object (_con); \
-		g_clear_pointer (_keyfile, g_key_file_unref); \
+		nm_clear_pointer (_keyfile, g_key_file_unref); \
 	} G_STMT_END
 
 static void
diff --git a/libnm-core/tests/test-setting.c b/libnm-core/tests/test-setting.c
index 5b2c7077..cb8f41fb 100644
--- a/libnm-core/tests/test-setting.c
+++ b/libnm-core/tests/test-setting.c
@@ -24,7 +24,7 @@
 #include "nm-simple-connection.h"
 #include "nm-setting-connection.h"
 #include "nm-errors.h"
-#include "nm-keyfile-internal.h"
+#include "nm-keyfile/nm-keyfile-internal.h"
 
 #include "nm-utils/nm-test-utils.h"
 
@@ -500,8 +500,6 @@ create_bond_connection (NMConnection **con, NMSettingBond **s_bond)
 	                                        NULL,
 	                                        NM_SETTING_BOND_SETTING_NAME,
 	                                        &s_con);
-	g_assert (*con);
-	g_assert (s_con);
 
 	g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "bond0", NULL);
 
@@ -512,28 +510,25 @@ create_bond_connection (NMConnection **con, NMSettingBond **s_bond)
 }
 
 #define test_verify_options(exp, ...) \
-	_test_verify_options (NM_MAKE_STRV (__VA_ARGS__), exp)
+	_test_verify_options (exp, NM_MAKE_STRV (__VA_ARGS__))
 
 static void
-_test_verify_options (const char *const *options,
-                      gboolean expected_result)
+_test_verify_options (gboolean expected_result,
+                      const char *const *options)
 {
 	gs_unref_object NMConnection *con = NULL;
 	NMSettingBond *s_bond;
-	GError *error = NULL;
-	gboolean success;
 	const char *const *option;
 
+	g_assert (NM_PTRARRAY_LEN (options) % 2 == 0);
+
 	create_bond_connection (&con, &s_bond);
 
-	for (option = options; option[0] && option[1]; option += 2)
+	for (option = options; option[0]; option += 2)
 		g_assert (nm_setting_bond_add_option (s_bond, option[0], option[1]));
 
 	if (expected_result) {
 		nmtst_assert_connection_verifies_and_normalizable (con);
-		nmtst_connection_normalize (con);
-		success = nm_setting_verify ((NMSetting *) s_bond, con, &error);
-		nmtst_assert_success (success, error);
 	} else {
 		nmtst_assert_connection_unnormalizable (con,
 		                                        NM_CONNECTION_ERROR,
@@ -595,6 +590,16 @@ test_bond_verify (void)
 	                     "mode", "0",
 	                     "downdelay", "0",
 	                     "updelay", "0");
+	test_verify_options (TRUE,
+	                     "mode", "0",
+	                     "miimon", "100",
+	                     "arp_ip_target", "1.1.1.1",
+	                     "arp_interval", "200");
+	test_verify_options (TRUE,
+	                     "mode", "0",
+	                     "downdelay", "100",
+	                     "arp_ip_target", "1.1.1.1",
+	                     "arp_interval", "200");
 }
 
 static void
@@ -631,21 +636,23 @@ test_bond_compare (void)
 	                           ((const char *[]){ "mode", "balance-rr", "miimon", "1", NULL }),
 	                           ((const char *[]){ "mode", "balance-rr", "miimon", "2", NULL }));
 
-	/* ignore default values */
-	test_bond_compare_options (TRUE,
+	test_bond_compare_options (FALSE,
 	                           ((const char *[]){ "miimon", "1", NULL }),
 	                           ((const char *[]){ "miimon", "1", "updelay", "0", NULL }));
 
-	/* special handling of num_grat_arp, num_unsol_na */
 	test_bond_compare_options (FALSE,
 	                           ((const char *[]){ "num_grat_arp", "2", NULL }),
 	                           ((const char *[]){ "num_grat_arp", "1", NULL }));
-	test_bond_compare_options (TRUE,
+	test_bond_compare_options (FALSE,
 	                           ((const char *[]){ "num_grat_arp", "3", NULL }),
 	                           ((const char *[]){ "num_unsol_na", "3", NULL }));
-	test_bond_compare_options (TRUE,
+	test_bond_compare_options (FALSE,
 	                           ((const char *[]){ "num_grat_arp", "4", NULL }),
 	                           ((const char *[]){ "num_unsol_na", "4", "num_grat_arp", "4", NULL }));
+
+	test_bond_compare_options (FALSE,
+	                           ((const char *[]){ "mode", "balance-rr", "miimon", "100", NULL }),
+	                           ((const char *[]){ "mode", "balance-rr", NULL }));
 }
 
 static void
@@ -1840,7 +1847,7 @@ test_bridge_vlans (void)
 	str = nm_bridge_vlan_to_str (v1, &error);
 	nmtst_assert_success (str, error);
 	g_assert_cmpstr (str, ==, "10 pvid");
-	g_clear_pointer (&str, g_free);
+	nm_clear_g_free (&str);
 
 	v2 = nm_bridge_vlan_from_str ("  10  pvid  ", &error);
 	nmtst_assert_success (v2, error);
@@ -1862,6 +1869,137 @@ test_bridge_vlans (void)
 	nm_bridge_vlan_unref (v2);
 }
 
+static void
+create_bridge_connection (NMConnection **con, NMSettingBridge **s_bridge)
+{
+	NMSettingConnection *s_con;
+
+	g_assert (con);
+	g_assert (s_bridge);
+
+	*con = nmtst_create_minimal_connection ("bridge",
+	                                        NULL,
+	                                        NM_SETTING_BOND_SETTING_NAME,
+	                                        &s_con);
+
+	g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "bridge0", NULL);
+
+	*s_bridge = (NMSettingBridge *) nm_setting_bridge_new ();
+	g_assert (*s_bridge);
+
+	nm_connection_add_setting (*con, NM_SETTING (*s_bridge));
+}
+
+#define test_verify_options_bridge(exp, ...) \
+	_test_verify_options_bridge (exp, NM_MAKE_STRV (__VA_ARGS__))
+
+static void
+_test_verify_options_bridge (gboolean expected_result,
+                             const char *const *options)
+{
+	gs_unref_object NMConnection *con = NULL;
+	NMSettingBridge *s_bridge;
+	const char *const *option;
+
+	g_assert (NM_PTRARRAY_LEN (options) % 2 == 0);
+
+	create_bridge_connection (&con, &s_bridge);
+
+	for (option = options; option[0]; option += 2) {
+		const char *option_key = option[0];
+		const char *option_val = option[1];
+		GParamSpec *pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (s_bridge), option_key);
+
+		g_assert (pspec);
+		g_assert (option_val);
+
+		switch (G_PARAM_SPEC_VALUE_TYPE (pspec)) {
+		case G_TYPE_UINT: {
+				guint uvalue;
+
+				uvalue = _nm_utils_ascii_str_to_uint64 (option_val, 10, 0, G_MAXUINT, 0);
+				g_object_set (s_bridge, option_key, uvalue, NULL);
+			}
+			break;
+		case G_TYPE_BOOLEAN: {
+				gboolean bvalue;
+
+				bvalue = _nm_utils_ascii_str_to_bool (option_val, FALSE);
+				g_object_set (s_bridge, option_key, bvalue, NULL);
+			}
+			break;
+		case G_TYPE_STRING:
+			g_object_set (s_bridge, option_key, option_val, NULL);
+			break;
+		default:
+			g_assert_not_reached();
+			break;
+		}
+	}
+
+	if (expected_result)
+		nmtst_assert_connection_verifies_and_normalizable (con);
+	else {
+		nmtst_assert_connection_unnormalizable (con,
+		                                        NM_CONNECTION_ERROR,
+		                                        NM_CONNECTION_ERROR_INVALID_PROPERTY);
+	}
+}
+
+static void
+test_bridge_verify (void)
+{
+	/* group-address */
+	test_verify_options_bridge (FALSE,
+	                            "group-address", "nonsense");
+	test_verify_options_bridge (FALSE,
+	                            "group-address", "FF:FF:FF:FF:FF:FF");
+	test_verify_options_bridge (FALSE,
+	                            "group-address", "01:02:03:04:05:06");
+	test_verify_options_bridge (TRUE,
+	                            "group-address", "01:80:C2:00:00:00");
+	test_verify_options_bridge (FALSE,
+	                            "group-address", "01:80:C2:00:00:02");
+	test_verify_options_bridge (FALSE,
+	                            "group-address", "01:80:C2:00:00:03");
+	test_verify_options_bridge (TRUE,
+	                            "group-address", "01:80:C2:00:00:00");
+	test_verify_options_bridge (TRUE,
+	                            "group-address", "01:80:C2:00:00:0A");
+	/* vlan-protocol */
+	test_verify_options_bridge (FALSE,
+	                            "vlan-protocol", "nonsense124");
+	test_verify_options_bridge (FALSE,
+	                            "vlan-protocol", "802.11");
+	test_verify_options_bridge (FALSE,
+	                            "vlan-protocol", "802.1Q1");
+	test_verify_options_bridge (TRUE,
+	                            "vlan-protocol", "802.1Q");
+	test_verify_options_bridge (TRUE,
+	                            "vlan-protocol", "802.1ad");
+	/* multicast-router */
+	test_verify_options_bridge (FALSE,
+	                            "multicast-router",   "nonsense");
+	test_verify_options_bridge (FALSE,
+	                            "multicast-snooping", "no",
+	                            "multicast-router",   "auto");
+	test_verify_options_bridge (FALSE,
+	                            "multicast-snooping", "no",
+	                            "multicast-router",   "enabled");
+	test_verify_options_bridge (TRUE,
+	                            "multicast-snooping", "no",
+	                            "multicast-router",   "disabled");
+	test_verify_options_bridge (TRUE,
+	                            "multicast-snooping", "yes",
+	                            "multicast-router",   "enabled");
+	test_verify_options_bridge (TRUE,
+	                            "multicast-snooping", "yes",
+	                            "multicast-router",   "auto");
+	test_verify_options_bridge (TRUE,
+	                            "multicast-snooping", "yes",
+	                            "multicast-router",   "disabled");
+}
+
 /*****************************************************************************/
 
 static void
@@ -3641,6 +3779,7 @@ main (int argc, char **argv)
 	g_test_add_func ("/libnm/settings/tc_config/dbus", test_tc_config_dbus);
 
 	g_test_add_func ("/libnm/settings/bridge/vlans", test_bridge_vlans);
+	g_test_add_func ("/libnm/settings/bridge/verify", test_bridge_verify);
 
 	g_test_add_func ("/libnm/settings/team/sync_runner_from_config_roundrobin",
 	                 test_runner_roundrobin_sync_from_config);