|
|
#include "../../unity/unity.h" |
|
|
#include <stdlib.h> |
|
|
#include <string.h> |
|
|
#include <unistd.h> |
|
|
#include <sys/wait.h> |
|
|
#include <fcntl.h> |
|
|
#include <errno.h> |
|
|
#include <stdbool.h> |
|
|
#include <stdio.h> |
|
|
|
|
|
|
|
|
extern void usage (int status); |
|
|
|
|
|
|
|
|
extern void set_program_name (const char *argv0); |
|
|
|
|
|
|
|
|
typedef struct RunResult { |
|
|
int exit_status; |
|
|
char *out; |
|
|
size_t out_len; |
|
|
char *err; |
|
|
size_t err_len; |
|
|
} RunResult; |
|
|
|
|
|
static char* read_all_from_fd (int fd, size_t *len_out) { |
|
|
size_t cap = 4096; |
|
|
size_t len = 0; |
|
|
char *buf = (char*) malloc (cap); |
|
|
if (!buf) { |
|
|
|
|
|
buf = (char*) malloc (1); |
|
|
if (!buf) _exit(255); |
|
|
buf[0] = '\0'; |
|
|
if (len_out) *len_out = 0; |
|
|
return buf; |
|
|
} |
|
|
|
|
|
for (;;) { |
|
|
char tmp[2048]; |
|
|
ssize_t n = read (fd, tmp, sizeof tmp); |
|
|
if (n < 0) { |
|
|
if (errno == EINTR) continue; |
|
|
break; |
|
|
} |
|
|
if (n == 0) break; |
|
|
if (len + (size_t) n + 1 > cap) { |
|
|
size_t new_cap = (len + (size_t) n + 1) * 2; |
|
|
char *nb = (char*) realloc (buf, new_cap); |
|
|
if (!nb) { |
|
|
free (buf); |
|
|
buf = (char*) malloc (1); |
|
|
if (!buf) _exit(255); |
|
|
buf[0] = '\0'; |
|
|
if (len_out) *len_out = 0; |
|
|
return buf; |
|
|
} |
|
|
buf = nb; |
|
|
cap = new_cap; |
|
|
} |
|
|
memcpy (buf + len, tmp, (size_t) n); |
|
|
len += (size_t) n; |
|
|
} |
|
|
buf[len] = '\0'; |
|
|
if (len_out) *len_out = len; |
|
|
return buf; |
|
|
} |
|
|
|
|
|
static void free_run_result (RunResult *r) { |
|
|
if (!r) return; |
|
|
free (r->out); |
|
|
free (r->err); |
|
|
r->out = NULL; |
|
|
r->err = NULL; |
|
|
} |
|
|
|
|
|
|
|
|
static void run_usage_capture (int status, RunResult *res) { |
|
|
int out_pipe[2] = {-1, -1}; |
|
|
int err_pipe[2] = {-1, -1}; |
|
|
|
|
|
if (pipe (out_pipe) != 0) { |
|
|
TEST_FAIL_MESSAGE("Failed to create stdout pipe"); |
|
|
} |
|
|
if (pipe (err_pipe) != 0) { |
|
|
close (out_pipe[0]); close (out_pipe[1]); |
|
|
TEST_FAIL_MESSAGE("Failed to create stderr pipe"); |
|
|
} |
|
|
|
|
|
pid_t pid = fork (); |
|
|
if (pid < 0) { |
|
|
close (out_pipe[0]); close (out_pipe[1]); |
|
|
close (err_pipe[0]); close (err_pipe[1]); |
|
|
TEST_FAIL_MESSAGE("fork() failed"); |
|
|
} |
|
|
|
|
|
if (pid == 0) { |
|
|
|
|
|
|
|
|
close (out_pipe[0]); |
|
|
close (err_pipe[0]); |
|
|
|
|
|
|
|
|
if (dup2 (out_pipe[1], STDOUT_FILENO) < 0) _exit (253); |
|
|
if (dup2 (err_pipe[1], STDERR_FILENO) < 0) _exit (252); |
|
|
|
|
|
|
|
|
close (out_pipe[1]); |
|
|
close (err_pipe[1]); |
|
|
|
|
|
|
|
|
usage (status); |
|
|
|
|
|
|
|
|
_exit (251); |
|
|
} |
|
|
|
|
|
|
|
|
close (out_pipe[1]); |
|
|
close (err_pipe[1]); |
|
|
|
|
|
res->out = read_all_from_fd (out_pipe[0], &res->out_len); |
|
|
res->err = read_all_from_fd (err_pipe[0], &res->err_len); |
|
|
|
|
|
close (out_pipe[0]); |
|
|
close (err_pipe[0]); |
|
|
|
|
|
int wstatus; |
|
|
for (;;) { |
|
|
if (waitpid (pid, &wstatus, 0) < 0) { |
|
|
if (errno == EINTR) continue; |
|
|
res->exit_status = -1; |
|
|
break; |
|
|
} |
|
|
if (WIFEXITED (wstatus)) { |
|
|
res->exit_status = WEXITSTATUS (wstatus); |
|
|
} else if (WIFSIGNALED (wstatus)) { |
|
|
res->exit_status = 128 + WTERMSIG (wstatus); |
|
|
} else { |
|
|
res->exit_status = -1; |
|
|
} |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
void setUp(void) { |
|
|
|
|
|
set_program_name ("nohup"); |
|
|
} |
|
|
|
|
|
void tearDown(void) { |
|
|
|
|
|
} |
|
|
|
|
|
static void assert_buffer_empty (const char *label, const char *buf, size_t len) { |
|
|
|
|
|
TEST_ASSERT_NOT_NULL_MESSAGE(buf, "Captured buffer is NULL"); |
|
|
if (len == 0) { |
|
|
TEST_ASSERT_EQUAL_UINT64_MESSAGE(0u, (unsigned long long)strlen(buf), label); |
|
|
} else { |
|
|
TEST_ASSERT_MESSAGE(len > 0, "Expected non-zero length"); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void test_usage_failure_status_one(void) { |
|
|
RunResult r = {0}; |
|
|
run_usage_capture (1, &r); |
|
|
|
|
|
TEST_ASSERT_EQUAL_INT_MESSAGE(1, r.exit_status, "usage(1) exit status mismatch"); |
|
|
assert_buffer_empty ("stdout should be empty", r.out, r.out_len); |
|
|
TEST_ASSERT_TRUE_MESSAGE(r.err_len > 0, "stderr should be non-empty for failure"); |
|
|
TEST_ASSERT_NOT_EQUAL_MESSAGE('\0', r.err[0], "stderr should contain data"); |
|
|
|
|
|
free_run_result (&r); |
|
|
} |
|
|
|
|
|
|
|
|
void test_usage_failure_status_127(void) { |
|
|
RunResult r = {0}; |
|
|
run_usage_capture (127, &r); |
|
|
|
|
|
TEST_ASSERT_EQUAL_INT_MESSAGE(127, r.exit_status, "usage(127) exit status mismatch"); |
|
|
assert_buffer_empty ("stdout should be empty", r.out, r.out_len); |
|
|
TEST_ASSERT_TRUE_MESSAGE(r.err_len > 0, "stderr should be non-empty for failure"); |
|
|
TEST_ASSERT_NOT_EQUAL_MESSAGE('\0', r.err[0], "stderr should contain data"); |
|
|
|
|
|
free_run_result (&r); |
|
|
} |
|
|
|
|
|
|
|
|
void test_usage_success_status_zero(void) { |
|
|
RunResult r = {0}; |
|
|
run_usage_capture (0, &r); |
|
|
|
|
|
TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.exit_status, "usage(0) exit status mismatch"); |
|
|
TEST_ASSERT_TRUE_MESSAGE(r.out_len > 0, "stdout should be non-empty for success"); |
|
|
TEST_ASSERT_NOT_EQUAL_MESSAGE('\0', r.out[0], "stdout should contain data"); |
|
|
assert_buffer_empty ("stderr should be empty", r.err, r.err_len); |
|
|
|
|
|
free_run_result (&r); |
|
|
} |
|
|
|
|
|
int main(void) { |
|
|
UNITY_BEGIN(); |
|
|
RUN_TEST(test_usage_failure_status_one); |
|
|
RUN_TEST(test_usage_failure_status_127); |
|
|
RUN_TEST(test_usage_success_status_zero); |
|
|
return UNITY_END(); |
|
|
} |