File size: 7,448 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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#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 <sys/select.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>

/* Forward declaration of the function under test */
void usage (int status);

/* Helpers */

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

typedef struct {
  char *out_buf;
  size_t out_len;
  char *err_buf;
  size_t err_len;
  int exit_code;   /* as returned by WEXITSTATUS */
  bool exited;     /* true if child exited normally */
} usage_result;

/* Append data to a buffer, reallocating as needed. bufp points to malloc'd buffer which will be null-terminated. */
static int append_data(char **bufp, size_t *lenp, const char *data, size_t n) {
  size_t oldlen = *lenp;
  size_t newlen = oldlen + n;
  char *newbuf = realloc(*bufp, newlen + 1);
  if (!newbuf) return -1;
  memcpy(newbuf + oldlen, data, n);
  newbuf[newlen] = '\0';
  *bufp = newbuf;
  *lenp = newlen;
  return 0;
}

/* Run usage(status) in a child, capturing stdout and stderr. */
static int run_usage_capture(int status, usage_result *res) {
  memset(res, 0, sizeof(*res));
  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) {
    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 to pipes and call usage. No Unity calls here. */
    /* Close read ends */
    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 original write fds after dup2 */
    close(out_pipe[1]);
    close(err_pipe[1]);

    usage(status);
    /* Should not reach here, but in case, exit */
    _exit(126);
  }

  /* Parent */
  close(out_pipe[1]);
  close(err_pipe[1]);

  int out_fd = out_pipe[0];
  int err_fd = err_pipe[0];

  bool out_open = true, err_open = true;
  char buf[4096];

  while (out_open || err_open) {
    fd_set rfds;
    FD_ZERO(&rfds);
    int maxfd = -1;
    if (out_open) { FD_SET(out_fd, &rfds); if (out_fd > maxfd) maxfd = out_fd; }
    if (err_open) { FD_SET(err_fd, &rfds); if (err_fd > maxfd) maxfd = err_fd; }

    int sel = select(maxfd + 1, &rfds, NULL, NULL, NULL);
    if (sel < 0) {
      if (errno == EINTR) continue;
      break;
    }

    if (out_open && FD_ISSET(out_fd, &rfds)) {
      ssize_t r = read(out_fd, buf, sizeof buf);
      if (r > 0) {
        if (append_data(&res->out_buf, &res->out_len, buf, (size_t)r) != 0) {
          /* Allocation failure: stop reading to avoid hang */
          break;
        }
      } else if (r == 0) {
        close(out_fd);
        out_open = false;
      } else if (errno != EINTR) {
        close(out_fd);
        out_open = false;
      }
    }

    if (err_open && FD_ISSET(err_fd, &rfds)) {
      ssize_t r = read(err_fd, buf, sizeof buf);
      if (r > 0) {
        if (append_data(&res->err_buf, &res->err_len, buf, (size_t)r) != 0) {
          break;
        }
      } else if (r == 0) {
        close(err_fd);
        err_open = false;
      } else if (errno != EINTR) {
        close(err_fd);
        err_open = false;
      }
    }
  }

  int wstatus = 0;
  pid_t w = waitpid(pid, &wstatus, 0);
  if (w == pid && WIFEXITED(wstatus)) {
    res->exited = true;
    res->exit_code = WEXITSTATUS(wstatus);
  } else {
    res->exited = false;
  }

  return 0;
}

static void free_usage_result(usage_result *res) {
  free(res->out_buf);
  free(res->err_buf);
  memset(res, 0, sizeof(*res));
}

/* Unity fixtures */
void setUp(void) {
  /* No setup required */
}

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

/* Tests */

static void assert_stdout_contains(usage_result *res, const char *needle) {
  TEST_ASSERT_NOT_NULL_MESSAGE(res->out_buf, "Expected stdout to have content but it was NULL");
  TEST_ASSERT_TRUE_MESSAGE(str_contains(res->out_buf, needle), "Expected stdout to contain required substring");
}

