coreutils / tests /groups /tests_for_usage.c
AryaWu's picture
Upload folder using huggingface_hub
78d2150 verified
#include "../../unity/unity.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <locale.h>
/* Helpers to capture output from a forked child running usage(). */
static char* read_all_fd(int fd, size_t* out_len)
{
size_t cap = 0;
size_t len = 0;
char* buf = NULL;
char tmp[4096];
for (;;)
{
ssize_t n = read(fd, tmp, sizeof tmp);
if (n > 0)
{
if (len + (size_t)n + 1 > cap)
{
size_t new_cap = (cap ? cap * 2 : 8192);
while (len + (size_t)n + 1 > new_cap)
new_cap *= 2;
char* new_buf = (char*)realloc(buf, new_cap);
if (!new_buf)
{
free(buf);
*out_len = 0;
return NULL;
}
buf = new_buf;
cap = new_cap;
}
memcpy(buf + len, tmp, (size_t)n);
len += (size_t)n;
}
else if (n == 0)
{
break; /* EOF */
}
else
{
if (errno == EINTR)
continue;
break; /* error, but continue with what we have */
}
}
if (!buf)
{
buf = (char*)malloc(1);
if (!buf)
{
*out_len = 0;
return NULL;
}
}
buf[len] = '\0';
*out_len = len;
return buf;
}
/* Run usage(status) in a child with given program name, capturing stdout/stderr and exit status. */
static void run_usage_capture_named(int status,
const char* progname,
char** out_str, size_t* out_len,
char** err_str, size_t* err_len,
int* exit_status)
{
int out_pipe[2] = {-1, -1};
int err_pipe[2] = {-1, -1};
*out_str = NULL; *out_len = 0;
*err_str = NULL; *err_len = 0;
*exit_status = -1;
if (pipe(out_pipe) < 0) return;
if (pipe(err_pipe) < 0) { close(out_pipe[0]); close(out_pipe[1]); return; }
fflush(stdout);
fflush(stderr);
pid_t pid = fork();
if (pid == 0)
{
/* Child: redirect stdout/stderr to pipes, set program name, and call usage. */
(void)setlocale(LC_ALL, "C");
/* Best effort to ensure C locale if environment changes between processes. */
dup2(out_pipe[1], STDOUT_FILENO);
dup2(err_pipe[1], STDERR_FILENO);
/* Close pipe fds no longer needed in child */
close(out_pipe[0]);
close(out_pipe[1]);
close(err_pipe[0]);
close(err_pipe[1]);
/* Provided by gnulib via system.h/progname.h included in the program source. */
set_program_name(progname ? progname : "groups");
usage(status);
_exit(127); /* Should not reach here. */
}
else if (pid > 0)
{
/* Parent */
close(out_pipe[1]);
close(err_pipe[1]);
char* out_buf = read_all_fd(out_pipe[0], out_len);
char* err_buf = read_all_fd(err_pipe[0], err_len);
close(out_pipe[0]);
close(err_pipe[0]);
int wstatus = 0;
if (waitpid(pid, &wstatus, 0) >= 0 && WIFEXITED(wstatus))
*exit_status = WEXITSTATUS(wstatus);
else
*exit_status = -1;
if (!out_buf)
{
out_buf = (char*)malloc(1);
if (out_buf) { out_buf[0] = '\0'; *out_len = 0; }
}
if (!err_buf)
{
err_buf = (char*)malloc(1);
if (err_buf) { err_buf[0] = '\0'; *err_len = 0; }
}
*out_str = out_buf;
*err_str = err_buf;
}
else
{
/* fork failed */
close(out_pipe[0]); close(out_pipe[1]);
close(err_pipe[0]); close(err_pipe[1]);
}
}
void setUp(void) {
/* Ensure deterministic English messages. */
setenv("LC_ALL", "C", 1);
setlocale(LC_ALL, "C");
}
void tearDown(void) {
/* Nothing to clean up */
}
/* Tests */
static void assert_contains(const char* haystack, const char* needle)
{
TEST_ASSERT_NOT_NULL(haystack);
TEST_ASSERT_NOT_NULL(needle);
TEST_ASSERT_TRUE_MESSAGE(strstr(haystack, needle) != NULL, "Expected substring not found");
}
void test_usage_exit_success_outputs_help_to_stdout_and_exits_zero(void)
{
char* out = NULL; size_t out_len = 0;
char* err = NULL; size_t err_len = 0;
int status = -1;
run_usage_capture_named(EXIT_SUCCESS, "groups", &out, &out_len, &err, &err_len, &status);
TEST_ASSERT_EQUAL_INT(0, status);
TEST_ASSERT_TRUE(out_len > 0);
TEST_ASSERT_EQUAL_INT(0, (int)err_len);
assert_contains(out, "Usage: ");
assert_contains(out, "Usage: groups");
assert_contains(out, "--help");
assert_contains(out, "--version");
assert_contains(out, "USERNAME");
free(out);
free(err);
}
void test_usage_nonzero_status_prints_try_help_to_stderr_and_exits_status(void)
{
char* out = NULL; size_t out_len = 0;
char* err = NULL; size_t err_len = 0;
int status = -1;
run_usage_capture_named(2, "groups", &out, &out_len, &err, &err_len, &status);
TEST_ASSERT_EQUAL_INT(2, status);
TEST_ASSERT_EQUAL_INT(0, (int)out_len);
TEST_ASSERT_TRUE(err_len > 0);
assert_contains(err, "--help");
assert_contains(err, "groups"); /* should mention the program name */
free(out);
free(err);
}
void test_usage_uses_program_name_in_usage_line(void)
{
char* out = NULL; size_t out_len = 0;
char* err = NULL; size_t err_len = 0;
int status = -1;
const char* custom_name = "gnugroups";
run_usage_capture_named(EXIT_SUCCESS, custom_name, &out, &out_len, &err, &err_len, &status);
TEST_ASSERT_EQUAL_INT(0, status);
TEST_ASSERT_TRUE(out_len > 0);
TEST_ASSERT_EQUAL_INT(0, (int)err_len);
/* Ensure the program name used matches what we set. */
char expect[128];
snprintf(expect, sizeof(expect), "Usage: %s", custom_name);
assert_contains(out, expect);
free(out);
free(err);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_usage_exit_success_outputs_help_to_stdout_and_exits_zero);
RUN_TEST(test_usage_nonzero_status_prints_try_help_to_stderr_and_exits_status);
RUN_TEST(test_usage_uses_program_name_in_usage_line);
return UNITY_END();
}