File size: 8,548 Bytes
78d2150 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
#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();
} |