coreutils / tests /logname /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 <locale.h>
#include <fcntl.h>
/* usage(int) is defined in the including translation unit above.
set_program_name is declared via system.h already included above. */
static int str_contains(const char *haystack, const char *needle) {
if (!haystack || !needle) return 0;
return strstr(haystack, needle) != NULL;
}
static int make_pipe(int p[2]) {
#if defined(O_CLOEXEC)
if (pipe(p) == 0) {
/* Set close-on-exec just in case (not strictly needed as we don't exec) */
fcntl(p[0], F_SETFD, FD_CLOEXEC);
fcntl(p[1], F_SETFD, FD_CLOEXEC);
return 0;
}
return -1;
#else
return pipe(p);
#endif
}
static char *read_all_fd(int fd, size_t *out_len) {
size_t cap = 1024;
size_t len = 0;
char *buf = (char *)malloc(cap);
if (!buf) return NULL;
for (;;) {
if (len == cap) {
size_t ncap = cap * 2;
char *nbuf = (char *)realloc(buf, ncap);
if (!nbuf) {
free(buf);
return NULL;
}
buf = nbuf;
cap = ncap;
}
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;
}
}
/* Ensure null-termination for string ops */
if (len == cap) {
char *nbuf = (char *)realloc(buf, cap + 1);
if (!nbuf) {
free(buf);
return NULL;
}
buf = nbuf;
}
buf[len] = '\0';
if (out_len) *out_len = len;
return buf;
}
typedef struct {
char *out_buf;
size_t out_len;
char *err_buf;
size_t err_len;
int exited;
int exit_status;
} usage_capture_t;
static int run_usage_and_capture(int status, usage_capture_t *cap) {
int outp[2], errp[2];
if (make_pipe(outp) < 0) return -1;
if (make_pipe(errp) < 0) {
close(outp[0]); close(outp[1]);
return -1;
}
fflush(stdout);
fflush(stderr);
pid_t pid = fork();
if (pid < 0) {
close(outp[0]); close(outp[1]);
close(errp[0]); close(errp[1]);
return -1;
}
if (pid == 0) {
/* Child: redirect stdout and stderr to pipes and call usage */
/* Ensure new locale and program name for predictable messages */
setenv("LC_ALL", "C", 1);
setlocale(LC_ALL, "C");
/* Close read ends in child */
close(outp[0]);
close(errp[0]);
/* Redirect */
if (dup2(outp[1], STDOUT_FILENO) < 0) _exit(127);
if (dup2(errp[1], STDERR_FILENO) < 0) _exit(127);
/* Close original write ends after dup */
close(outp[1]);
close(errp[1]);
/* Ensure program_name is set for messages */
set_program_name("logname");
/* Call the function under test: this will exit(status) */
usage(status);
/* Should not reach */
_exit(127);
}
/* Parent: close write ends and read from read ends */
close(outp[1]);
close(errp[1]);
usage_capture_t tmp = {0};
tmp.out_buf = read_all_fd(outp[0], &tmp.out_len);
tmp.err_buf = read_all_fd(errp[0], &tmp.err_len);
close(outp[0]);
close(errp[0]);
int wstatus = 0;
pid_t w = waitpid(pid, &wstatus, 0);
if (w < 0) {
if (tmp.out_buf) free(tmp.out_buf);
if (tmp.err_buf) free(tmp.err_buf);
return -1;
}
tmp.exited = WIFEXITED(wstatus);
tmp.exit_status = tmp.exited ? WEXITSTATUS(wstatus) : -1;
if (cap) *cap = tmp;
else {
if (tmp.out_buf) free(tmp.out_buf);
if (tmp.err_buf) free(tmp.err_buf);
}
return 0;
}
void setUp(void) {
/* Ensure predictable locale in parent too for any indirect operations */
setenv("LC_ALL", "C", 1);
setlocale(LC_ALL, "C");
}
void tearDown(void) {
/* nothing */
}
static void free_capture(usage_capture_t *cap) {
if (!cap) return;
free(cap->out_buf);
free(cap->err_buf);
memset(cap, 0, sizeof(*cap));
}
void test_usage_failure_emits_try_help_to_stderr_only(void) {
usage_capture_t cap = {0};
int rc = run_usage_and_capture(EXIT_FAILURE, &cap);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "run_usage_and_capture failed");
TEST_ASSERT_TRUE_MESSAGE(cap.exited, "Child did not exit normally");
TEST_ASSERT_EQUAL_INT(EXIT_FAILURE, cap.exit_status);
/* On failure, expect no stdout and a 'Try ... --help' message on stderr. */
TEST_ASSERT_NOT_NULL(cap.err_buf);
TEST_ASSERT_TRUE(cap.err_len > 0);
TEST_ASSERT_TRUE_MESSAGE(str_contains(cap.err_buf, "Try '"), "stderr lacks 'Try '");
TEST_ASSERT_TRUE_MESSAGE(str_contains(cap.err_buf, "logname"), "stderr lacks program name");
TEST_ASSERT_TRUE_MESSAGE(str_contains(cap.err_buf, "--help"), "stderr lacks --help hint");
/* stdout should be empty */
if (cap.out_buf) {
TEST_ASSERT_EQUAL_UINT32_MESSAGE(0u, (unsigned)cap.out_len, "stdout should be empty on failure");
TEST_ASSERT_EQUAL_STRING_MESSAGE("", cap.out_buf, "stdout should be empty on failure");
} else {
/* NULL treated as empty */
TEST_ASSERT_NULL(cap.out_buf);
}
free_capture(&cap);
}
void test_usage_success_prints_full_help_to_stdout_and_exits_success(void) {
usage_capture_t cap = {0};
int rc = run_usage_and_capture(EXIT_SUCCESS, &cap);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "run_usage_and_capture failed");
TEST_ASSERT_TRUE_MESSAGE(cap.exited, "Child did not exit normally");
TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, cap.exit_status);
/* On success, expect usage banner and description on stdout, and empty stderr */
TEST_ASSERT_NOT_NULL_MESSAGE(cap.out_buf, "stdout buffer is NULL");
TEST_ASSERT_TRUE_MESSAGE(cap.out_len > 0, "stdout should have content");
TEST_ASSERT_TRUE_MESSAGE(str_contains(cap.out_buf, "Usage: logname [OPTION]"),
"stdout lacks Usage line");
TEST_ASSERT_TRUE_MESSAGE(str_contains(cap.out_buf, "Print the user's login name."),
"stdout lacks primary description");
/* Also expect help/version option hints to be present somewhere in help text */
TEST_ASSERT_TRUE_MESSAGE(str_contains(cap.out_buf, "--help"),
"stdout lacks --help option description");
TEST_ASSERT_TRUE_MESSAGE(str_contains(cap.out_buf, "--version"),
"stdout lacks --version option description");
/* stderr should be empty on success */
if (cap.err_buf) {
TEST_ASSERT_EQUAL_UINT32_MESSAGE(0u, (unsigned)cap.err_len, "stderr should be empty on success");
TEST_ASSERT_EQUAL_STRING_MESSAGE("", cap.err_buf, "stderr should be empty on success");
} else {
TEST_ASSERT_NULL(cap.err_buf);
}
free_capture(&cap);
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_usage_failure_emits_try_help_to_stderr_only);
RUN_TEST(test_usage_success_prints_full_help_to_stdout_and_exits_success);
return UNITY_END();
}