coreutils / tests /mknod /tests_for_usage.c
AryaWu's picture
Upload folder using huggingface_hub
78d2150 verified
#include "../../unity/unity.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#include <locale.h>
#include <stdio.h>
#include <signal.h>
/* program_name is declared by coreutils headers included before this test */
extern char const *program_name;
/* Helper: read all data from fd into a dynamically-allocated buffer.
Returns 0 on success, -1 on error. On success, *buf points to malloc'd
memory (NUL-terminated for convenience) and *len to its length. */
static int read_all_fd(int fd, char **buf, size_t *len)
{
size_t cap = 1024;
size_t used = 0;
char *out = (char *)malloc(cap);
if (!out) return -1;
for (;;)
{
if (used == cap)
{
size_t ncap = cap * 2;
char *tmp = (char *)realloc(out, ncap);
if (!tmp) { free(out); return -1; }
out = tmp;
cap = ncap;
}
ssize_t n = read(fd, out + used, cap - used);
if (n > 0)
{
used += (size_t)n;
continue;
}
if (n == 0)
break;
if (errno == EINTR)
continue;
free(out);
return -1;
}
/* NUL-terminate for strstr convenience */
if (used == cap)
{
char *tmp = (char *)realloc(out, cap + 1);
if (!tmp) { free(out); return -1; }
out = tmp;
}
out[used] = '\0';
*buf = out;
*len = used;
return 0;
}
/* Helper: create a pipe with CLOEXEC if available, else set it manually. */
static int make_pipe_cloexec(int p[2])
{
#ifdef O_CLOEXEC
if (pipe2(p, O_CLOEXEC) == 0)
return 0;
if (errno != ENOSYS && errno != EINVAL)
return -1;
#endif
if (pipe(p) != 0)
return -1;
/* set CLOEXEC on both ends */
for (int i = 0; i < 2; i++)
{
int flags = fcntl(p[i], F_GETFD);
if (flags >= 0)
fcntl(p[i], F_SETFD, flags | FD_CLOEXEC);
}
return 0;
}
/* Run usage(status) in a child, capturing its stdout and stderr.
On success returns 0 and populates outputs (caller must free *out and *err).
On failure returns -1. */
static int run_usage_and_capture(int status,
char **out, size_t *out_len,
char **err, size_t *err_len,
int *exit_code)
{
int out_p[2] = {-1,-1};
int err_p[2] = {-1,-1};
if (make_pipe_cloexec(out_p) != 0) return -1;
if (make_pipe_cloexec(err_p) != 0)
{
close(out_p[0]); close(out_p[1]);
return -1;
}
fflush(stdout);
fflush(stderr);
pid_t pid = fork();
if (pid < 0)
{
close(out_p[0]); close(out_p[1]);
close(err_p[0]); close(err_p[1]);
return -1;
}
if (pid == 0)
{
/* Child: redirect stdout/stderr to pipes */
/* Avoid stdio buffering issues by setting line buffering or unbuffered */
/* But since we dup the fds, exit() will flush appropriately */
if (dup2(out_p[1], STDOUT_FILENO) < 0) _exit(127);
if (dup2(err_p[1], STDERR_FILENO) < 0) _exit(127);
close(out_p[0]); close(out_p[1]);
close(err_p[0]); close(err_p[1]);
/* Ensure program_name has a value */
if (!program_name || program_name[0] == '\0')
program_name = "mknod";
/* Call the function under test; it should not return. */
usage(status);
/* If it returns, exit with sentinel code. */
_exit(123);
}
/* Parent */
close(out_p[1]);
close(err_p[1]);
char *out_buf = NULL, *err_buf = NULL;
size_t out_sz = 0, err_sz = 0;
int r1 = read_all_fd(out_p[0], &out_buf, &out_sz);
int r2 = read_all_fd(err_p[0], &err_buf, &err_sz);
close(out_p[0]);
close(err_p[0]);
int wstatus = 0;
pid_t w = -1;
do {
w = waitpid(pid, &wstatus, 0);
} while (w < 0 && errno == EINTR);
if (r1 != 0 || r2 != 0 || w < 0)
{
free(out_buf);
free(err_buf);
return -1;
}
int code = -1;
if (WIFEXITED(wstatus))
code = WEXITSTATUS(wstatus);
else if (WIFSIGNALED(wstatus))
code = 128 + WTERMSIG(wstatus);
*out = out_buf; *out_len = out_sz;
*err = err_buf; *err_len = err_sz;
*exit_code = code;
return 0;
}
/* Helper: check if buffer contains substring (ASCII) */
static int buf_contains(const char *buf, const char *needle)
{
if (!buf || !needle) return 0;
return strstr(buf, needle) != NULL;
}
void setUp(void) {
/* Force C locale for deterministic messages */
setenv("LC_ALL", "C", 1);
setlocale(LC_ALL, "C");
/* Ensure program_name is set consistently for messages */
if (!program_name || program_name[0] == '\0')
program_name = "mknod";
else
program_name = "mknod";
}
void tearDown(void) {
/* Nothing to cleanup */
}
static void free_captured(char *out, char *err)
{
free(out);
free(err);
}
void test_usage_success_outputs_usage_and_exits_zero(void)
{
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int code = -1;
int rc = run_usage_and_capture(EXIT_SUCCESS, &out, &out_len, &err, &err_len, &code);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "run_usage_and_capture failed");
/* Check exit code */
TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, code);
/* stderr should be empty for success usage */
TEST_ASSERT_TRUE_MESSAGE(err_len == 0 || (err && err[0] == '\0'),
"Expected no stderr output for successful usage()");
/* stdout should contain usage header and key content */
TEST_ASSERT_TRUE_MESSAGE(buf_contains(out, "Usage: mknod"),
"stdout should contain 'Usage: mknod'");
TEST_ASSERT_TRUE_MESSAGE(buf_contains(out, "TYPE may be"),
"stdout should mention 'TYPE may be'");
TEST_ASSERT_TRUE_MESSAGE(buf_contains(out, "block (buffered) special file"),
"stdout should describe block special file");
free_captured(out, err);
}
void test_usage_nonzero_status_emits_try_help_and_exits_status(void)
{
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int code = -1;
int wanted = 2;
int rc = run_usage_and_capture(wanted, &out, &out_len, &err, &err_len, &code);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "run_usage_and_capture failed");
/* Expect the exact exit status */
TEST_ASSERT_EQUAL_INT(wanted, code);
/* stdout should be empty in the error path */
TEST_ASSERT_TRUE_MESSAGE(out_len == 0 || (out && out[0] == '\0'),
"Expected no stdout output when status != EXIT_SUCCESS");
/* stderr should contain a try-help message mentioning '--help' and program name */
TEST_ASSERT_TRUE_MESSAGE(buf_contains(err, "--help"),
"stderr should contain '--help' hint");
TEST_ASSERT_TRUE_MESSAGE(buf_contains(err, "mknod"),
"stderr should mention program name 'mknod'");
TEST_ASSERT_TRUE_MESSAGE(buf_contains(err, "Try"),
"stderr should start with a 'Try' hint");
free_captured(out, err);
}
void test_usage_success_includes_mode_option_line(void)
{
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int code = -1;
int rc = run_usage_and_capture(EXIT_SUCCESS, &out, &out_len, &err, &err_len, &code);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "run_usage_and_capture failed");
TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, code);
TEST_ASSERT_TRUE_MESSAGE(buf_contains(out, "-m, --mode"),
"stdout should document the -m, --mode option");
/* Ensure no unexpected stderr output */
TEST_ASSERT_TRUE_MESSAGE(err_len == 0 || (err && err[0] == '\0'),
"Expected no stderr output for successful usage()");
free_captured(out, err);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_usage_success_outputs_usage_and_exits_zero);
RUN_TEST(test_usage_nonzero_status_emits_try_help_and_exits_status);
RUN_TEST(test_usage_success_includes_mode_option_line);
return UNITY_END();
}