coreutils / tests /paste /tests_for_usage.c
AryaWu's picture
Upload folder using huggingface_hub
78d2150 verified
#include "../../unity/unity.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <locale.h>
/* Helper to read all data from a fd into a malloc'd buffer.
Returns buffer (NUL-terminated) and sets *len. On error, returns NULL. */
static char* tfu_read_all(int fd, size_t *len_out) {
size_t cap = 4096;
size_t len = 0;
char *buf = (char*)malloc(cap + 1);
if (!buf) return NULL;
for (;;) {
if (len == cap) {
size_t new_cap = cap * 2;
char *nb = (char*)realloc(buf, new_cap + 1);
if (!nb) {
free(buf);
return NULL;
}
buf = nb;
cap = new_cap;
}
ssize_t r = read(fd, buf + len, cap - len);
if (r > 0) {
len += (size_t)r;
continue;
} else if (r == 0) {
break;
} else {
if (errno == EINTR) continue;
free(buf);
return NULL;
}
}
buf[len] = '\0';
if (len_out) *len_out = len;
return buf;
}
typedef struct {
char *out;
size_t out_len;
char *err;
size_t err_len;
int exit_code; /* valid if term_sig == 0 */
int term_sig; /* nonzero if terminated by signal */
} tfu_capture_result;
/* Run usage(status) in a child, capturing stdout and stderr.
Returns true on success, false on setup/fork errors. */
static bool tfu_run_usage_capture(int status, tfu_capture_result *res) {
int out_pipe[2] = {-1, -1};
int err_pipe[2] = {-1, -1};
if (pipe(out_pipe) != 0) return false;
if (pipe(err_pipe) != 0) {
close(out_pipe[0]); close(out_pipe[1]);
return false;
}
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 false;
}
if (pid == 0) {
/* Child: redirect stdout/stderr and call usage. */
/* Ensure C locale (but gettext defaults to C without setlocale). */
setenv("LC_ALL", "C", 1);
setlocale(LC_ALL, "");
/* Redirect stdout and stderr to pipes. */
if (dup2(out_pipe[1], STDOUT_FILENO) < 0) _exit(127);
if (dup2(err_pipe[1], STDERR_FILENO) < 0) _exit(127);
/* Close unused fds. */
close(out_pipe[0]); close(out_pipe[1]);
close(err_pipe[0]); close(err_pipe[1]);
/* Call target (will exit). */
usage(status);
/* Should not reach here; ensure exit anyway. */
_exit(127);
}
/* Parent: close write ends; read from read ends. */
close(out_pipe[1]);
close(err_pipe[1]);
tfu_capture_result tmp = {0};
tmp.out = tfu_read_all(out_pipe[0], &tmp.out_len);
tmp.err = tfu_read_all(err_pipe[0], &tmp.err_len);
close(out_pipe[0]);
close(err_pipe[0]);
int wstatus = 0;
pid_t w = -1;
do {
w = waitpid(pid, &wstatus, 0);
} while (w < 0 && errno == EINTR);
if (w < 0) {
free(tmp.out);
free(tmp.err);
return false;
}
if (WIFEXITED(wstatus)) {
tmp.exit_code = WEXITSTATUS(wstatus);
tmp.term_sig = 0;
} else if (WIFSIGNALED(wstatus)) {
tmp.term_sig = WTERMSIG(wstatus);
tmp.exit_code = -1;
} else {
tmp.term_sig = -1;
tmp.exit_code = -1;
}
*res = tmp;
return true;
}
void setUp(void) {
/* No global setup required. */
}
void tearDown(void) {
/* No global teardown required. */
}
/* Helper to free capture result buffers. */
static void tfu_free_capture(tfu_capture_result *res) {
if (!res) return;
free(res->out);
free(res->err);
res->out = NULL;
res->err = NULL;
res->out_len = res->err_len = 0;
}
/* Test that usage(EXIT_SUCCESS) prints full help to stdout, nothing to stderr, and exits 0. */
void test_usage_success_prints_help_and_exits_zero(void) {
tfu_capture_result r = {0};
TEST_ASSERT_TRUE_MESSAGE(tfu_run_usage_capture(EXIT_SUCCESS, &r), "Failed to run usage in child");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.term_sig, "Child terminated by signal");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.exit_code, "Exit code mismatch for success usage");
/* stdout should be non-empty and contain key option tokens and header */
TEST_ASSERT_NOT_NULL(r.out);
TEST_ASSERT_TRUE_MESSAGE(r.out_len > 0, "Expected stdout to be non-empty");
TEST_ASSERT_NOT_NULL(r.err);
TEST_ASSERT_EQUAL_UINT_MESSAGE(0u, r.err_len, "Expected stderr to be empty on success");
/* Check for stable substrings in help output (C locale). */
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(r.out, "Usage:"), "Missing 'Usage:' line");
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(r.out, "-d, --delimiters"), "Missing -d, --delimiters");
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(r.out, "-s, --serial"), "Missing -s, --serial");
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(r.out, "-z, --zero-terminated"), "Missing -z, --zero-terminated");
tfu_free_capture(&r);
}
/* Test that usage with nonzero status emits try-help to stderr and exits with that status. */
void test_usage_failure_emits_try_help_and_exits_with_status(void) {
const int code = 42; /* arbitrary nonzero in 0..255 */
tfu_capture_result r = {0};
TEST_ASSERT_TRUE_MESSAGE(tfu_run_usage_capture(code, &r), "Failed to run usage in child");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.term_sig, "Child terminated by signal");
TEST_ASSERT_EQUAL_INT_MESSAGE(code, r.exit_code, "Exit code mismatch for failure usage");
/* stderr should be non-empty and contain '--help' */
TEST_ASSERT_NOT_NULL(r.err);
TEST_ASSERT_TRUE_MESSAGE(r.err_len > 0, "Expected stderr to be non-empty on failure usage");
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(r.err, "--help"), "Expected '--help' hint in stderr");
/* stdout should be empty */
TEST_ASSERT_NOT_NULL(r.out);
TEST_ASSERT_EQUAL_UINT_MESSAGE(0u, r.out_len, "Expected stdout to be empty on failure usage");
tfu_free_capture(&r);
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_usage_success_prints_help_and_exits_zero);
RUN_TEST(test_usage_failure_emits_try_help_and_exits_with_status);
return UNITY_END();
}