mirror of
https://github.com/qemu/qemu.git
synced 2026-07-08 17:46:10 +00:00
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>
This commit is contained in:
committed by
Stefan Hajnoczi
parent
c131ae56c1
commit
05221c600a
@@ -49,6 +49,7 @@ tests = {
|
||||
'test-qapi-util': [],
|
||||
'test-interval-tree': [],
|
||||
'test-fifo': [],
|
||||
'test-envlist': [],
|
||||
}
|
||||
|
||||
if have_system or have_tools
|
||||
|
||||
196
tests/unit/test-envlist.c
Normal file
196
tests/unit/test-envlist.c
Normal file
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user