File size: 6,437 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
#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 <errno.h>
#include <locale.h>

#include "progname.h" /* for set_program_name */

static bool str_contains(const char *haystack, const char *needle)
{
    if (!haystack || !needle) return false;
    return strstr(haystack, needle) != NULL;
}

static char *read_all_from_fd(int fd, size_t *out_len)
{
    size_t cap = 1024;
    size_t len = 0;
    char *buf = (char *)malloc(cap + 1);
    if (!buf) return NULL;

    for (;;)
    {
        if (len == cap)
        {
            cap *= 2;
            char *nb = (char *)realloc(buf, cap + 1);
            if (!nb) { free(buf); return NULL; }
            buf = nb;
        }
        ssize_t n = read(fd, buf + len, cap - len);
        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 stdout and stderr.
   Returns 0 on success, -1 on failure to set up/run. */
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 *out_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;
    }
    else if (pid == 0)
    {
        /* Child: redirect stdout/stderr to pipes and call usage(status). */
        /* Ensure our stdio streams go to the pipes. */
        if (dup2(out_pipe[1], STDOUT_FILENO) < 0) _exit(127);
        if (dup2(err_pipe[1], STDERR_FILENO) < 0) _exit(127);

        /* Close all pipe fds now in child. */
        close(out_pipe[0]); close(out_pipe[1]);
        close(err_pipe[0]); close(err_pipe[1]);

        /* Call usage; it will exit(status). */
        usage(status);
        _exit(127); /* Should not reach here */
    }
    else
    {
        /* Parent: close write ends, read outputs, and wait. */
        close(out_pipe[1]);
        close(err_pipe[1]);

        size_t out_len = 0, err_len = 0;
        char *out_buf = read_all_from_fd(out_pipe[0], &out_len);
        char *err_buf = read_all_from_fd(err_pipe[0], &err_len);

        close(out_pipe[0]);
        close(err_pipe[0]);

        int wstatus = 0;
        if (waitpid(pid, &wstatus, 0) < 0)
        {
            free(out_buf);
            free(err_buf);
            return -1;
        }

        if (out_stdout) *out_stdout = out_buf; else free(out_buf);
        if (out_stdout_len) *out_stdout_len = out_len;
        if (out_stderr) *out_stderr = err_buf; else free(err_buf);
        if (out_stderr_len) *out_stderr_len = err_len;

        if (out_exit_status)
        {
            if (WIFEXITED(wstatus))
                *out_exit_status = WEXITSTATUS(wstatus);
            else
                *out_exit_status = -1;
        }

        return 0;
    }
}

/* Unity fixtures */
void setUp(void) {
    /* Ensure stable, English output. */
    setenv("LC_ALL", "C", 1);
    setlocale(LC_ALL, "C");

    /* Ensure program_name is set to "fold" for usage output. */
    set_program_name("fold");
}

void tearDown(void) {
    /* No-op */
}

/* Tests */

void test_usage_failure_emits_try_help_on_stderr(void)
{
    char *out = NULL, *err = NULL;
    size_t out_len = 0, err_len = 0;
    int exit_code = -1;

    int rc = run_usage_and_capture(EXIT_FAILURE, &out, &out_len, &err, &err_len, &exit_code);
    TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to run usage() in child.");

    /* Should exit with EXIT_FAILURE */
    TEST_ASSERT_EQUAL_INT(EXIT_FAILURE, exit_code);

    /* stdout should be empty; stderr should contain the try-help hint with program name and --help */
    TEST_ASSERT_TRUE_MESSAGE(out_len == 0 || (out && out[0] == '\0'), "stdout should be empty on failure.");
    TEST_ASSERT_TRUE_MESSAGE(err && err_len > 0, "stderr should not be empty on failure.");
    TEST_ASSERT_TRUE_MESSAGE(str_contains(err, "Try"), "stderr should contain 'Try' hint.");
    TEST_ASSERT_TRUE_MESSAGE(str_contains(err, "--help"), "stderr should mention --help.");
    TEST_ASSERT_TRUE_MESSAGE(str_contains(err, "fold"), "stderr should include the program name.");

    free(out);
    free(err);
}

void test_usage_success_prints_help_to_stdout_and_exits_zero(void)
{
    char *out = NULL, *err = NULL;
    size_t out_len = 0, err_len = 0;
    int exit_code = -1;

    int rc = run_usage_and_capture(EXIT_SUCCESS, &out, &out_len, &err, &err_len, &exit_code);
    TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to run usage() in child.");

    /* Should exit with 0 */
    TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, exit_code);

    /* stdout should contain the usage banner and options; stderr should be empty */
    TEST_ASSERT_TRUE_MESSAGE(out && out_len > 0, "stdout should not be empty on success.");
    TEST_ASSERT_TRUE_MESSAGE(err_len == 0 || (err && err[0] == '\0'), "stderr should be empty on success.");

    /* Check for key substrings from the help text */
    TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "Usage: fold"), "Help should include 'Usage: fold'.");
    TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "Wrap input lines"), "Help should describe wrapping input lines.");
    TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "-b, --bytes"), "Help should list --bytes option.");
    TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "-c, --characters"), "Help should list --characters option.");
    TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "-s, --spaces"), "Help should list --spaces option.");
    TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "-w, --width"), "Help should list --width option.");

    free(out);
    free(err);
}

int main(void)
{
    UNITY_BEGIN();
    RUN_TEST(test_usage_failure_emits_try_help_on_stderr);
    RUN_TEST(test_usage_success_prints_help_to_stdout_and_exits_zero);
    return UNITY_END();
}