coreutils / tests /mv /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/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>
/* Access the global program_name used by usage(). */
extern char const *program_name;
/* Forward declaration of the target function. */
void usage (int status);
/* Helper: run usage(status) in a child, capturing either stdout or stderr.
capture_fd must be STDOUT_FILENO or STDERR_FILENO.
progname will be assigned to program_name in the child before calling usage.
On return, *out_buf is a malloc'ed, NUL-terminated buffer with captured output
(caller must free), and *exit_code is the child's exit code if it exited
normally (or -1 otherwise). Returns 0 on success, -1 on setup error. */
static int run_usage_and_capture(int status_to_pass, int capture_fd,
const char *progname,
char **out_buf, int *exit_code)
{
if (!out_buf || !exit_code) return -1;
*out_buf = NULL;
*exit_code = -1;
int p[2];
if (pipe(p) < 0)
return -1;
fflush(stdout);
fflush(stderr);
pid_t pid = fork();
if (pid < 0)
{
int saved = errno;
close(p[0]); close(p[1]);
errno = saved;
return -1;
}
if (pid == 0)
{
/* Child: redirect selected fd to pipe write end. */
/* Ensure parent's stdout is unaffected; redirection only in child. */
close(p[0]); /* close read end */
if (dup2(p[1], capture_fd) < 0)
{
/* If dup2 fails, exit with distinctive code. */
_exit(127);
}
/* Close the duplicated fd as pipe write end is now on capture_fd. */
close(p[1]);
/* Set the program name to control output. */
program_name = progname ? progname : "mv";
/* Call the target; it will call exit(status_to_pass). */
usage(status_to_pass);
/* Should not reach here. */
_exit(126);
}
else
{
/* Parent: read from pipe and wait for child. */
close(p[1]); /* close write end */
size_t cap = 1024;
size_t len = 0;
char *buf = (char *)malloc(cap);
if (!buf)
{
close(p[0]);
/* Best effort: reap child. */
int st; waitpid(pid, &st, 0);
return -1;
}
ssize_t n;
while ((n = read(p[0], buf + len, cap - len)) > 0)
{
len += (size_t)n;
if (len == cap)
{
size_t newcap = cap * 2;
char *nb = (char *)realloc(buf, newcap);
if (!nb)
{
free(buf);
close(p[0]);
int st; waitpid(pid, &st, 0);
return -1;
}
buf = nb;
cap = newcap;
}
}
close(p[0]);
/* NUL-terminate */
if (len == cap)
{
char *nb = (char *)realloc(buf, cap + 1);
if (!nb)
{
free(buf);
int st; waitpid(pid, &st, 0);
return -1;
}
buf = nb;
}
buf[len] = '\0';
int st = 0;
if (waitpid(pid, &st, 0) < 0)
{
free(buf);
return -1;
}
*out_buf = buf;
if (WIFEXITED(st))
*exit_code = WEXITSTATUS(st);
else
*exit_code = -1;
return 0;
}
}
/* Utility: check if substring exists. */
static int contains_substr(const char *haystack, const char *needle)
{
return haystack && needle && strstr(haystack, needle) != NULL;
}
void setUp(void) {
/* No global setup required. Each test sets program_name in child. */
}
void tearDown(void) {
/* No global teardown required. */
}
void test_usage_success_writes_help_to_stdout_and_exits_zero(void)
{
char *out = NULL;
int code = -1;
int rc = run_usage_and_capture(0, STDOUT_FILENO, "mv", &out, &code);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_NOT_NULL(out);
TEST_ASSERT_EQUAL_INT(0, code);
/* Verify key parts of the help text are present on stdout. */
TEST_ASSERT_TRUE(contains_substr(out, "Usage:"));
TEST_ASSERT_TRUE(contains_substr(out, "mv"));
TEST_ASSERT_TRUE(contains_substr(out, "Rename SOURCE to DEST"));
TEST_ASSERT_TRUE(contains_substr(out, "--backup"));
TEST_ASSERT_TRUE(contains_substr(out, "--verbose"));
free(out);
}
void test_usage_failure_writes_try_help_to_stderr_and_exits_nonzero(void)
{
char *err = NULL;
int code = -1;
int rc = run_usage_and_capture(1, STDERR_FILENO, "mv", &err, &code);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_NOT_NULL(err);
TEST_ASSERT_EQUAL_INT(1, code);
/* Typical message: "Try 'mv --help' for more information." */
TEST_ASSERT_TRUE(contains_substr(err, "Try"));
TEST_ASSERT_TRUE(contains_substr(err, "mv"));
TEST_ASSERT_TRUE(contains_substr(err, "--help"));
free(err);
}
void test_usage_success_does_not_write_to_stderr(void)
{
char *err = NULL;
int code = -1;
int rc = run_usage_and_capture(0, STDERR_FILENO, "mv", &err, &code);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_NOT_NULL(err);
TEST_ASSERT_EQUAL_INT(0, code);
/* Expect stderr to be empty for successful usage(status==0). */
TEST_ASSERT_EQUAL_UINT32(0u, (unsigned)strlen(err));
free(err);
}
void test_usage_failure_does_not_write_to_stdout(void)
{
char *out = NULL;
int code = -1;
int rc = run_usage_and_capture(1, STDOUT_FILENO, "mv", &out, &code);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_NOT_NULL(out);
TEST_ASSERT_EQUAL_INT(1, code);
/* Expect stdout to be empty for failure usage(status!=0). */
TEST_ASSERT_EQUAL_UINT32(0u, (unsigned)strlen(out));
free(out);
}
void test_usage_uses_program_name_variable(void)
{
const char *custom = "my-mv";
char *out = NULL;
int code = -1;
int rc = run_usage_and_capture(0, STDOUT_FILENO, custom, &out, &code);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_NOT_NULL(out);
TEST_ASSERT_EQUAL_INT(0, code);
/* Ensure custom program name appears in the usage heading. */
TEST_ASSERT_TRUE(contains_substr(out, "Usage: my-mv"));
free(out);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_usage_success_writes_help_to_stdout_and_exits_zero);
RUN_TEST(test_usage_failure_writes_try_help_to_stderr_and_exits_nonzero);
RUN_TEST(test_usage_success_does_not_write_to_stderr);
RUN_TEST(test_usage_failure_does_not_write_to_stdout);
RUN_TEST(test_usage_uses_program_name_variable);
return UNITY_END();
}