#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* usage() and program_name are defined in the compiled unit (echo.c). */ extern void usage (int status); extern char const *program_name; /* Helper to run usage(EXIT_SUCCESS) in a child, capture stdout, and return it. - pname: the desired program_name to set in the child. - status_out: returns the raw wait status from waitpid (WIFEXITED/WEXITSTATUS to inspect). - out_len: length of returned buffer (not including terminating NUL). Returns: malloc'd NUL-terminated buffer containing child's stdout (caller must free). */ static char *run_usage_and_capture(const char *pname, int *status_out, size_t *out_len) { int pipefd[2]; if (pipe(pipefd) != 0) { /* In case of pipe failure, we still want the test to continue with a clear failure. */ *status_out = -1; *out_len = 0; return NULL; } fflush(stdout); fflush(stderr); pid_t pid = fork(); if (pid < 0) { /* fork failed */ close(pipefd[0]); close(pipefd[1]); *status_out = -1; *out_len = 0; return NULL; } if (pid == 0) { /* Child: redirect stdout to pipe and invoke usage(EXIT_SUCCESS). */ /* Avoid Unity macros here (they write to stdout). */ /* Force C locale for predictable (English) output. */ setenv("LC_ALL", "C", 1); setlocale(LC_ALL, "C"); /* Set program_name as desired. Cast away const to assign global pointer. */ program_name = pname; /* Redirect stdout to pipe write end. */ close(pipefd[0]); if (dup2(pipefd[1], STDOUT_FILENO) < 0) { _exit(127); } /* Close the original write end fd after dup. */ close(pipefd[1]); /* Ensure stdout is unbuffered to reduce risk of partial writes on exit. */ setvbuf(stdout, NULL, _IONBF, 0); /* Call the function under test; it should exit(EXIT_SUCCESS). */ usage(EXIT_SUCCESS); /* Should not reach here, but just in case. */ _exit(125); } /* Parent: read all data from pipe, wait for child. */ close(pipefd[1]); size_t cap = 4096; size_t len = 0; char *buf = (char *)malloc(cap); if (!buf) { close(pipefd[0]); /* Still wait for child to avoid zombie. */ int st; (void)waitpid(pid, &st, 0); *status_out = -1; *out_len = 0; return NULL; } for (;;) { if (len + 1024 > cap) { size_t new_cap = cap * 2; char *new_buf = (char *)realloc(buf, new_cap); if (!new_buf) { free(buf); close(pipefd[0]); int st; (void)waitpid(pid, &st, 0); *status_out = -1; *out_len = 0; return NULL; } buf = new_buf; cap = new_cap; } ssize_t n = read(pipefd[0], buf + len, cap - len); if (n > 0) { len += (size_t)n; continue; } else if (n == 0) { break; /* EOF */ } else { if (errno == EINTR) continue; /* Read error */ break; } } close(pipefd[0]); int st = 0; (void)waitpid(pid, &st, 0); /* NUL-terminate */ if (len + 1 > cap) { char *new_buf = (char *)realloc(buf, len + 1); if (!new_buf) { free(buf); *status_out = st; *out_len = len; return NULL; } buf = new_buf; } buf[len] = '\0'; *status_out = st; *out_len = len; return buf; } /* Unity fixtures */ void setUp(void) { /* Force C locale globally too; child will set this as well. */ setenv("LC_ALL", "C", 1); setlocale(LC_ALL, "C"); } void tearDown(void) { /* No global cleanup needed. */ } /* Tests */ void test_usage_exits_success_and_prints_basic_help(void) { int st = -1; size_t out_len = 0; char *out = run_usage_and_capture("echo", &st, &out_len); TEST_ASSERT_NOT_NULL_MESSAGE(out, "Failed to capture output from usage()."); TEST_ASSERT_TRUE_MESSAGE(WIFEXITED(st), "usage() did not exit normally."); TEST_ASSERT_EQUAL_INT_MESSAGE(EXIT_SUCCESS, WEXITSTATUS(st), "usage() did not exit with EXIT_SUCCESS."); TEST_ASSERT_TRUE_MESSAGE(out_len > 0, "usage() produced no output."); /* Check key content. */ TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "Usage: echo"), "Missing 'Usage: echo' heading."); TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "Echo the STRING"), "Missing brief description of echo."); TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "\n -n"), "Missing -n option description."); free(out); } void test_usage_uses_given_program_name_twice_in_synopsis(void) { int st = -1; size_t out_len = 0; const char *pname = "my-echo"; char *out = run_usage_and_capture(pname, &st, &out_len); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_TRUE(WIFEXITED(st)); TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, WEXITSTATUS(st)); /* Count occurrences of the program name; synopsis prints it twice ("Usage:" and "or:"). */ int count = 0; const char *p = out; while ((p = strstr(p, pname)) != NULL) { count++; p += strlen(pname); } TEST_ASSERT_TRUE_MESSAGE(count >= 2, "Program name did not appear at least twice in the synopsis."); free(out); } void test_usage_includes_e_and_E_option_lines(void) { int st = -1; size_t out_len = 0; char *out = run_usage_and_capture("echo", &st, &out_len); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_TRUE(WIFEXITED(st)); TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, WEXITSTATUS(st)); /* Look for the option list entries. We search for lines beginning with two spaces and the option. */ TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "\n -e"), "Missing -e option line."); TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "\n -E"), "Missing -E option line."); free(out); } void test_usage_includes_backslash_sequences_section(void) { int st = -1; size_t out_len = 0; char *out = run_usage_and_capture("echo", &st, &out_len); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_TRUE(WIFEXITED(st)); TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, WEXITSTATUS(st)); /* Check for some representative escape sequence descriptions. */ TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "\\\\ backslash"), "Missing backslash escape description."); TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "\\n new line"), "Missing newline escape description."); TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "\\t horizontal tab"), "Missing tab escape description."); TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "\\0NNN byte with octal value NNN"), "Missing octal escape description."); TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "\\xHH byte with hexadecimal value HH"), "Missing hex escape description."); free(out); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_usage_exits_success_and_prints_basic_help); RUN_TEST(test_usage_uses_given_program_name_twice_in_synopsis); RUN_TEST(test_usage_includes_e_and_E_option_lines); RUN_TEST(test_usage_includes_backslash_sequences_section); return UNITY_END(); }