#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* Globals from the program under test (same translation unit). */ /* static int input_desc; */ /* static char const *infile; */ /* Helper: create a temporary file, write data, return fd and path. */ static int test_create_temp_file(char *tmpl_out, size_t tmpl_out_size, const char *prefix, const char *data, size_t data_len) { char tmpl[256]; snprintf(tmpl, sizeof(tmpl), "/tmp/%sXXXXXX", prefix ? prefix : "copy_cat_"); if (strlen(tmpl) + 1 > tmpl_out_size) { return -1; } strcpy(tmpl_out, tmpl); int fd = mkstemp(tmpl_out); if (fd < 0) { return -1; } /* Ensure file is empty then write if data provided. */ if (ftruncate(fd, 0) != 0) { close(fd); unlink(tmpl_out); return -1; } if (data && data_len > 0) { ssize_t wr = write(fd, data, data_len); if (wr < 0 || (size_t)wr != data_len) { close(fd); unlink(tmpl_out); return -1; } /* Reset offset for readers. */ if (lseek(fd, 0, SEEK_SET) < 0) { close(fd); unlink(tmpl_out); return -1; } } return fd; } /* Helper: read entire file content into malloc'd buffer; returns length via out_len. */ static char *test_read_fd_all(int fd, size_t *out_len) { if (lseek(fd, 0, SEEK_SET) < 0) { /* If not seekable, attempt reading anyway. */ } const size_t chunk = 4096; size_t cap = chunk; size_t len = 0; char *buf = (char *)malloc(cap); if (!buf) return NULL; while (1) { if (len + chunk > cap) { size_t ncap = cap * 2; char *nbuf = (char *)realloc(buf, ncap); if (!nbuf) { free(buf); return NULL; } buf = nbuf; cap = ncap; } ssize_t rd = read(fd, buf + len, chunk); if (rd < 0) { free(buf); return NULL; } if (rd == 0) break; len += (size_t)rd; } *out_len = len; return buf; } /* Helper: redirect stdout to newfd; save original in *saved_out. Returns 0 on success. */ static int test_redirect_stdout(int newfd, int *saved_out) { int so = dup(STDOUT_FILENO); if (so < 0) return -1; if (dup2(newfd, STDOUT_FILENO) < 0) { int e = errno; close(so); errno = e; return -1; } *saved_out = so; return 0; } /* Helper: restore stdout from saved_out. */ static void test_restore_stdout(int saved_out) { dup2(saved_out, STDOUT_FILENO); close(saved_out); } void setUp(void) { /* Nothing to setup globally. */ } void tearDown(void) { /* Nothing to cleanup globally. */ } /* Test: unsupported path using a pipe for input (and no stdout redirection). With the write end closed (EOF on first read), copy_file_range should return 0 (no bytes available) at first call, causing copy_cat to return 0. */ void test_copy_cat_unsupported_or_empty_pipe_returns_0(void) { int pipefd[2]; TEST_ASSERT_EQUAL_INT(0, pipe(pipefd)); /* Make the input a pipe with EOF: close write end immediately. */ close(pipefd[1]); /* Set inputs for copy_cat. */ infile = "pipe_in"; input_desc = pipefd[0]; int ret = copy_cat(); /* Clean up. */ close(pipefd[0]); /* Expect 0 because first copy_file_range should see EOF on pipe (0 bytes). */ TEST_ASSERT_EQUAL_INT(0, ret); } /* Test: empty regular file input with stdout redirected to a regular file. copy_file_range should return 0 immediately (EOF) and copy_cat returns 0. Output must remain empty. */ void test_copy_cat_empty_file_returns_0_and_no_output(void) { char in_path[256]; char out_path[256]; int in_fd = -1, out_fd = -1; int saved_out = -1; in_fd = test_create_temp_file(in_path, sizeof(in_path), "cc_in_empty_", NULL, 0); TEST_ASSERT_TRUE_MESSAGE(in_fd >= 0, "Failed to create input temp file"); out_fd = test_create_temp_file(out_path, sizeof(out_path), "cc_out_empty_", NULL, 0); TEST_ASSERT_TRUE_MESSAGE(out_fd >= 0, "Failed to create output temp file"); /* Prepare globals. */ infile = in_path; input_desc = in_fd; /* Redirect stdout to out_fd (no assertions while redirected). */ int redir_ok = test_redirect_stdout(out_fd, &saved_out); TEST_ASSERT_EQUAL_INT_MESSAGE(0, redir_ok, "Failed to redirect stdout"); int ret = copy_cat(); /* Restore stdout before making assertions. */ test_restore_stdout(saved_out); /* Validate return and that output is empty. */ TEST_ASSERT_EQUAL_INT(0, ret); /* Check output content length is zero. */ size_t out_len = 0; if (lseek(out_fd, 0, SEEK_SET) >= 0) { /* ensure read from start */ } char *out_data = test_read_fd_all(out_fd, &out_len); TEST_ASSERT_NOT_NULL(out_data); TEST_ASSERT_EQUAL_UINT32(0u, (unsigned int)out_len); free(out_data); /* Cleanup */ close(in_fd); close(out_fd); unlink(in_path); unlink(out_path); } /* Test: non-empty regular file input with stdout redirected to a regular file. If copy_file_range is supported, expect return 1 and the output to match input. If unsupported on this platform, function returns 0 and output should remain empty. */ void test_copy_cat_regular_files_success_or_unsupported(void) { const char *payload = "The quick brown fox jumps over the lazy dog.\nSecond line.\n"; size_t payload_len = strlen(payload); char in_path[256]; char out_path[256]; int in_fd = -1, out_fd = -1; int saved_out = -1; in_fd = test_create_temp_file(in_path, sizeof(in_path), "cc_in_", payload, payload_len); TEST_ASSERT_TRUE_MESSAGE(in_fd >= 0, "Failed to create input file"); out_fd = test_create_temp_file(out_path, sizeof(out_path), "cc_out_", NULL, 0); TEST_ASSERT_TRUE_MESSAGE(out_fd >= 0, "Failed to create output file"); infile = in_path; input_desc = in_fd; int redir_ok = test_redirect_stdout(out_fd, &saved_out); TEST_ASSERT_EQUAL_INT_MESSAGE(0, redir_ok, "Failed to redirect stdout"); int ret = copy_cat(); test_restore_stdout(saved_out); /* Read back output. */ if (lseek(out_fd, 0, SEEK_SET) >= 0) { /* reset offset */ } size_t out_len = 0; char *out_data = test_read_fd_all(out_fd, &out_len); TEST_ASSERT_NOT_NULL(out_data); if (ret == 1) { /* Supported path: content must match exactly. */ TEST_ASSERT_EQUAL_UINT32((unsigned int)payload_len, (unsigned int)out_len); TEST_ASSERT_EQUAL_MEMORY(payload, out_data, payload_len); } else if (ret == 0) { /* Unsupported path: output should still be empty (no bytes copied). */ TEST_ASSERT_EQUAL_UINT32(0u, (unsigned int)out_len); } else { /* Serious error not expected in normal environments. */ TEST_FAIL_MESSAGE("copy_cat returned -1 (unexpected serious error)"); } free(out_data); close(in_fd); close(out_fd); unlink(in_path); unlink(out_path); } /* Test: invalid input descriptor (EBADF) should be treated as unsupported and return 0. */ void test_copy_cat_invalid_input_fd_returns_0(void) { infile = "invalid_fd"; input_desc = -1; int ret = copy_cat(); TEST_ASSERT_EQUAL_INT(0, ret); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_copy_cat_unsupported_or_empty_pipe_returns_0); RUN_TEST(test_copy_cat_empty_file_returns_0_and_no_output); RUN_TEST(test_copy_cat_regular_files_success_or_unsupported); RUN_TEST(test_copy_cat_invalid_input_fd_returns_0); return UNITY_END(); }