diff options
| author | Denis V. Lunev <den@openvz.org> | 2026-05-20 23:26:28 +0200 |
|---|---|---|
| committer | Michael Tokarev <mjt@tls.msk.ru> | 2026-06-04 22:57:10 +0300 |
| commit | 3c03b500761097cc9c0dda5d7f1404c07e02148d (patch) | |
| tree | 78fee9d4833badb4ac277b8a01bfd64ba7a06713 | |
| parent | 69a43064dc74ecc0a79745f9dd48a0bdb7c00d9d (diff) | |
| download | qemu-3c03b500761097cc9c0dda5d7f1404c07e02148d.tar.gz qemu-3c03b500761097cc9c0dda5d7f1404c07e02148d.zip | |
tests/unit: add test-envlist covering setenv/unsetenv name matching
util/envlist had no test coverage. Add tests/unit/test-envlist
exercising the public envlist API and pinning down the prefix-match
hazard fixed in the previous commit:
- envlist_unsetenv("FOO") must not remove an entry named "FOOBAR";
- envlist_setenv("FOO=...") must not replace an existing "FOOBAR=..."
entry placed earlier in the list (envlist_setenv() inserts at the
head, so the first prefix match wins under the old strncmp rule).
Also cover the rest of the contract: head-insertion order observed
through envlist_to_environ(), replacement of an existing variable,
the count argument of envlist_to_environ(), and the documented EINVAL
paths (NULL inputs, setenv without '=', unsetenv with '=').
Signed-off-by: Denis V. Lunev <den@openvz.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Message-id: 20260520212628.479772-3-den@openvz.org
Cc: Stefan Hajnoczi <stefanha@redhat.com>
Cc: Markus Armbruster <armbru@redhat.com>
Cc: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
(cherry picked from commit 05221c600a5f3ef657d71aeaea632c5f1bab3a2d)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
| -rw-r--r-- | tests/unit/meson.build | 1 | ||||
| -rw-r--r-- | tests/unit/test-envlist.c | 196 |
2 files changed, 197 insertions, 0 deletions
diff --git a/tests/unit/meson.build b/tests/unit/meson.build index d5248ae51d..1a85f85d18 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -48,6 +48,7 @@ tests = { 'test-qapi-util': [], 'test-interval-tree': [], 'test-fifo': [], + 'test-envlist': [], } if have_system or have_tools diff --git a/tests/unit/test-envlist.c b/tests/unit/test-envlist.c new file mode 100644 index 0000000000..53813dd4de --- /dev/null +++ b/tests/unit/test-envlist.c @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * envlist tests + * + * Copyright 2026 Virtuozzo International GmbH + * + * Authors: + * Denis V. Lunev <den@openvz.org> + */ + +#include "qemu/osdep.h" +#include "qemu/envlist.h" + +static void free_environ(char **env) +{ + char **p; + + for (p = env; *p != NULL; p++) { + g_free(*p); + } + g_free(env); +} + +static const char *find_env(char **env, const char *name) +{ + size_t name_len = strlen(name); + char **p; + + for (p = env; *p != NULL; p++) { + if (strncmp(*p, name, name_len) == 0 && (*p)[name_len] == '=') { + return *p + name_len + 1; + } + } + return NULL; +} + +static void test_envlist_basic(void) +{ + envlist_t *el = envlist_create(); + char **env; + size_t count; + + /* empty list */ + env = envlist_to_environ(el, &count); + g_assert_cmpuint(count, ==, 0); + g_assert_null(env[0]); + free_environ(env); + + /* add */ + g_assert_cmpint(envlist_setenv(el, "A=1"), ==, 0); + g_assert_cmpint(envlist_setenv(el, "B=2"), ==, 0); + + env = envlist_to_environ(el, &count); + g_assert_cmpuint(count, ==, 2); + g_assert_cmpstr(find_env(env, "A"), ==, "1"); + g_assert_cmpstr(find_env(env, "B"), ==, "2"); + free_environ(env); + + /* replace */ + g_assert_cmpint(envlist_setenv(el, "A=42"), ==, 0); + env = envlist_to_environ(el, &count); + g_assert_cmpuint(count, ==, 2); + g_assert_cmpstr(find_env(env, "A"), ==, "42"); + g_assert_cmpstr(find_env(env, "B"), ==, "2"); + free_environ(env); + + /* unset existing */ + g_assert_cmpint(envlist_unsetenv(el, "A"), ==, 0); + env = envlist_to_environ(el, &count); + g_assert_cmpuint(count, ==, 1); + g_assert_null(find_env(env, "A")); + g_assert_cmpstr(find_env(env, "B"), ==, "2"); + free_environ(env); + + /* unset non-existing is a no-op success */ + g_assert_cmpint(envlist_unsetenv(el, "NOPE"), ==, 0); + env = envlist_to_environ(el, &count); + g_assert_cmpuint(count, ==, 1); + free_environ(env); + + envlist_free(el); +} + +/* + * envlist_setenv() inserts at the head; envlist_to_environ() walks + * head-to-tail, so the last setenv comes out first. + */ +static void test_envlist_head_insertion_order(void) +{ + envlist_t *el = envlist_create(); + char **env; + size_t count; + + g_assert_cmpint(envlist_setenv(el, "A=1"), ==, 0); + g_assert_cmpint(envlist_setenv(el, "B=2"), ==, 0); + g_assert_cmpint(envlist_setenv(el, "C=3"), ==, 0); + + env = envlist_to_environ(el, &count); + g_assert_cmpuint(count, ==, 3); + g_assert_cmpstr(env[0], ==, "C=3"); + g_assert_cmpstr(env[1], ==, "B=2"); + g_assert_cmpstr(env[2], ==, "A=1"); + g_assert_null(env[3]); + + free_environ(env); + envlist_free(el); +} + +static void test_envlist_einval(void) +{ + envlist_t *el = envlist_create(); + + /* NULL list */ + g_assert_cmpint(envlist_setenv(NULL, "A=1"), ==, EINVAL); + g_assert_cmpint(envlist_unsetenv(NULL, "A"), ==, EINVAL); + + /* NULL string */ + g_assert_cmpint(envlist_setenv(el, NULL), ==, EINVAL); + g_assert_cmpint(envlist_unsetenv(el, NULL), ==, EINVAL); + + /* setenv: missing '=' */ + g_assert_cmpint(envlist_setenv(el, "NOEQ"), ==, EINVAL); + + /* unsetenv: name must not contain '=' */ + g_assert_cmpint(envlist_unsetenv(el, "A=B"), ==, EINVAL); + + envlist_free(el); +} + +/* + * Regression: envlist_unsetenv("FOO") must not remove an entry named + * "FOOBAR" -- the previous strncmp(entry, name, strlen(name)) lookup + * prefix-matched. To trigger the bug, the longer-named entry has to + * be ahead of the target in the list: envlist_setenv() inserts at + * the head, so we add FOO first and FOOBAR last. + */ +static void test_envlist_unsetenv_no_prefix_match(void) +{ + envlist_t *el = envlist_create(); + char **env; + size_t count; + + g_assert_cmpint(envlist_setenv(el, "FOO=y"), ==, 0); + g_assert_cmpint(envlist_setenv(el, "FOOBAR=x"), ==, 0); + + g_assert_cmpint(envlist_unsetenv(el, "FOO"), ==, 0); + + env = envlist_to_environ(el, &count); + g_assert_cmpuint(count, ==, 1); + g_assert_cmpstr(find_env(env, "FOOBAR"), ==, "x"); + g_assert_null(find_env(env, "FOO")); + + free_environ(env); + envlist_free(el); +} + +/* + * envlist_setenv() must not replace a prior FOOBAR=... entry when + * setting FOO=... The pre-fix code happened to be safe here only + * because it included the trailing '=' byte in its strncmp length; + * this test pins down the post-fix contract that the name boundary + * is a property of the entry, not of the encoded form. + */ +static void test_envlist_setenv_no_prefix_match(void) +{ + envlist_t *el = envlist_create(); + char **env; + size_t count; + + g_assert_cmpint(envlist_setenv(el, "FOOBAR=x"), ==, 0); + g_assert_cmpint(envlist_setenv(el, "FOO=y"), ==, 0); + + env = envlist_to_environ(el, &count); + g_assert_cmpuint(count, ==, 2); + g_assert_cmpstr(find_env(env, "FOOBAR"), ==, "x"); + g_assert_cmpstr(find_env(env, "FOO"), ==, "y"); + + free_environ(env); + envlist_free(el); +} + +int main(int argc, char *argv[]) +{ + g_test_init(&argc, &argv, NULL); + + g_test_add_func("/envlist/basic", test_envlist_basic); + g_test_add_func("/envlist/head_insertion_order", + test_envlist_head_insertion_order); + g_test_add_func("/envlist/einval", test_envlist_einval); + g_test_add_func("/envlist/unsetenv_no_prefix_match", + test_envlist_unsetenv_no_prefix_match); + g_test_add_func("/envlist/setenv_no_prefix_match", + test_envlist_setenv_no_prefix_match); + + return g_test_run(); +} |
