coreutils / tests /link /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 <fcntl.h>
#include <errno.h>
#include <locale.h>
/* Access the target function and globals from the program */
extern void usage (int status);
extern char const *program_name;
/* Helper: read all data from a file descriptor into a NUL-terminated buffer.
Returns malloc'd buffer (caller must free). Sets *out_len to length (not including NUL).
On error, returns NULL and *out_len = 0. */
static char *read_all_from_fd (int fd, size_t *out_len)
{
size_t cap = 4096;
size_t len = 0;
char *buf = (char *)malloc(cap + 1);
if (!buf) {
if (out_len) *out_len = 0;
return NULL;
}
for (;;) {
if (len == cap) {
size_t ncap = cap * 2;
char *nbuf = (char *)realloc(buf, ncap + 1);
if (!nbuf) {
free(buf);
if (out_len) *out_len = 0;
return NULL;
}
buf = nbuf;
cap = ncap;
}
ssize_t n = read(fd, buf + len, cap - len);
if (n > 0) {
len += (size_t)n;
continue;
} else if (n == 0) {
break; /* EOF */
} else {
if (errno == EINTR)
continue;
free(buf);
if (out_len) *out_len = 0;
return NULL;
}
}
buf[len] = '\0';
if (out_len) *out_len = len;
return buf;
}
/* Helper: run usage(status) in a child, with program_name overridden, capture stdout/stderr and exit code.
- progname_override: if non-NULL, set program_name = progname_override in the child before calling usage.
- status: the status to pass to usage().
Returns 0 on success (captures filled), -1 on failure to set up pipes/fork. */
static int spawn_usage_and_capture (const char *progname_override, int status,
char **out_stdout, size_t *out_stdout_len,
char **out_stderr, size_t *out_stderr_len,
int *out_exit_code)
{
int out_pipe[2] = {-1,-1};
int err_pipe[2] = {-1,-1};
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 and call usage(). No Unity assertions here. */
/* Ensure predictable messages. */
setenv("LC_ALL", "C", 1);
setlocale(LC_ALL, "C");
if (progname_override && *progname_override) {
/* Assign the global program_name (pointer to const char). */
program_name = progname_override;
} else {
/* Ensure default in case not already set. */
program_name = "link";
}
/* Redirect stdout and stderr */
close(out_pipe[0]);
close(err_pipe[0]);
if (dup2(out_pipe[1], STDOUT_FILENO) < 0) _exit(127);
if (dup2(err_pipe[1], STDERR_FILENO) < 0) _exit(127);
/* Close the extra fds after dup */
close(out_pipe[1]);
close(err_pipe[1]);
/* Call the function (will exit). */
usage(status);
/* Should not reach here, but just in case, exit with sentinel. */
_exit(255);
}
/* Parent: close write ends and read captured outputs. */
close(out_pipe[1]);
close(err_pipe[1]);
char *cap_out = read_all_from_fd(out_pipe[0], out_stdout_len);
char *cap_err = read_all_from_fd(err_pipe[0], out_stderr_len);
close(out_pipe[0]);
close(err_pipe[0]);
int wstatus = 0;
int rc;
do {
rc = waitpid(pid, &wstatus, 0);
} while (rc < 0 && errno == EINTR);
int exit_code = -1;
if (rc >= 0) {
if (WIFEXITED(wstatus)) {
exit_code = WEXITSTATUS(wstatus);
} else if (WIFSIGNALED(wstatus)) {
exit_code = 128 + WTERMSIG(wstatus);
} else {
exit_code = -1;
}
}
if (!cap_out) {
/* Allocation/read failure: still provide empty buffers to avoid NULL deref in tests. */
cap_out = (char *)malloc(1);
if (cap_out) cap_out[0] = '\0';
if (out_stdout_len) *out_stdout_len = 0;
}
if (!cap_err) {
cap_err = (char *)malloc(1);
if (cap_err) cap_err[0] = '\0';
if (out_stderr_len) *out_stderr_len = 0;
}
if (out_stdout) *out_stdout = cap_out; else free(cap_out);
if (out_stderr) *out_stderr = cap_err; else free(cap_err);
if (out_exit_code) *out_exit_code = exit_code;
return 0;
}
void setUp(void) {
/* No global setup needed for these tests */
}
void tearDown(void) {
/* No global teardown needed */
}
/* Test: Nonzero status EXIT_FAILURE path prints try-help to stderr, no stdout, exit code == EXIT_FAILURE */
void test_usage_failure_prints_try_help_and_exits_failure(void)
{
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int code = -1;
int rc = spawn_usage_and_capture("link", EXIT_FAILURE, &out, &out_len, &err, &err_len, &code);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to spawn child for usage(EXIT_FAILURE)");
TEST_ASSERT_EQUAL_INT(EXIT_FAILURE, code);
TEST_ASSERT_NOT_NULL(out);
TEST_ASSERT_NOT_NULL(err);
TEST_ASSERT_EQUAL_UINT64_MESSAGE(0, (uint64_t)out_len, "Expected no stdout on failure");
/* Expect: Try 'link --help' for more information. */
TEST_ASSERT_TRUE_MESSAGE(strstr(err, "Try 'link --help' for more information.") != NULL,
"stderr should contain the try-help hint for link");
free(out);
free(err);
}
/* Test: Nonzero arbitrary status is propagated to exit code and prints try-help */
void test_usage_failure_with_arbitrary_status_propagated(void)
{
const int arbitrary = 42;
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int code = -1;
int rc = spawn_usage_and_capture("link", arbitrary, &out, &out_len, &err, &err_len, &code);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to spawn child for usage(42)");
TEST_ASSERT_EQUAL_INT(arbitrary, code);
TEST_ASSERT_EQUAL_UINT64_MESSAGE(0, (uint64_t)out_len, "Expected no stdout on failure");
TEST_ASSERT_TRUE_MESSAGE(strstr(err, "Try 'link --help' for more information.") != NULL,
"stderr should contain the try-help hint for link");
free(out);
free(err);
}
/* Test: Success path prints usage/help to stdout, nothing to stderr, and exits with 0 */
void test_usage_success_prints_usage_and_exits_zero(void)
{
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int code = -1;
int rc = spawn_usage_and_capture("link", EXIT_SUCCESS, &out, &out_len, &err, &err_len, &code);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to spawn child for usage(EXIT_SUCCESS)");
TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, code);
TEST_ASSERT_NOT_NULL(out);
TEST_ASSERT_NOT_NULL(err);
TEST_ASSERT_EQUAL_UINT64_MESSAGE(0, (uint64_t)err_len, "Expected no stderr on success");
/* Check key substrings */
TEST_ASSERT_TRUE_MESSAGE(strstr(out, "Usage: link FILE1 FILE2") != NULL,
"stdout should contain the main usage line with program name");
TEST_ASSERT_TRUE_MESSAGE(strstr(out, "or: link OPTION") != NULL,
"stdout should contain the alternative usage line");
TEST_ASSERT_TRUE_MESSAGE(strstr(out, "Call the link function to create a link named FILE2") != NULL,
"stdout should contain the description line");
/* Ensure help/version options are advertised (exact wording can vary by gettext/lib). */
TEST_ASSERT_TRUE_MESSAGE(strstr(out, "--help") != NULL,
"stdout should mention the --help option");
TEST_ASSERT_TRUE_MESSAGE(strstr(out, "--version") != NULL,
"stdout should mention the --version option");
free(out);
free(err);
}
/* Test: program_name variable is respected in the header lines */
void test_usage_respects_program_name_variable(void)
{
const char *custom = "linky";
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int code = -1;
int rc = spawn_usage_and_capture(custom, EXIT_SUCCESS, &out, &out_len, &err, &err_len, &code);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to spawn child for usage(EXIT_SUCCESS) with custom program_name");
TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, code);
TEST_ASSERT_EQUAL_UINT64_MESSAGE(0, (uint64_t)err_len, "Expected no stderr on success with custom name");
TEST_ASSERT_TRUE_MESSAGE(strstr(out, "Usage: linky FILE1 FILE2") != NULL,
"stdout should use custom program_name in main usage line");
TEST_ASSERT_TRUE_MESSAGE(strstr(out, "or: linky OPTION") != NULL,
"stdout should use custom program_name in alternative usage line");
free(out);
free(err);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_usage_failure_prints_try_help_and_exits_failure);
RUN_TEST(test_usage_failure_with_arbitrary_status_propagated);
RUN_TEST(test_usage_success_prints_usage_and_exits_zero);
RUN_TEST(test_usage_respects_program_name_variable);
return UNITY_END();
}