]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
test: extend tests for new machine tag rules 42618/head
authorLennart Poettering <lennart@amutable.com>
Tue, 16 Jun 2026 13:58:05 +0000 (15:58 +0200)
committerLennart Poettering <lennart@amutable.com>
Fri, 19 Jun 2026 21:05:36 +0000 (23:05 +0200)
src/test/test-condition.c
src/test/test-hostname-util.c

index 4f450ab12d8d4030b6386fa9edd35f062819671b..a4c57dc1979dcaeb3b373413bb2e6fb546f4fb82 100644 (file)
@@ -1474,6 +1474,69 @@ TEST(condition_test_machine_tag) {
         ASSERT_OK_ZERO(condition_test(condition, environ));
         condition_free(condition);
 
+        /* Key/value (parameterized) tags */
+        ASSERT_OK(write_string_file(f, "TAGS=\"role=webserver:env=prod:berlin\"\n",
+                                    WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE));
+
+        /* Exact match of a key/value tag */
+        ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "role=webserver", /* trigger= */ false, /* negate= */ false)));
+        ASSERT_OK_POSITIVE(condition_test(condition, environ));
+        condition_free(condition);
+
+        /* Glob on the value */
+        ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "role=web*", /* trigger= */ false, /* negate= */ false)));
+        ASSERT_OK_POSITIVE(condition_test(condition, environ));
+        condition_free(condition);
+
+        /* Glob on the key */
+        ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "*=prod", /* trigger= */ false, /* negate= */ false)));
+        ASSERT_OK_POSITIVE(condition_test(condition, environ));
+        condition_free(condition);
+
+        /* A bare key does not match an assignment (the "=" is part of the tag) */
+        ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "role", /* trigger= */ false, /* negate= */ false)));
+        ASSERT_OK_ZERO(condition_test(condition, environ));
+        condition_free(condition);
+
+        /* Right key, wrong value → no match */
+        ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "role=database", /* trigger= */ false, /* negate= */ false)));
+        ASSERT_OK_ZERO(condition_test(condition, environ));
+        condition_free(condition);
+
+        /* A plain (non-parameterized) tag still matches alongside key/value tags */
+        ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "berlin", /* trigger= */ false, /* negate= */ false)));
+        ASSERT_OK_POSITIVE(condition_test(condition, environ));
+        condition_free(condition);
+
+        /* Negation against a key/value tag */
+        ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "role=database", /* trigger= */ false, /* negate= */ true)));
+        ASSERT_OK_POSITIVE(condition_test(condition, environ));
+        condition_free(condition);
+        ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "role=webserver", /* trigger= */ false, /* negate= */ true)));
+        ASSERT_OK_ZERO(condition_test(condition, environ));
+        condition_free(condition);
+
+        /* Conflicting values for the same key: graceful parsing keeps the first (lexicographically smallest)
+         * value and drops the rest, so only "env=prod" remains visible. */
+        ASSERT_OK(write_string_file(f, "TAGS=\"env=staging:env=prod\"\n",
+                                    WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE));
+        ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "env=prod", /* trigger= */ false, /* negate= */ false)));
+        ASSERT_OK_POSITIVE(condition_test(condition, environ));
+        condition_free(condition);
+        ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "env=staging", /* trigger= */ false, /* negate= */ false)));
+        ASSERT_OK_ZERO(condition_test(condition, environ));
+        condition_free(condition);
+
+        /* Invalid key/value tags in the file are ignored, valid ones still match */
+        ASSERT_OK(write_string_file(f, "TAGS=\"bad-=x:role=good\"\n",
+                                    WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE));
+        ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "bad-=x", /* trigger= */ false, /* negate= */ false)));
+        ASSERT_OK_ZERO(condition_test(condition, environ));
+        condition_free(condition);
+        ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "role=good", /* trigger= */ false, /* negate= */ false)));
+        ASSERT_OK_POSITIVE(condition_test(condition, environ));
+        condition_free(condition);
+
         ASSERT_OK(set_unset_env("SYSTEMD_ETC_MACHINE_INFO", saved, /* overwrite= */ true));
 }
 
