coreutils / tests /echo /tests_for_usage.c
AryaWu's picture
Upload folder using huggingface_hub
78d2150 verified
#include "../../unity/unity.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <locale.h>
/* 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();
}