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

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

/* Target function under test: void usage(int status); declared earlier in od.c */

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 + 1024 > cap) {
            size_t ncap = cap * 2;
            char *nb = (char *)realloc(buf, ncap);
            if (!nb) { free(buf); return NULL; }
            buf = nb; cap = ncap;
        }
        ssize_t n = read(fd, buf + len, cap - len - 1);
        if (n < 0) {
            if (errno == EINTR) continue;
            /* read error */
            free(buf);
            return NULL;
        }
        if (n == 0) break; /* EOF */
        len += (size_t)n;
    }
    buf[len] = '\0';
    if (out_len) *out_len = len;
    return buf;
}

/* Helper to run usage(status) in a child, capturing stdout and stderr. */
static int run_usage_and_capture(int status, char **out_str, char **err_str, int *exit_code)
{
    int out_pipe[2];
    int err_pipe[2];
    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) {
        /* fork failed */
        close(out_pipe[0]); close(out_pipe[1]);
        close(err_pipe[0]); close(err_pipe[1]);
        return -1;
    }

    if (pid == 0) {
        /* Child: redirect stdout/stderr, set locale, set program name, call usage */
        /* Ensure English output */
        setenv("LC_ALL", "C", 1);
        setenv("LANGUAGE", "C", 1);
        setlocale(LC_ALL, "C");

        /* Prepare redirection */
        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]);

        /* Ensure program_name is set for formatted messages */
        /* set_program_name is declared via headers included earlier in od.c */
        extern void set_program_name(const char *);
        set_program_name("od");

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

        /* Not reached */
        _exit(255);
    }

    /* Parent: close write ends and read output */
    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 status_wait = 0;
    pid_t w = waitpid(pid, &status_wait, 0);
    if (w < 0) {
        if (out_buf) free(out_buf);
        if (err_buf) free(err_buf);
        return -1;
    }

    int code = -1;
    if (WIFEXITED(status_wait)) code = WEXITSTATUS(status_wait);
    else if (WIFSIGNALED(status_wait)) code = 128 + WTERMSIG(status_wait);

    if (exit_code) *exit_code = code;
    if (out_str) *out_str = out_buf; else if (out_buf) free(out_buf);
    if (err_str) *err_str = err_buf; else if (err_buf) free(err_buf);

    return 0;
}

void setUp(void) {
    /* No global setup required */
}

void tearDown(void) {
    /* No global teardown required */
}

static void assert_substr(const char *haystack, const char *needle)
{
    TEST_ASSERT_NOT_NULL_MESSAGE(haystack, "Captured text is NULL");
    TEST_ASSERT_NOT_NULL_MESSAGE(needle, "Needle is NULL");
    TEST_ASSERT_NOT_NULL_MESSAGE(strstr(haystack, needle), "Expected substring not found");
}

void test_usage_success_outputs_help_and_exits_success(void)
{
    char *out = NULL; char *err = NULL; int code = -1;
    int rc = run_usage_and_capture(EXIT_SUCCESS, &out, &err, &code);
    TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "run_usage_and_capture failed");

    /* Verify exit status */
    TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, code);

    /* For success case, help goes to stdout, stderr should be empty */
    TEST_ASSERT_NOT_NULL(out);
    TEST_ASSERT_TRUE_MESSAGE(out[0] != '\0', "stdout should not be empty for --help");
    TEST_ASSERT_NOT_NULL(err);
    TEST_ASSERT_TRUE_MESSAGE(err[0] == '\0', "stderr should be empty for --help");

    /* Verify some key substrings of the help text */
    assert_substr(out, "Usage: od ");
    assert_substr(out, "--address-radix=");
    assert_substr(out, "--endian=");
    assert_substr(out, "--width");

    free(out);
    free(err);
}

void test_usage_failure_outputs_try_help_to_stderr_and_exits_failure(void)
{
    char *out = NULL; char *err = NULL; int code = -1;
    int rc = run_usage_and_capture(EXIT_FAILURE, &out, &err, &code);
    TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "run_usage_and_capture failed");

    TEST_ASSERT_EQUAL_INT(EXIT_FAILURE, code);

    /* For failure case, try-help goes to stderr */
    TEST_ASSERT_NOT_NULL(out);
    /* stdout should be empty or nearly empty */
    TEST_ASSERT_TRUE_MESSAGE(out[0] == '\0', "stdout should be empty when emitting try-help");

    TEST_ASSERT_NOT_NULL(err);
    assert_substr(err, "Try 'od --help' for more information.");

    free(out);
    free(err);
}

void test_usage_custom_nonzero_status_propagates_exit_code_and_try_help(void)
{
    char *out = NULL; char *err = NULL; int code = -1;
    /* Use a non-standard nonzero exit status */
    int wanted = 77;
    int rc = run_usage_and_capture(wanted, &out, &err, &code);
    TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "run_usage_and_capture failed");

    TEST_ASSERT_EQUAL_INT(wanted, code);

    TEST_ASSERT_NOT_NULL(err);
    assert_substr(err, "Try 'od --help' for more information.");

    free(out);
    free(err);
}

int main(void)
{
    UNITY_BEGIN();
    RUN_TEST(test_usage_success_outputs_help_and_exits_success);
    RUN_TEST(test_usage_failure_outputs_try_help_to_stderr_and_exits_failure);
    RUN_TEST(test_usage_custom_nonzero_status_propagates_exit_code_and_try_help);
    return UNITY_END();
}