coreutils / tests /cat /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 <stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#include <locale.h>
#include <signal.h>
/* program_name is defined in gnulib; referenced by usage(). */
extern char const *program_name;
/* Unity fixtures */
void setUp(void) {
/* Setup code here, or leave empty */
}
void tearDown(void) {
/* Cleanup code here, or leave empty */
}
/* Helper: read all data from fd into a malloc'd buffer.
Returns 0 on success. On success, *out points to a NUL-terminated buffer and
*out_len is the number of bytes read (not including the NUL). */
static int read_all_fd(int fd, char **out, size_t *out_len) {
size_t cap = 4096;
size_t len = 0;
char *buf = (char *)malloc(cap);
if (!buf) return -1;
for (;;) {
if (len == cap) {
size_t ncap = cap * 2;
char *nbuf = (char *)realloc(buf, ncap);
if (!nbuf) {
free(buf);
return -1;
}
buf = nbuf;
cap = ncap;
}
ssize_t n = read(fd, buf + len, cap - len);
if (n < 0) {
if (errno == EINTR) continue;
free(buf);
return -1;
}
if (n == 0) break;
len += (size_t)n;
}
/* ensure NUL terminator */
if (len == cap) {
char *nbuf = (char *)realloc(buf, cap + 1);
if (!nbuf) {
free(buf);
return -1;
}
buf = nbuf;
}
buf[len] = '\0';
*out = buf;
*out_len = len;
return 0;
}
/* Helper: run usage(status) in a child process with program_name set to pname.
Captures stdout/stderr into malloc'd buffers.
Returns 0 on success, -1 on failure to set up/execute child.
exit_code is set to the child's exit status if it exited normally,
or -1 otherwise. */
static int run_usage_and_capture(const char *pname, int call_status,
char **out_buf, size_t *out_len,
char **err_buf, size_t *err_len,
int *exit_code) {
int pout[2] = {-1, -1};
int perr[2] = {-1, -1};
if (pipe(pout) < 0) return -1;
if (pipe(perr) < 0) {
close(pout[0]); close(pout[1]);
return -1;
}
fflush(stdout);
fflush(stderr);
pid_t pid = fork();
if (pid < 0) {
close(pout[0]); close(pout[1]);
close(perr[0]); close(perr[1]);
return -1;
}
if (pid == 0) {
/* Child: redirect stdout/stderr, set program_name, then call usage(). */
/* Ensure C locale for predictable output where applicable. */
setenv("LC_ALL", "C", 1);
/* Redirect stdout/stderr to pipes. */
if (dup2(pout[1], STDOUT_FILENO) < 0) _exit(127);
if (dup2(perr[1], STDERR_FILENO) < 0) _exit(127);
close(pout[0]); close(pout[1]);
close(perr[0]); close(perr[1]);
/* Set program_name used by usage(). */
program_name = pname;
/* Call the target function; it will exit() the process. */
usage(call_status);
/* Should never reach here. */
_exit(127);
}
/* Parent: close write ends, read from read ends. */
close(pout[1]); close(perr[1]);
int status;
char *out_local = NULL, *err_local = NULL;
size_t out_local_len = 0, err_local_len = 0;
/* Read outputs before waiting to avoid potential deadlocks on full pipes. */
int r1 = read_all_fd(pout[0], &out_local, &out_local_len);
int r2 = read_all_fd(perr[0], &err_local, &err_local_len);
close(pout[0]);
close(perr[0]);
if (r1 != 0 || r2 != 0) {
if (out_local) free(out_local);
if (err_local) free(err_local);
/* Reap child to avoid zombies. */
waitpid(pid, &status, 0);
return -1;
}
if (waitpid(pid, &status, 0) < 0) {
free(out_local);
free(err_local);
return -1;
}
if (WIFEXITED(status)) {
*exit_code = WEXITSTATUS(status);
} else {
*exit_code = -1;
}
*out_buf = out_local;
*out_len = out_local_len;
*err_buf = err_local;
*err_len = err_local_len;
return 0;
}
/* Test: usage(EXIT_SUCCESS) prints help to stdout, stderr empty, exits 0. */
static void test_usage_success_outputs_help_to_stdout_and_exits_zero(void) {
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int ec = -2;
int rc = run_usage_and_capture("cat", EXIT_SUCCESS, &out, &out_len, &err, &err_len, &ec);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to run usage() in child");
TEST_ASSERT_EQUAL_INT_MESSAGE(EXIT_SUCCESS, ec, "usage(EXIT_SUCCESS) did not exit with 0");
TEST_ASSERT_NOT_NULL_MESSAGE(out, "stdout buffer is NULL");
TEST_ASSERT_TRUE_MESSAGE(out_len > 0, "stdout is empty for usage(EXIT_SUCCESS)");
TEST_ASSERT_NOT_NULL_MESSAGE(err, "stderr buffer is NULL");
TEST_ASSERT_EQUAL_UINT_MESSAGE(0, err_len, "stderr not empty for usage(EXIT_SUCCESS)");
/* Check for stable option flag substrings in the help text. */
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "-A, --show-all"), "Help missing '-A, --show-all'");
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "-n, --number"), "Help missing '-n, --number'");
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "-T, --show-tabs"), "Help missing '-T, --show-tabs'");
free(out);
free(err);
}
/* Test: usage(nonzero) prints 'try --help' to stderr, stdout empty, exits with status. */
static void test_usage_nonzero_status_outputs_try_help_to_stderr_and_exits_code(void) {
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int ec = -2;
int rc = run_usage_and_capture("cat", 3, &out, &out_len, &err, &err_len, &ec);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to run usage() in child");
TEST_ASSERT_EQUAL_INT_MESSAGE(3, ec, "usage(nonzero) did not exit with given status");
TEST_ASSERT_NOT_NULL_MESSAGE(out, "stdout buffer is NULL");
TEST_ASSERT_EQUAL_UINT_MESSAGE(0, out_len, "stdout should be empty for usage(nonzero)");
TEST_ASSERT_NOT_NULL_MESSAGE(err, "stderr buffer is NULL");
TEST_ASSERT_TRUE_MESSAGE(err_len > 0, "stderr is empty for usage(nonzero)");
/* Look for stable markers: program name and '--help'. */
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(err, "cat"), "stderr missing program name");
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(err, "--help"), "stderr missing '--help' hint");
free(out);
free(err);
}
/* Test: program_name reflected in outputs for both success and failure cases. */
static void test_usage_reflects_program_name_in_outputs(void) {
/* Success case: program_name should appear in full help (e.g., Usage line or Examples). */
{
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int ec = -2;
int rc = run_usage_and_capture("mycat", EXIT_SUCCESS, &out, &out_len, &err, &err_len, &ec);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, ec);
TEST_ASSERT_TRUE(out_len > 0);
TEST_ASSERT_NOT_NULL(strstr(out, "mycat")); /* Should appear in Usage or Examples lines. */
TEST_ASSERT_EQUAL_UINT(0, err_len);
free(out);
free(err);
}
/* Non-success case: program_name should appear in the 'try --help' diagnostic. */
{
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int ec = -2;
int rc = run_usage_and_capture("mycat", 2, &out, &out_len, &err, &err_len, &ec);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_INT(2, ec);
TEST_ASSERT_EQUAL_UINT(0, out_len);
TEST_ASSERT_TRUE(err_len > 0);
TEST_ASSERT_NOT_NULL(strstr(err, "mycat"));
TEST_ASSERT_NOT_NULL(strstr(err, "--help"));
free(out);
free(err);
}
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_usage_success_outputs_help_to_stdout_and_exits_zero);
RUN_TEST(test_usage_nonzero_status_outputs_try_help_to_stderr_and_exits_code);
RUN_TEST(test_usage_reflects_program_name_in_outputs);
return UNITY_END();
}