Files
crun/tests/tests_libcrun_utils.c
Giuseppe Scrivano 3ef2a77fa3 tests: add more utils unit tests for coverage
Add additional unit tests for utils.c to improve coverage:
- test_has_prefix: Test has_prefix string function
- test_has_suffix: Test has_suffix string function
- test_str_join_array: Test str_join_array with various inputs
- test_get_current_timestamp: Test timestamp generation
- test_crun_ensure_directory: Test directory creation/existence

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2025-12-22 07:22:13 +00:00

679 lines
16 KiB
C

/*
* crun - OCI runtime written in C
*
* Copyright (C) 2017, 2018, 2019 Giuseppe Scrivano <giuseppe@scrivano.org>
* crun is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* crun 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with crun. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libcrun/error.h>
#include <libcrun/utils.h>
#include <libcrun/cgroup.h>
#include <libcrun/cgroup-systemd.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
typedef int (*test) ();
extern int cpuset_string_to_bitmask (const char *str, char **out, size_t *out_size, libcrun_error_t *err);
static int
test_socket_pair ()
{
libcrun_error_t err = NULL;
int fds[2];
__attribute__ ((unused)) cleanup_close int fd0 = -1;
__attribute__ ((unused)) cleanup_close int fd1 = -1;
char buffer[256];
int ret = create_socket_pair (fds, &err);
if (ret < 0)
return -1;
fd0 = fds[0];
fd1 = fds[1];
if (err != NULL)
return -1;
ret = write (fds[0], "HELLO", 6);
if (ret != 6)
return -1;
ret = read (fds[1], buffer, sizeof (buffer));
if (ret != 6)
return -1;
if (strcmp (buffer, "HELLO") != 0)
return -1;
ret = write (fds[1], "WORLD", 6);
if (ret != 6)
return -1;
ret = read (fds[0], buffer, sizeof (buffer));
if (ret != 6)
return -1;
if (strcmp (buffer, "WORLD") != 0)
return -1;
return 0;
}
static int
test_send_receive_fd ()
{
libcrun_error_t err = NULL;
int fds[2], pipes[2];
cleanup_close int fd0 = -1;
cleanup_close int fd1 = -1;
pid_t pid;
int ret = create_socket_pair (fds, &err);
if (ret < 0)
return -1;
fd0 = fds[0];
fd1 = fds[1];
if (err != NULL)
return -1;
pid = fork ();
if (pid < 0)
return -1;
if (pid)
{
cleanup_close int pipefd0 = -1;
cleanup_close int pipefd1 = -1;
char buffer[256];
const char *test_string = "TEST STRING";
if (pipe (pipes) < 0)
return -1;
pipefd0 = pipes[0];
pipefd1 = pipes[1];
close (fd0);
fd0 = -1;
if (send_fd_to_socket (fd1, pipefd0, &err) < 0)
return -1;
if (write (pipefd1, test_string, strlen (test_string) + 1) < 0)
return -1;
ret = read (fd1, buffer, sizeof (buffer));
if (ret < 0)
return -1;
if (ret != (int) strlen (test_string) + 1)
return -1;
return strcmp (buffer, test_string);
}
else
{
char buffer[256];
int ret, fd = receive_fd_from_socket (fd0, &err);
if (fd < 0)
_exit (1);
close (fd1);
fd1 = -1;
ret = read (fd, buffer, sizeof (buffer));
if (ret <= 0)
return -1;
if (write (fd0, buffer, ret) < 0)
return -1;
_exit (0);
}
return 0;
}
static int
test_run_process ()
{
libcrun_error_t err = NULL;
{
char *args[] = { "/bin/true", NULL };
if (run_process (args, &err) != 0)
return -1;
}
{
char *args[] = { "/bin/false", NULL };
if (run_process (args, &err) <= 0)
return -1;
if (err != NULL)
return -1;
}
{
char *args[] = { "/does/not/exist", NULL };
int r = run_process (args, &err);
if (r <= 0)
return -1;
if (err != NULL)
return -1;
}
return 0;
}
static int
test_dir_p ()
{
libcrun_error_t err;
if (crun_dir_p ("/usr", false, &err) <= 0)
return -1;
if (crun_dir_p ("/dev/zero", false, &err) != 0)
return -1;
if (crun_dir_p ("/hopefully/does/not/really/exist", false, &err) >= 0)
return -1;
return 0;
}
static int
test_write_read_file ()
{
libcrun_error_t err = NULL;
cleanup_free char *name = NULL;
size_t len;
size_t i;
int ret, failed = 0;
size_t max = 1 << 10;
cleanup_free char *written = xmalloc (max);
xasprintf (&name, "tests/write-file-%i", getpid ());
for (i = 0; i < max; i++)
written[i] = i;
for (i = 1; i <= max; i *= 2)
{
cleanup_free char *read_buf = NULL;
ret = write_file (name, written, i, &err);
if (ret < 0)
{
failed = 1;
break;
}
ret = read_all_file (name, &read_buf, &len, &err);
if (ret < 0)
{
failed = 1;
break;
}
if (len != i)
{
failed = 1;
break;
}
if (memcmp (read_buf, written, len) != 0)
{
failed = 1;
break;
}
}
unlink (name);
return failed ? -1 : 0;
}
static int
test_crun_path_exists ()
{
libcrun_error_t err = NULL;
if (crun_path_exists ("/dev/null", &err) <= 0)
return -1;
if (crun_path_exists ("/usr/foo/bin/ls", &err) != 0)
return -1;
if (crun_path_exists ("/proc/sysrq-trigger", &err) != 1)
return -1;
return 0;
}
static int
test_path_is_slash_dev ()
{
if (! path_is_slash_dev ("/dev"))
return -1;
if (! path_is_slash_dev ("/dev//"))
return -1;
if (! path_is_slash_dev ("///dev///"))
return -1;
if (! path_is_slash_dev ("/dev/"))
return -1;
if (! path_is_slash_dev ("dev"))
return -1;
if (! path_is_slash_dev ("dev////"))
return -1;
if (path_is_slash_dev ("dev////foo"))
return -1;
if (path_is_slash_dev ("/dev/foo"))
return -1;
if (path_is_slash_dev ("/dev/foo/"))
return -1;
if (path_is_slash_dev ("/dev/foo/bar"))
return -1;
if (path_is_slash_dev ("///dev/foo//bar"))
return -1;
if (path_is_slash_dev ("///dev/foo/////"))
return -1;
return 0;
}
static int
test_append_paths ()
{
#define PROLOGUE() \
cleanup_free char *out = NULL; \
libcrun_error_t err = NULL; \
int ret;
#define EXPECT_STRING(exp) \
{ \
if (ret < 0 || out == NULL) \
{ \
crun_error_release (&err); \
return ret; \
} \
if (strcmp (out, exp)) \
return -1; \
}
{
PROLOGUE ();
ret = append_paths (&out, &err, "/sys/fs/cgroup/", "memory", "some/path", NULL);
EXPECT_STRING ("/sys/fs/cgroup/memory/some/path");
}
{
PROLOGUE ();
ret = append_paths (&out, &err, "/sys/fs/cgroup", "memory", "some/path", NULL);
EXPECT_STRING ("/sys/fs/cgroup/memory/some/path");
}
{
PROLOGUE ();
ret = append_paths (&out, &err, "/sys/fs/cgroup////////", "memory////////", "some/path//////", NULL);
EXPECT_STRING ("/sys/fs/cgroup/memory/some/path");
}
{
PROLOGUE ();
ret = append_paths (&out, &err, "/sys/fs/cgroup////////", "memory////////", "///////some/path//////", NULL);
EXPECT_STRING ("/sys/fs/cgroup/memory/some/path");
}
{
PROLOGUE ();
ret = append_paths (&out, &err, "", "//", "", "", "", NULL);
EXPECT_STRING ("/");
}
{
PROLOGUE ();
ret = append_paths (&out, &err, "///", "/", "", "///", "a", NULL);
EXPECT_STRING ("/a");
}
{
PROLOGUE ();
ret = append_paths (&out, &err, "////", "/////", "///", "", "", NULL);
EXPECT_STRING ("/");
}
{
PROLOGUE ();
ret = append_paths (&out, &err, "", "", "", "", "", NULL);
EXPECT_STRING ("");
}
{
PROLOGUE ();
ret = append_paths (&out, &err, "", "///sys/fs/cgroup////////", "", "some/path", NULL);
EXPECT_STRING ("/sys/fs/cgroup/some/path");
}
{
PROLOGUE ();
ret = append_paths (&out, &err, "a", "b", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d",
"a", "b", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d",
"a", "b", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d", NULL);
if (ret == 0)
return -1;
crun_error_release (&err);
}
return 0;
}
#ifdef HAVE_SYSTEMD
static int
test_parse_sd_array ()
{
char *out, *next;
libcrun_error_t err = NULL;
{
cleanup_free char *s = xstrdup (" 'firewalld.service' ");
if (parse_sd_array (s, &out, &next, &err) < 0)
{
crun_error_release (&err);
return -1;
}
if (strcmp (out, "firewalld.service"))
return -1;
if (next != NULL)
return -1;
}
{
cleanup_free char *s = xstrdup ("'fir\\ewalld.ser\\vi\\ce']");
if (parse_sd_array (s, &out, &next, &err) < 0)
{
crun_error_release (&err);
return -1;
}
if (strcmp (out, "firewalld.service"))
return -1;
if (next != NULL)
return -1;
}
{
cleanup_free char *s = xstrdup ("'fi\\rew\\alld.s\\erv\\ice', 'foo.service']");
if (parse_sd_array (s, &out, &next, &err) < 0)
{
crun_error_release (&err);
return -1;
}
if (strcmp (out, "firewalld.service"))
return -1;
if (strcmp (next, " 'foo.service']"))
return -1;
}
return 0;
}
static int
test_get_scope_path ()
{
# define CHECK(x, y) \
{ \
cleanup_free char *r = x; \
if (strcmp (r, y)) \
{ \
fprintf (stderr, "expected %s, got %s\n", y, r); \
return -1; \
} \
}
CHECK (get_cgroup_scope_path ("foo.scope", "foo.scope"), "foo.scope");
CHECK (get_cgroup_scope_path ("/foo/bar/user.slice/foo.scope/a/b/c", "foo.scope"), "/foo/bar/user.slice/foo.scope");
CHECK (get_cgroup_scope_path ("/foo/bar-foo.scope/user.slice/foo.scope/a/b/c", "foo.scope"), "/foo/bar-foo.scope/user.slice/foo.scope");
CHECK (get_cgroup_scope_path ("/foo/foo.scope-bar/user.slice/foo.scope/a/b/c", "foo.scope"), "/foo/foo.scope-bar/user.slice/foo.scope");
CHECK (get_cgroup_scope_path ("////foo.scope", "foo.scope"), "////foo.scope");
# undef CHECK
return 0;
}
static int
test_cpuset_string_to_bitmask ()
{
libcrun_error_t err = NULL;
cleanup_free char *mask = NULL;
size_t mask_size;
if (cpuset_string_to_bitmask ("0", &mask, &mask_size, &err) < 0)
return -1;
if (mask_size != 1 || mask[0] != 0x01)
return -1;
free (mask);
mask = NULL;
if (cpuset_string_to_bitmask ("0-1", &mask, &mask_size, &err) < 0)
return -1;
if (mask_size != 1 || mask[0] != 0x03)
return -1;
free (mask);
mask = NULL;
if (cpuset_string_to_bitmask ("0,2", &mask, &mask_size, &err) < 0)
return -1;
if (mask_size != 1 || mask[0] != 0x05)
return -1;
free (mask);
mask = NULL;
if (cpuset_string_to_bitmask ("0-2", &mask, &mask_size, &err) < 0)
return -1;
if (mask_size != 1 || mask[0] != 0x07)
return -1;
free (mask);
mask = NULL;
if (cpuset_string_to_bitmask ("0,8", &mask, &mask_size, &err) < 0)
return -1;
if (mask_size != 2 || mask[0] != 0x01 || mask[1] != 0x01)
return -1;
free (mask);
mask = NULL;
if (cpuset_string_to_bitmask ("a", &mask, &mask_size, &err) == 0)
return -1;
if (cpuset_string_to_bitmask ("-1", &mask, &mask_size, &err) == 0)
return -1;
if (cpuset_string_to_bitmask ("0-", &mask, &mask_size, &err) == 0)
return -1;
if (cpuset_string_to_bitmask ("0-2,4-6", &mask, &mask_size, &err) < 0)
return -1;
if (mask_size != 1 || mask[0] != 0x77)
return -1;
free (mask);
mask = NULL;
return 0;
}
#endif
static int
test_has_prefix ()
{
if (! has_prefix ("hello world", "hello"))
return -1;
if (! has_prefix ("hello", "hello"))
return -1;
if (! has_prefix ("hello", ""))
return -1;
if (has_prefix ("hello", "world"))
return -1;
if (has_prefix ("hello", "hello world"))
return -1;
if (has_prefix ("", "hello"))
return -1;
return 0;
}
static int
test_has_suffix ()
{
if (! has_suffix ("hello world", "world"))
return -1;
if (! has_suffix ("world", "world"))
return -1;
if (! has_suffix ("hello", ""))
return -1;
if (has_suffix ("hello", "world"))
return -1;
if (has_suffix ("world", "hello world"))
return -1;
if (has_suffix ("", "hello"))
return -1;
return 0;
}
static int
test_str_join_array ()
{
{
char *array[] = { "a", "b", "c", NULL };
cleanup_free char *result = str_join_array (0, 3, array, ",");
if (result == NULL)
return -1;
if (strcmp (result, "a,b,c") != 0)
return -1;
}
{
char *array[] = { "hello", "world", NULL };
cleanup_free char *result = str_join_array (0, 2, array, " ");
if (result == NULL)
return -1;
if (strcmp (result, "hello world") != 0)
return -1;
}
{
char *array[] = { "single", NULL };
cleanup_free char *result = str_join_array (0, 1, array, ",");
if (result == NULL)
return -1;
if (strcmp (result, "single") != 0)
return -1;
}
{
/* Test with offset - size is the end index (exclusive) */
char *array[] = { "skip", "a", "b", NULL };
cleanup_free char *result = str_join_array (1, 3, array, "-");
if (result == NULL)
return -1;
if (strcmp (result, "a-b") != 0)
return -1;
}
return 0;
}
static int
test_get_current_timestamp ()
{
char buf[64];
get_current_timestamp (buf, sizeof (buf));
/* Check that timestamp is not empty */
if (buf[0] == '\0')
return -1;
/* Check that timestamp has expected format: starts with year (4 digits) */
if (buf[0] != '2') /* Year should start with 2 for 2xxx */
return -1;
/* Check that timestamp contains T separator (ISO 8601 format) */
if (strchr (buf, 'T') == NULL && strchr (buf, '-') == NULL)
return -1;
return 0;
}
static int
test_crun_ensure_directory ()
{
libcrun_error_t err = NULL;
cleanup_free char *path = NULL;
int ret;
xasprintf (&path, "/tmp/crun-test-dir-%d", getpid ());
/* Create directory */
ret = crun_ensure_directory (path, 0755, false, &err);
if (ret < 0)
{
crun_error_release (&err);
return -1;
}
/* Verify it's a directory */
ret = crun_dir_p (path, false, &err);
if (ret <= 0)
{
rmdir (path);
crun_error_release (&err);
return -1;
}
/* Creating again should succeed (already exists) */
ret = crun_ensure_directory (path, 0755, false, &err);
if (ret < 0)
{
rmdir (path);
crun_error_release (&err);
return -1;
}
/* Cleanup */
rmdir (path);
return 0;
}
static void
run_and_print_test_result (const char *name, int id, test t)
{
int ret = t ();
if (ret == 0)
printf ("ok %d - %s\n", id, name);
else if (ret == 77)
printf ("ok %d - %s #SKIP\n", id, name);
else
printf ("not ok %d - %s\n", id, name);
}
#define RUN_TEST(T) \
do \
{ \
run_and_print_test_result (#T, id++, T); \
} while (0)
int
main ()
{
int id = 1;
#ifdef HAVE_SYSTEMD
printf ("1..16\n");
#else
printf ("1..13\n");
#endif
RUN_TEST (test_crun_path_exists);
RUN_TEST (test_write_read_file);
RUN_TEST (test_run_process);
RUN_TEST (test_dir_p);
RUN_TEST (test_socket_pair);
RUN_TEST (test_send_receive_fd);
RUN_TEST (test_append_paths);
RUN_TEST (test_path_is_slash_dev);
RUN_TEST (test_has_prefix);
RUN_TEST (test_has_suffix);
RUN_TEST (test_str_join_array);
RUN_TEST (test_get_current_timestamp);
RUN_TEST (test_crun_ensure_directory);
#ifdef HAVE_SYSTEMD
RUN_TEST (test_parse_sd_array);
RUN_TEST (test_get_scope_path);
RUN_TEST (test_cpuset_string_to_bitmask);
#endif
return 0;
}