coreutils / tests /pathchk /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 <stdio.h>
#include <stdbool.h>
/* Helper to read all data from an fd into a NUL-terminated buffer. */
static int read_all(int fd, char **out_buf, size_t *out_len)
{
char tmp[4096];
size_t cap = 0;
size_t len = 0;
char *buf = NULL;
for (;;)
{
ssize_t n = read(fd, tmp, sizeof tmp);
if (n < 0)
{
if (errno == EINTR)
continue;
free(buf);
return -1;
}
if (n == 0)
break;
if (len + (size_t)n + 1 > cap)
{
size_t newcap = cap ? cap * 2 : 8192;
while (newcap < len + (size_t)n + 1)
newcap *= 2;
char *nb = (char *)realloc(buf, newcap);
if (!nb)
{
free(buf);
errno = ENOMEM;
return -1;
}
buf = nb;
cap = newcap;
}
memcpy(buf + len, tmp, (size_t)n);
len += (size_t)n;
}
if (!buf)
{
buf = (char *)malloc(1);
if (!buf)
return -1;
buf[0] = '\0';
len = 0;
}
else
{
buf[len] = '\0';
}
*out_buf = buf;
*out_len = len;
return 0;
}
typedef struct {
char *out_buf;
size_t out_len;
char *err_buf;
size_t err_len;
int exit_code; /* -1 on abnormal termination */
} usage_result;
/* Run usage(status) in a child process capturing stdout/stderr and exit status. */
static int run_usage_and_capture(int status, usage_result *res)
{
int out_pipe[2];
int err_pipe[2];
if (pipe(out_pipe) < 0)
return -1;
if (pipe(err_pipe) < 0)
{
close(out_pipe[0]); close(out_pipe[1]);
return -1;
}
fflush(stdout);
fflush(stderr);
pid_t pid = fork();
if (pid < 0)
{
close(out_pipe[0]); close(out_pipe[1]);
close(err_pipe[0]); close(err_pipe[1]);
return -1;
}
else if (pid == 0)
{
/* Child: redirect stdout/stderr to pipes and invoke usage. */
if (dup2(out_pipe[1], STDOUT_FILENO) < 0) _exit(127);
if (dup2(err_pipe[1], STDERR_FILENO) < 0) _exit(127);
close(out_pipe[0]); close(out_pipe[1]);
close(err_pipe[0]); close(err_pipe[1]);
/* Call the target function; this should not return. */
usage(status);
/* Safety fallback if usage returns unexpectedly. */
_exit(255);
}
else
{
/* Parent: close write ends, read from read ends, wait for child. */
close(out_pipe[1]);
close(err_pipe[1]);
usage_result tmp = {0};
if (read_all(out_pipe[0], &tmp.out_buf, &tmp.out_len) < 0)
{
close(out_pipe[0]);
close(err_pipe[0]);
return -1;
}
if (read_all(err_pipe[0], &tmp.err_buf, &tmp.err_len) < 0)
{
close(out_pipe[0]);
close(err_pipe[0]);
free(tmp.out_buf);
return -1;
}
close(out_pipe[0]);
close(err_pipe[0]);
int wstatus = 0;
if (waitpid(pid, &wstatus, 0) < 0)
{
free(tmp.out_buf);
free(tmp.err_buf);
return -1;
}
if (WIFEXITED(wstatus))
tmp.exit_code = WEXITSTATUS(wstatus);
else
tmp.exit_code = -1;
*res = tmp;
return 0;
}
}
/* Simple substring checker. */
static bool contains_substr(const char *hay, const char *needle)
{
if (!hay || !needle) return false;
return strstr(hay, needle) != NULL;
}
/* program_name is provided by the program environment via system.h/progname.h. */
extern const char *program_name;
void setUp(void) {
/* Ensure deterministic English output. */
setenv("LC_ALL", "C", 1);
/* Ensure program_name is suitable for messages. */
program_name = "pathchk";
}
void tearDown(void) {
/* No-op */
}
/* Test: usage(EXIT_SUCCESS) prints full help to stdout and exits with 0. */
void test_usage_success_outputs_help_and_exits_zero(void)
{
usage_result r = {0};
int rc = run_usage_and_capture(EXIT_SUCCESS, &r);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to run usage in child process");
/* Check exit status. */
TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.exit_code, "usage(EXIT_SUCCESS) did not exit(0)");
/* Check stdout contains key help elements. */
TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.out_buf, "Usage:"), "Help text missing 'Usage:'");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.out_buf, "pathchk"), "Help text missing program name");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.out_buf, "-p"), "Help text missing '-p' option");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.out_buf, "-P"), "Help text missing '-P' option");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.out_buf, "--portability"), "Help text missing '--portability' option");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.out_buf, "--help"), "Help text missing '--help' description");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.out_buf, "--version"), "Help text missing '--version' description");
/* On success, stderr should be empty. */
TEST_ASSERT_TRUE_MESSAGE(r.err_len == 0 || (r.err_buf && r.err_buf[0] == '\0'),
"Expected no stderr output for usage(EXIT_SUCCESS)");
free(r.out_buf);
free(r.err_buf);
}
/* Test: usage(nonzero) emits a 'Try ... --help ...' hint to stderr and exits with the given status. */
void test_usage_failure_emits_try_help_on_stderr_and_propagates_status(void)
{
int requested_status = 2;
usage_result r = {0};
int rc = run_usage_and_capture(requested_status, &r);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to run usage in child process");
/* Check exit status propagates. */
TEST_ASSERT_EQUAL_INT_MESSAGE(requested_status, r.exit_code,
"usage(nonzero) did not propagate exit status");
/* Check stderr contains a 'Try' hint mentioning --help and program name. */
TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.err_buf, "Try"),
"Expected stderr to contain a 'Try' hint");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.err_buf, "--help"),
"Expected stderr hint to mention '--help'");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.err_buf, "pathchk"),
"Expected stderr hint to mention the program name");
/* stdout should not contain the full usage header in this path. */
TEST_ASSERT_FALSE_MESSAGE(contains_substr(r.out_buf, "Usage:"),
"Did not expect full help on stdout for nonzero status");
free(r.out_buf);
free(r.err_buf);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_usage_success_outputs_help_and_exits_zero);
RUN_TEST(test_usage_failure_emits_try_help_on_stderr_and_propagates_status);
return UNITY_END();
}