coreutils / tests /date /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 <fcntl.h>
#include <errno.h>
#include <stdio.h>
/* Helper to read all data from an fd into a NUL-terminated buffer. */
static char *read_all_fd(int fd, size_t *out_len) {
size_t cap = 4096;
size_t len = 0;
char *buf = (char *)malloc(cap + 1);
if (!buf) {
*out_len = 0;
return NULL;
}
for (;;) {
if (len + 2048 > cap) {
size_t ncap = cap * 2;
char *nbuf = (char *)realloc(buf, ncap + 1);
if (!nbuf) {
free(buf);
*out_len = 0;
return NULL;
}
buf = nbuf;
cap = ncap;
}
ssize_t r = read(fd, buf + len, (cap - len));
if (r < 0) {
if (errno == EINTR) continue;
break;
}
if (r == 0) break;
len += (size_t)r;
}
buf[len] = '\0';
*out_len = len;
return buf;
}
/* Spawn a child that calls usage(status) with program_name set to pname,
capturing its stdout/stderr and exit status. Returns 0 on success. */
static int run_usage_and_capture(int status, const char *pname,
char **out_buf, size_t *out_len,
char **err_buf, size_t *err_len,
int *exit_code) {
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;
}
if (pid == 0) {
/* Child: redirect stdout/stderr, set environment and program_name, call usage. */
/* Ensure predictable messages. */
setenv("LC_ALL", "C", 1);
setenv("LANG", "C", 1);
/* Access program_name declared earlier via included headers in this TU. */
extern char const *program_name;
program_name = pname ? pname : "date";
/* Redirect stdout/stderr to pipes. */
if (dup2(out_pipe[1], STDOUT_FILENO) == -1) _exit(127);
if (dup2(err_pipe[1], STDERR_FILENO) == -1) _exit(127);
close(out_pipe[0]); close(out_pipe[1]);
close(err_pipe[0]); close(err_pipe[1]);
/* Call the target function; it will call exit(). */
usage(status);
/* Should not reach here, but just in case. */
_exit(255);
}
/* Parent: close write ends, read outputs, wait for child. */
close(out_pipe[1]);
close(err_pipe[1]);
char *outb = read_all_fd(out_pipe[0], out_len);
char *errb = read_all_fd(err_pipe[0], err_len);
close(out_pipe[0]);
close(err_pipe[0]);
int wstatus = 0;
pid_t w = waitpid(pid, &wstatus, 0);
if (w < 0) {
if (outb) free(outb);
if (errb) free(errb);
return -1;
}
int ec = -1;
if (WIFEXITED(wstatus)) {
ec = WEXITSTATUS(wstatus);
} else if (WIFSIGNALED(wstatus)) {
ec = 128 + WTERMSIG(wstatus);
}
*exit_code = ec;
*out_buf = outb ? outb : strdup("");
*err_buf = errb ? errb : strdup("");
return 0;
}
/* Simple substring check. */
static int str_contains(const char *haystack, const char *needle) {
if (!haystack || !needle) return 0;
return strstr(haystack, needle) != NULL;
}
void setUp(void) {
/* Setup code here, or leave empty */
}
void tearDown(void) {
/* Cleanup code here, or leave empty */
}
/* Tests */
void test_usage_success_outputs_usage_and_exits_success(void) {
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int ec = -1;
int rc = run_usage_and_capture(0, "date", &out, &out_len, &err, &err_len, &ec);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to run usage() in child process");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, ec, "usage(0) should exit with status 0");
/* stdout should contain the Usage header with program name, stderr should be empty */
TEST_ASSERT_TRUE_MESSAGE(out_len > 0, "usage(0) should produce help on stdout");
TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "Usage: date"), "stdout should contain 'Usage: date'");
TEST_ASSERT_EQUAL_UINT_MESSAGE(0u, err_len, "stderr should be empty for usage(0)");
free(out);
free(err);
}
void test_usage_nonzero_emits_try_help_and_exits_with_code(void) {
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int ec = -1;
int rc = run_usage_and_capture(1, "date", &out, &out_len, &err, &err_len, &ec);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to run usage() in child process");
TEST_ASSERT_EQUAL_INT_MESSAGE(1, ec, "usage(1) should exit with status 1");
/* Should not print full help to stdout; err should include --help hint. */
TEST_ASSERT_EQUAL_UINT_MESSAGE(0u, out_len, "stdout should be empty for nonzero status");
TEST_ASSERT_TRUE_MESSAGE(err_len > 0, "stderr should contain try-help message for nonzero status");
TEST_ASSERT_TRUE_MESSAGE(str_contains(err, "--help"),
"stderr should contain '--help' in try-help message");
free(out);
free(err);
}
void test_usage_uses_program_name_variable(void) {
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int ec = -1;
/* Set program name to a custom value and ensure it appears in the Usage line. */
const char *custom = "mydate";
int rc = run_usage_and_capture(0, custom, &out, &out_len, &err, &err_len, &ec);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to run usage() in child process");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, ec, "usage(0) should exit with status 0");
TEST_ASSERT_TRUE_MESSAGE(out_len > 0, "stdout should contain help text");
TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "Usage: mydate"),
"stdout should contain 'Usage: mydate'");
TEST_ASSERT_EQUAL_UINT_MESSAGE(0u, err_len, "stderr should be empty for usage(0)");
free(out);
free(err);
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_usage_success_outputs_usage_and_exits_success);
RUN_TEST(test_usage_nonzero_emits_try_help_and_exits_with_code);
RUN_TEST(test_usage_uses_program_name_variable);
return UNITY_END();
}