index 8ec4034af7fb5422fb1058195b80bcc84cfc5ecc..513c8edc9b83cdd4a314d48b7d74ec9e9616adbe 100644 (file)
@@ -124,6 +124,15 @@ TEST(machine_tag_is_valid) {
         assert_se(machine_tag_is_valid("foo-bar.baz"));
         assert_se(machine_tag_is_valid("Webserver01"));
         assert_se(machine_tag_is_valid("a"));
+        assert_se(machine_tag_is_valid("a="));         /* empty value is OK */
+        assert_se(machine_tag_is_valid("a=b"));
+        assert_se(machine_tag_is_valid("foo.bar="));
+        assert_se(machine_tag_is_valid("foo.bar-baz=zuziuziuz"));
+        assert_se(machine_tag_is_valid("foo=bar.baz")); /* "." and "-" are fine inside a value */
+        assert_se(machine_tag_is_valid("foo=bar-"));    /* even as the very last char of a value */
+        assert_se(machine_tag_is_valid("foo=.bar"));    /* and as the very first char of a value */
+        assert_se(machine_tag_is_valid("foo=bar="));    /* a value may itself contain a "=" */
+        assert_se(machine_tag_is_valid("a=b=c"));       /* only the first "=" is the separator */
 
         assert_se(!machine_tag_is_valid(NULL));
         assert_se(!machine_tag_is_valid(""));
@@ -136,6 +145,15 @@ TEST(machine_tag_is_valid) {
         assert_se(!machine_tag_is_valid("foo-"));
         assert_se(!machine_tag_is_valid(".foo"));
         assert_se(!machine_tag_is_valid("foo."));
+        assert_se(!machine_tag_is_valid("=b"));
+        assert_se(!machine_tag_is_valid("="));
+        assert_se(!machine_tag_is_valid(".foo=asd"));  /* "." not allowed as first char */
+        assert_se(!machine_tag_is_valid("foo.=asd"));  /* "." not allowed as last char of key */
+        assert_se(!machine_tag_is_valid("foo-=asd"));  /* "-" not allowed as last char of key */
+        assert_se(!machine_tag_is_valid("_foo=asd"));  /* "_" is not in the charset */
+        assert_se(!machine_tag_is_valid("foo_=sda"));
+        assert_se(!machine_tag_is_valid("foo=a_b"));   /* ... not even in the value */
+        assert_se(!machine_tag_is_valid("foo=a:b"));   /* colon is the separator, not allowed in a value */
 
         /* Length boundary: 255 characters is fine, 256 is too long */
         _cleanup_free_ char *max = strrep("a", 255), *over = strrep("a", 256);
@@ -149,9 +167,18 @@ TEST(machine_tag_list_is_valid) {
         assert_se(machine_tag_list_is_valid(NULL));    /* empty list is valid */
         assert_se(machine_tag_list_is_valid(STRV_MAKE("a")));
         assert_se(machine_tag_list_is_valid(STRV_MAKE("foo", "bar", "c-d.e")));
+        assert_se(machine_tag_list_is_valid(STRV_MAKE("foo=uuu", "bar=qqqq", "c-d.e")));
+        assert_se(machine_tag_list_is_valid(STRV_MAKE("foo=aa", "foo=aa")));     /* same key + same value is OK */
+        assert_se(machine_tag_list_is_valid(STRV_MAKE("foo", "foo=aa")));        /* bare key and assignment coexist */
+        assert_se(machine_tag_list_is_valid(STRV_MAKE("foo=1", "foobar=2")));    /* one key is a prefix of the other */
+        assert_se(machine_tag_list_is_valid(STRV_MAKE("ab=1", "a=2")));          /* ... and the other way around */
 
         assert_se(!machine_tag_list_is_valid(STRV_MAKE("foo", "b:c")));
         assert_se(!machine_tag_list_is_valid(STRV_MAKE("foo", "")));
+        assert_se(!machine_tag_list_is_valid(STRV_MAKE("foo=aa", "foo=b")));     /* same key, different value */
+        assert_se(!machine_tag_list_is_valid(STRV_MAKE("a=1", "b=2", "a=3")));   /* ... also when not adjacent */
+        assert_se(!machine_tag_list_is_valid(STRV_MAKE("foo=aa", "bar", "foo=aa", "foo=b")));
+        assert_se(!machine_tag_list_is_valid(STRV_MAKE("=aa")));
 }
 
 TEST(machine_tags_from_string) {
@@ -183,6 +210,30 @@ TEST(machine_tags_from_string) {
         /* Fatal: a single invalid tag fails the whole parse */
         ASSERT_ERROR(machine_tags_from_string("foo:in valid:bar", /* graceful= */ false, &l), EINVAL);
         assert_se(!l);
+
+        /* With assignment */
+        ASSERT_OK(machine_tags_from_string("foo=aa:bar=aaa:foo2=x:baz", /* graceful= */ false, &l));
+        assert_se(strv_equal(l, STRV_MAKE("bar=aaa", "baz", "foo2=x", "foo=aa")));
+        l = strv_free(l);
+
+        /* Graceful: a duplicate key is suppressed, keeping the first (i.e. lexicographically smallest) value */
+        ASSERT_OK(machine_tags_from_string("foo=zzz:foo=aaa:foo=mmm", /* graceful= */ true, &l));
+        assert_se(strv_equal(l, STRV_MAKE("foo=aaa")));
+        l = strv_free(l);
+
+        /* Graceful: a bare key and an assignment for the same name are not considered duplicates */
+        ASSERT_OK(machine_tags_from_string("foo:foo=aaa", /* graceful= */ true, &l));
+        assert_se(strv_equal(l, STRV_MAKE("foo", "foo=aaa")));
+        l = strv_free(l);
+
+        /* Graceful: an invalid value is dropped, conflicting keys that remain are deduplicated */
+        ASSERT_OK(machine_tags_from_string("foo=a_b:foo=good:foo=zzz", /* graceful= */ true, &l));
+        assert_se(strv_equal(l, STRV_MAKE("foo=good")));
+        l = strv_free(l);
+
+        /* Fatal: conflicting values for the same key fail the whole parse */
+        ASSERT_ERROR(machine_tags_from_string("foo=a:foo=b", /* graceful= */ false, &l), EINVAL);
+        assert_se(!l);
 }
 
 DEFINE_TEST_MAIN(LOG_DEBUG);