File size: 6,297 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
#include "../../unity/unity.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>

/* usage(int) is defined in the program source.
   program_name is declared in the program headers included before this file. */

typedef struct {
    char *output;        /* NUL-terminated captured output */
    size_t length;       /* number of bytes captured (not including NUL) */
    int exited;          /* 1 if child exited normally */
    int exit_code;       /* exit status if exited==1 */
    int signaled;        /* 1 if child terminated via signal */
    int term_signal;     /* signal number if signaled==1 */
} ChildResult;

static void free_child_result(ChildResult *r) {
    if (!r) return;
    free(r->output);
    r->output = NULL;
    r->length = 0;
}

/* Read all data from fd into a malloc'd NUL-terminated buffer. */
static char *read_all_from_fd(int fd, size_t *out_len) {
    size_t cap = 4096;
    size_t len = 0;
    char *buf = (char *)malloc(cap);
    if (!buf) {
        return NULL;
    }
    for (;;) {
        if (len + 2048 > cap) {
            size_t new_cap = cap * 2;
            char *new_buf = (char *)realloc(buf, new_cap);
            if (!new_buf) {
                free(buf);
                return NULL;
            }
            buf = new_buf;
            cap = new_cap;
        }
        ssize_t n = read(fd, buf + len, cap - len - 1);
        if (n < 0) {
            if (errno == EINTR) continue;
            free(buf);
            return NULL;
        }
        if (n == 0) break;
        len += (size_t)n;
    }
    buf[len] = '\0';
    if (out_len) *out_len = len;
    return buf;
}

/* Run usage(status) in a child process, capturing either stdout or stderr. */
static ChildResult run_usage_and_capture(int status, int capture_stdout) {
    ChildResult res;
    memset(&res, 0, sizeof(res));

    int pipefd[2];
    if (pipe(pipefd) != 0) {
        /* On failure, keep defaults; tests will fail appropriately. */
        return res;
    }

    fflush(stdout);
    fflush(stderr);
    pid_t pid = fork();
    if (pid == 0) {
        /* Child */
        /* Redirect selected stream to pipe write end. */
        if (capture_stdout) {
            if (dup2(pipefd[1], STDOUT_FILENO) < 0) _exit(127);
        } else {
            if (dup2(pipefd[1], STDERR_FILENO) < 0) _exit(127);
        }
        close(pipefd[0]);
        close(pipefd[1]);

        /* Ensure predictable English output for help text. */
        if (status == EXIT_SUCCESS) {
            setenv("LC_ALL", "C", 1);
            /* Set program_name to expected value for substitutions in messages. */
            extern char const *program_name;
            program_name = "dirname";
        }

        /* Call the target function; it will exit(status). */
        usage(status);

        /* Should never reach here. */
        _exit(255);
    }

    /* Parent */
    close(pipefd[1]);

    /* Read all output from child. */
    res.output = read_all_from_fd(pipefd[0], &res.length);
    close(pipefd[0]);

    int wstatus = 0;
    if (waitpid(pid, &wstatus, 0) >= 0) {
        if (WIFEXITED(wstatus)) {
            res.exited = 1;
            res.exit_code = WEXITSTATUS(wstatus);
        } else if (WIFSIGNALED(wstatus)) {
            res.signaled = 1;
            res.term_signal = WTERMSIG(wstatus);
        }
    }
    return res;
}

void setUp(void) {
    /* Setup code here, or leave empty */
}

void tearDown(void) {
    /* Cleanup code here, or leave empty */
}

/* Test: usage with nonzero status exits with that status and writes something to stderr. */
void test_usage_nonzero_status_exits_and_writes_stderr(void) {
    int status_to_test = 2;
    ChildResult r = run_usage_and_capture(status_to_test, /*capture_stdout=*/0);

    TEST_ASSERT_TRUE_MESSAGE(r.exited || r.signaled, "Child did not terminate properly");
    TEST_ASSERT_TRUE_MESSAGE(!r.signaled, "Child terminated by signal");
    TEST_ASSERT_EQUAL_INT(status_to_test, r.exit_code);

    /* We expect some guidance message on stderr. Ensure it's non-empty. */
    TEST_ASSERT_NOT_NULL(r.output);
    TEST_ASSERT_TRUE_MESSAGE(r.length > 0, "Expected some stderr output for nonzero status");

    free_child_result(&r);
}

/* Test: usage with nonzero status does not write to stdout. */
void test_usage_nonzero_status_no_stdout_output(void) {
    int status_to_test = 1;
    ChildResult r = run_usage_and_capture(status_to_test, /*capture_stdout=*/1);

    TEST_ASSERT_TRUE_MESSAGE(r.exited || r.signaled, "Child did not terminate properly");
    TEST_ASSERT_TRUE_MESSAGE(!r.signaled, "Child terminated by signal");
    TEST_ASSERT_EQUAL_INT(status_to_test, r.exit_code);

    /* For error usage, stdout should remain empty. */
    TEST_ASSERT_NOT_NULL(r.output);
    TEST_ASSERT_EQUAL_UINT64_MESSAGE(0u, (unsigned long long)r.length, "Expected no stdout output for nonzero status");

    free_child_result(&r);
}

/* Test: usage with EXIT_SUCCESS prints help/usage text to stdout and exits 0. */
void test_usage_success_prints_help_and_exits_zero(void) {
    ChildResult r = run_usage_and_capture(EXIT_SUCCESS, /*capture_stdout=*/1);

    TEST_ASSERT_TRUE_MESSAGE(r.exited || r.signaled, "Child did not terminate properly");
    TEST_ASSERT_TRUE_MESSAGE(!r.signaled, "Child terminated by signal");
    TEST_ASSERT_EQUAL_INT(0, r.exit_code);

    TEST_ASSERT_NOT_NULL(r.output);
    /* Basic sanity checks on help content in C locale. */
    TEST_ASSERT_TRUE_MESSAGE(strstr(r.output, "Usage: ") != NULL, "Help text should contain 'Usage:'");
    TEST_ASSERT_TRUE_MESSAGE(strstr(r.output, "Examples:") != NULL, "Help text should contain 'Examples:'");
    TEST_ASSERT_TRUE_MESSAGE(strstr(r.output, "-z, --zero") != NULL, "Help text should document the -z, --zero option");
    /* Ensure program name appears somewhere (set earlier to 'dirname'). */
    TEST_ASSERT_TRUE_MESSAGE(strstr(r.output, "dirname") != NULL, "Help text should mention the program name");

    free_child_result(&r);
}

int main(void) {
    UNITY_BEGIN();
    RUN_TEST(test_usage_nonzero_status_exits_and_writes_stderr);
    RUN_TEST(test_usage_nonzero_status_no_stdout_output);
    RUN_TEST(test_usage_success_prints_help_and_exits_zero);
    return UNITY_END();
}