coreutils / tests /basename /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/wait.h>
#include <errno.h>
#include <locale.h>
/* Helper to read all bytes from fd into a malloc'd buffer.
Returns buffer (null-terminated) and sets *out_len. */
static char *read_all_from_fd(int fd, size_t *out_len)
{
size_t cap = 1024;
size_t len = 0;
char *buf = (char *)malloc(cap + 1);
if (!buf) return NULL;
while (1)
{
if (len + 512 > cap)
{
cap *= 2;
char *nb = (char *)realloc(buf, cap + 1);
if (!nb)
{
free(buf);
return NULL;
}
buf = nb;
}
ssize_t n = read(fd, buf + len, cap - len);
if (n < 0)
{
if (errno == EINTR) continue;
break;
}
if (n == 0) break;
len += (size_t)n;
}
buf[len] = '\0';
if (out_len) *out_len = len;
return buf;
}
/* Runs usage(status) in a child process with stdout/stderr captured.
Sets LC_ALL=C, sets program name via set_program_name(progname).
On success, returns 0 and fills outputs; on error returns -1. */
static int run_usage_and_capture(int status,
const char *progname,
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;
}
else if (pid == 0)
{
/* Child: redirect stdout/stderr to pipes, set locale and program name, call usage. */
if (dup2(out_pipe[1], STDOUT_FILENO) < 0) _exit(127);
if (dup2(err_pipe[1], STDERR_FILENO) < 0) _exit(127);
close(out_pipe[0]); close(out_pipe[1]);
close(err_pipe[0]); close(err_pipe[1]);
/* Force C locale to stabilize messages. */
setenv("LC_ALL", "C", 1);
/* Ensure program_name is set. system.h should have included progname.h earlier. */
set_program_name(progname ? progname : "basename");
/* Call the function under test; it will exit(status). */
usage(status);
/* Should not reach here. */
_exit(255);
}
else
{
/* Parent: close write ends, read from pipes, wait for child. */
close(out_pipe[1]);
close(err_pipe[1]);
char *o = read_all_from_fd(out_pipe[0], out_len);
char *e = read_all_from_fd(err_pipe[0], err_len);
close(out_pipe[0]);
close(err_pipe[0]);
int wstatus = 0;
if (waitpid(pid, &wstatus, 0) < 0)
{
free(o); free(e);
return -1;
}
int code = -1;
if (WIFEXITED(wstatus))
code = WEXITSTATUS(wstatus);
else if (WIFSIGNALED(wstatus))
code = 128 + WTERMSIG(wstatus);
if (out_buf) *out_buf = o; else free(o);
if (err_buf) *err_buf = e; else free(e);
if (exit_code) *exit_code = code;
return 0;
}
}
/* Simple substring check. */
static int contains_substr(const char *hay, const char *needle)
{
if (!hay || !needle) return 0;
return strstr(hay, needle) != NULL;
}
void setUp(void) {
/* No global setup needed. */
}
void tearDown(void) {
/* No global teardown needed. */
}
void test_usage_success_outputs_usage_and_exits_zero(void)
{
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int code = -1;
int rc = run_usage_and_capture(EXIT_SUCCESS, "basename",
&out, &out_len, &err, &err_len, &code);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_INT(0, code);
TEST_ASSERT_NOT_NULL(out);
TEST_ASSERT_TRUE(out_len > 0);
TEST_ASSERT_TRUE(contains_substr(out, "Usage: "));
TEST_ASSERT_TRUE(contains_substr(out, "NAME"));
TEST_ASSERT_TRUE(contains_substr(out, "basename"));
TEST_ASSERT_NOT_NULL(err);
TEST_ASSERT_EQUAL_UINT64(0, (unsigned long long)err_len);
free(out);
free(err);
}
void test_usage_success_includes_options_and_examples(void)
{
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int code = -1;
int rc = run_usage_and_capture(EXIT_SUCCESS, "basename",
&out, &out_len, &err, &err_len, &code);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_INT(0, code);
TEST_ASSERT_TRUE(contains_substr(out, "-a, --multiple"));
TEST_ASSERT_TRUE(contains_substr(out, "-s, --suffix"));
TEST_ASSERT_TRUE(contains_substr(out, "-z, --zero"));
TEST_ASSERT_TRUE(contains_substr(out, "Examples:"));
free(out);
free(err);
}
void test_usage_nonzero_status_exits_with_status_and_writes_stderr_only(void)
{
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int code = -1;
int rc = run_usage_and_capture(2, "basename",
&out, &out_len, &err, &err_len, &code);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_INT(2, code);
/* On error, full usage should not be printed to stdout. */
TEST_ASSERT_TRUE(out_len == 0 || !contains_substr(out, "Usage: "));
/* We expect some message on stderr. */
TEST_ASSERT_TRUE(err_len > 0);
/* Likely message suggests --help. Under C locale, check for "--help". */
TEST_ASSERT_TRUE(contains_substr(err, "--help"));
free(out);
free(err);
}
void test_usage_respects_program_name_in_output(void)
{
/* Use a custom program name to confirm it appears in the usage text. */
const char *custom = "mybasename";
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int code = -1;
int rc = run_usage_and_capture(EXIT_SUCCESS, custom,
&out, &out_len, &err, &err_len, &code);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_INT(0, code);
TEST_ASSERT_TRUE(contains_substr(out, custom));
free(out);
free(err);
}
void test_usage_success_writes_nothing_to_stderr(void)
{
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int code = -1;
int rc = run_usage_and_capture(EXIT_SUCCESS, "basename",
&out, &out_len, &err, &err_len, &code);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_INT(0, code);
TEST_ASSERT_EQUAL_UINT64(0, (unsigned long long)err_len);
free(out);
free(err);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_usage_success_outputs_usage_and_exits_zero);
RUN_TEST(test_usage_success_includes_options_and_examples);
RUN_TEST(test_usage_nonzero_status_exits_with_status_and_writes_stderr_only);
RUN_TEST(test_usage_respects_program_name_in_output);
RUN_TEST(test_usage_success_writes_nothing_to_stderr);
return UNITY_END();
}