coreutils / tests /expr /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 <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
/* usage(int) is defined in the included program source. We also rely on
set_program_name() and program_name being available via earlier includes
in the translation unit (system.h/progname.h). */
/* Helper: read all from fd into a NUL-terminated buffer. Caller must free. */
static char *read_all_fd(int fd) {
size_t cap = 4096;
size_t len = 0;
char *buf = (char *)malloc(cap + 1);
if (!buf) return NULL;
for (;;) {
if (len + 2048 > cap) {
size_t new_cap = cap * 2;
char *new_buf = (char *)realloc(buf, new_cap + 1);
if (!new_buf) {
free(buf);
return NULL;
}
buf = new_buf;
cap = new_cap;
}
ssize_t r = read(fd, buf + len, cap - len);
if (r > 0) {
len += (size_t)r;
} else if (r == 0) {
break; /* EOF */
} else {
if (errno == EINTR) continue;
/* On error, stop and return what we have so far */
break;
}
}
buf[len] = '\0';
return buf;
}
/* Run usage(status) in a child, capturing stdout/stderr and exit status.
Returns 0 on success (child ran and exited), -1 on error.
On success, out_str and err_str are malloc'd NUL-terminated strings. */
static int run_usage_and_capture(int status_code, char **out_str, char **err_str, int *exit_status) {
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 and call usage. */
/* Ensure English messages for deterministic tests. */
setenv("LC_ALL", "C", 1);
/* Ensure program_name is set properly for output formatting. */
/* Prototype is available from earlier includes in the TU. */
set_program_name("expr");
/* Redirect to pipes. */
dup2(out_pipe[1], STDOUT_FILENO);
dup2(err_pipe[1], STDERR_FILENO);
/* Close all pipe fds we don't need. */
close(out_pipe[0]); close(out_pipe[1]);
close(err_pipe[0]); close(err_pipe[1]);
/* Call the function under test; it will call exit(). */
usage(status_code);
/* Should not reach here, but be safe. */
_exit(255);
} else {
/* Parent: close write ends, read outputs, wait for child. */
close(out_pipe[1]);
close(err_pipe[1]);
char *out_buf = read_all_fd(out_pipe[0]);
char *err_buf = read_all_fd(err_pipe[0]);
close(out_pipe[0]);
close(err_pipe[0]);
int wstatus = 0;
if (waitpid(pid, &wstatus, 0) < 0) {
if (out_buf) free(out_buf);
if (err_buf) free(err_buf);
return -1;
}
if (WIFEXITED(wstatus)) {
*exit_status = WEXITSTATUS(wstatus);
} else if (WIFSIGNALED(wstatus)) {
/* Child terminated by signal; emulate a nonzero code. */
*exit_status = 128 + WTERMSIG(wstatus);
} else {
*exit_status = -1;
}
*out_str = out_buf ? out_buf : strdup("");
*err_str = err_buf ? err_buf : strdup("");
return 0;
}
}
/* Unity fixtures */
void setUp(void) {
/* No global setup needed */
}
void tearDown(void) {
/* No global cleanup needed */
}
/* Tests */
static void assert_contains(const char *haystack, const char *needle, const char *context) {
if (!haystack || !needle) {
TEST_FAIL_MESSAGE("Null pointer passed to assert_contains");
}
if (strstr(haystack, needle) == NULL) {
char msg[512];
snprintf(msg, sizeof(msg), "Expected to find substring \"%s\" in %s output, but did not.", needle, context);
TEST_FAIL_MESSAGE(msg);
}
}
void test_usage_success_prints_full_help_and_exits_zero(void) {
char *out = NULL;
char *err = NULL;
int code = -1;
int rc = run_usage_and_capture(0, &out, &err, &code);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to execute usage() in child process");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, code, "usage(EXIT_SUCCESS) did not exit with code 0");
/* stdout should contain the main usage lines with program name */
assert_contains(out, "Usage: expr EXPRESSION", "stdout");
assert_contains(out, "or: expr OPTION", "stdout");
/* stderr should be empty for full help */
TEST_ASSERT_TRUE_MESSAGE(err != NULL, "err buffer is NULL");
TEST_ASSERT_EQUAL_UINT_MESSAGE(0u, (unsigned)strlen(err), "stderr should be empty for full help");
free(out);
free(err);
}
void test_usage_nonzero_prints_try_help_on_stderr_and_exits_with_status(void) {
char *out = NULL;
char *err = NULL;
int code = -1;
int rc = run_usage_and_capture(7, &out, &err, &code);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to execute usage() in child process");
TEST_ASSERT_EQUAL_INT_MESSAGE(7, code, "usage(nonzero) did not exit with the given status");
/* stdout should be empty or minimal for nonzero status */
TEST_ASSERT_TRUE_MESSAGE(out != NULL, "out buffer is NULL");
TEST_ASSERT_EQUAL_UINT_MESSAGE(0u, (unsigned)strlen(out), "stdout should be empty for nonzero status");
/* stderr should contain a prompt to use --help and program name */
assert_contains(err, "--help", "stderr");
assert_contains(err, "expr", "stderr");
free(out);
free(err);
}
void test_usage_success_includes_exit_status_explanation(void) {
char *out = NULL;
char *err = NULL;
int code = -1;
int rc = run_usage_and_capture(0, &out, &err, &code);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_INT(0, code);
/* Check that the full help text includes the exit status explanation line */
assert_contains(out, "Exit status is 0 if EXPRESSION is neither null nor 0", "stdout");
free(out);
free(err);
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_usage_success_prints_full_help_and_exits_zero);
RUN_TEST(test_usage_nonzero_prints_try_help_on_stderr_and_exits_with_status);
RUN_TEST(test_usage_success_includes_exit_status_explanation);
return UNITY_END();
}