coreutils / tests /chmod /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 <stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
/* set_program_name is declared via system.h in the parent translation unit,
since this test file is included after the program includes. */
extern void set_program_name (const char *);
/* Helper to append data to a dynamic buffer. Ensures NUL-termination. */
static void append_buf(char **buf, size_t *len, const char *data, size_t n)
{
char *newp = realloc(*buf, *len + n + 1);
if (!newp)
return;
memcpy(newp + *len, data, n);
*len += n;
newp[*len] = '\0';
*buf = newp;
}
/* Run usage(status) in a child process, capturing stdout and stderr.
Returns 0 on success, and populates out_stdout/out_stderr and exit_status.
Caller must free *out_stdout and *out_stderr. */
static int run_usage_and_capture(int status,
char **out_stdout, size_t *out_stdout_len,
char **out_stderr, size_t *out_stderr_len,
int *exit_status)
{
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: set locale and program name, redirect fds, call usage. */
/* Ensure C locale to avoid translation variability. */
setenv("LC_ALL", "C", 1);
setenv("LANG", "C", 1);
/* Ensure program_name is initialized for usage() output. */
set_program_name("chmod");
/* 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(out_pipe[1]);
close(err_pipe[1]);
extern void usage (int status);
usage(status);
/* Should not reach here. */
_exit(126);
}
/* Parent: close write ends, read from read ends, then wait for child. */
close(out_pipe[1]);
close(err_pipe[1]);
char *stdout_buf = NULL;
size_t stdout_len = 0;
char *stderr_buf = NULL;
size_t stderr_len = 0;
char tmp[4096];
ssize_t n;
/* Read stdout */
while ((n = read(out_pipe[0], tmp, sizeof tmp)) > 0) {
append_buf(&stdout_buf, &stdout_len, tmp, (size_t)n);
}
close(out_pipe[0]);
/* Read stderr */
while ((n = read(err_pipe[0], tmp, sizeof tmp)) > 0) {
append_buf(&stderr_buf, &stderr_len, tmp, (size_t)n);
}
close(err_pipe[0]);
int wstatus = 0;
if (waitpid(pid, &wstatus, 0) < 0) {
free(stdout_buf);
free(stderr_buf);
return -1;
}
int es = -1;
if (WIFEXITED(wstatus)) {
es = WEXITSTATUS(wstatus);
} else if (WIFSIGNALED(wstatus)) {
es = 128 + WTERMSIG(wstatus);
}
*out_stdout = stdout_buf ? stdout_buf : strdup("");
*out_stdout_len = stdout_len;
*out_stderr = stderr_buf ? stderr_buf : strdup("");
*out_stderr_len = stderr_len;
*exit_status = es;
return 0;
}
void setUp(void) {
/* Setup code here, or leave empty */
}
void tearDown(void) {
/* Cleanup code here, or leave empty */
}
static bool contains_substr(const char *haystack, const char *needle) {
return haystack && needle && strstr(haystack, needle) != NULL;
}
/* Test that usage(EXIT_SUCCESS) prints the full usage to stdout,
nothing to stderr, and exits with code 0. */
void test_usage_success_outputs_to_stdout_and_exit_zero(void)
{
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int es = -1;
int rc = run_usage_and_capture(EXIT_SUCCESS, &out, &out_len, &err, &err_len, &es);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to run usage in child");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, es, "usage(EXIT_SUCCESS) did not exit with 0");
TEST_ASSERT_TRUE_MESSAGE(out_len > 0, "Expected non-empty stdout for success usage");
TEST_ASSERT_EQUAL_size_t_MESSAGE(0, err_len, "Expected empty stderr for success usage");
/* Check for key strings that should be in usage output.
Use option flags/syntax that are not localized. */
TEST_ASSERT_TRUE_MESSAGE(contains_substr(out, "Usage:"), "Missing 'Usage:' prefix");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(out, "chmod"), "Missing program name 'chmod'");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(out, "-R, --recursive"), "Missing recursive option flag");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(out, "--reference"), "Missing --reference option flag");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(out, "--no-dereference"), "Missing --no-dereference option flag");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(out, "--dereference"), "Missing --dereference option flag");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(out, "--preserve-root"), "Missing --preserve-root option flag");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(out, "--help"), "Missing --help in usage text");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(out, "--version"), "Missing --version in usage text");
free(out);
free(err);
}
/* Test that usage(nonzero) prints a 'try --help' message to stderr,
nothing to stdout, and exits with the provided status. */
void test_usage_failure_outputs_try_help_to_stderr_and_exit_status(void)
{
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int es = -1;
int desired_status = 2;
int rc = run_usage_and_capture(desired_status, &out, &out_len, &err, &err_len, &es);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to run usage in child");
TEST_ASSERT_EQUAL_INT_MESSAGE(desired_status, es, "usage(nonzero) did not exit with provided status");
TEST_ASSERT_EQUAL_size_t_MESSAGE(0, out_len, "Expected empty stdout for error usage");
TEST_ASSERT_TRUE_MESSAGE(err_len > 0, "Expected non-empty stderr for error usage");
/* Check for generic 'try --help' hint and program name; avoid localization issues. */
TEST_ASSERT_TRUE_MESSAGE(contains_substr(err, "--help"), "Missing '--help' hint in stderr");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(err, "chmod"), "Missing program name 'chmod' in stderr");
free(out);
free(err);
}
/* Test that usage text includes key mode syntax and option groups. */
void test_usage_includes_mode_syntax_and_option_groups(void)
{
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int es = -1;
int rc = run_usage_and_capture(EXIT_SUCCESS, &out, &out_len, &err, &err_len, &es);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to run usage in child");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, es, "usage(EXIT_SUCCESS) did not exit with 0");
TEST_ASSERT_TRUE_MESSAGE(out_len > 0, "Expected non-empty stdout for success usage");
/* Look for some characteristic substrings that should be present. */
TEST_ASSERT_TRUE_MESSAGE(contains_substr(out, "MODE[,MODE]"), "Missing 'MODE[,MODE]' synopsis");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(out, "OCTAL-MODE"), "Missing 'OCTAL-MODE' synopsis");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(out, "--reference=RFILE"), "Missing '--reference=RFILE' synopsis");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(out, "-c, --changes"), "Missing '-c, --changes' option");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(out, "-v, --verbose"), "Missing '-v, --verbose' option");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(out, "-f, --silent"), "Missing '-f, --silent' option");
/* The exact phrasing of the "Each MODE is of the form" line may be localized,
but the bracket characters and pattern are likely stable. We'll check for key tokens. */
TEST_ASSERT_TRUE_MESSAGE(contains_substr(out, "[ugoa]"), "Missing token '[ugoa]' in mode description");
TEST_ASSERT_TRUE_MESSAGE(contains_substr(out, "[-+=]"), "Missing token '[-+=]' in mode description");
free(out);
free(err);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_usage_success_outputs_to_stdout_and_exit_zero);
RUN_TEST(test_usage_failure_outputs_try_help_to_stderr_and_exit_status);
RUN_TEST(test_usage_includes_mode_syntax_and_option_groups);
return UNITY_END();
}