void test_usage_success_exits_zero_and_prints_help(void) {
  usage_result r;
  TEST_ASSERT_EQUAL_INT(0, run_usage_capture(EXIT_SUCCESS, &r));
  TEST_ASSERT_TRUE_MESSAGE(r.exited, "Child did not exit normally");
  TEST_ASSERT_EQUAL_INT_MESSAGE(EXIT_SUCCESS, r.exit_code, "usage(EXIT_SUCCESS) did not exit with EXIT_SUCCESS");

  /* Should print help to stdout and nothing to stderr */
  TEST_ASSERT_NOT_NULL_MESSAGE(r.out_buf, "Expected help on stdout");
  TEST_ASSERT_EQUAL_UINT_MESSAGE(0u, r.err_len, "Expected empty stderr on success");

  /* Check for some key help content */
  TEST_ASSERT_TRUE_MESSAGE(str_contains(r.out_buf, "Usage:"), "Help text missing 'Usage:'");
  TEST_ASSERT_TRUE_MESSAGE(str_contains(r.out_buf, "FILE PATTERN"), "Help usage line missing file/pattern info");
  /* Option examples */
  TEST_ASSERT_TRUE_MESSAGE(str_contains(r.out_buf, "--prefix=PREFIX"), "Help text missing --prefix option");
  TEST_ASSERT_TRUE_MESSAGE(str_contains(r.out_buf, "--suffix-format"), "Help text missing --suffix-format option");

  free_usage_result(&r);
}

void test_usage_failure_exits_nonzero_and_emits_try_help_to_stderr(void) {
  usage_result r;
  TEST_ASSERT_EQUAL_INT(0, run_usage_capture(EXIT_FAILURE, &r));
  TEST_ASSERT_TRUE_MESSAGE(r.exited, "Child did not exit normally");
  TEST_ASSERT_EQUAL_INT_MESSAGE(EXIT_FAILURE, r.exit_code, "usage(EXIT_FAILURE) did not exit with EXIT_FAILURE");

  /* Should not print full help to stdout */
  TEST_ASSERT_EQUAL_UINT_MESSAGE(0u, r.out_len, "Expected empty stdout on failure path");

  /* Should print a 'try --help' style hint to stderr; content may be localized, but should be non-empty */
  TEST_ASSERT_TRUE_MESSAGE(r.err_len > 0, "Expected non-empty stderr hint on failure path");
  /* Heuristic: should mention --help */
  TEST_ASSERT_TRUE_MESSAGE(str_contains(r.err_buf, "--help"), "Expected stderr hint to include '--help'");

  free_usage_result(&r);
}

void test_usage_success_includes_patterns_and_options_section(void) {
  usage_result r;
  TEST_ASSERT_EQUAL_INT(0, run_usage_capture(EXIT_SUCCESS, &r));
  TEST_ASSERT_TRUE_MESSAGE(r.exited, "Child did not exit normally");
  TEST_ASSERT_EQUAL_INT_MESSAGE(EXIT_SUCCESS, r.exit_code, "usage(EXIT_SUCCESS) did not exit with EXIT_SUCCESS");

  /* Check presence of sections and some exemplar lines */
  assert_stdout_contains(&r, "Each PATTERN may be:");
  TEST_ASSERT_TRUE_MESSAGE(str_contains(r.out_buf, "/REGEXP/"), "Help text missing /REGEXP/ pattern example");
  TEST_ASSERT_TRUE_MESSAGE(str_contains(r.out_buf, "{INTEGER}"), "Help text missing {INTEGER} example");
  TEST_ASSERT_TRUE_MESSAGE(str_contains(r.out_buf, "--quiet"), "Help text missing --quiet option");
  TEST_ASSERT_TRUE_MESSAGE(str_contains(r.out_buf, "--elide-empty-files"), "Help text missing --elide-empty-files option");

  free_usage_result(&r);
}

/* Unity main */
int main(void) {
  UNITY_BEGIN();
  RUN_TEST(test_usage_success_exits_zero_and_prints_help);
  RUN_TEST(test_usage_failure_exits_nonzero_and_emits_try_help_to_stderr);
  RUN_TEST(test_usage_success_includes_patterns_and_options_section);
  return UNITY_END();
}