#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* The following globals/functions are defined in the program source (paste.c) and are available here since this file is included into the same TU: - static bool paste_serial(size_t, char **) - static int collapse_escapes(const char *) - static bool have_read_stdin; - static unsigned char line_delim; - static char *delims; - static char const *delim_end; */ static char *create_temp_file_with_bytes(const void *data, size_t len) { char tmpl[] = "/tmp/paste_serial_test_XXXXXX"; int fd = mkstemp(tmpl); if (fd < 0) return NULL; ssize_t written = 0; const char *p = (const char *)data; while ((size_t)written < len) { ssize_t r = write(fd, p + written, len - (size_t)written); if (r < 0) { int saved = errno; close(fd); unlink(tmpl); errno = saved; return NULL; } written += r; } if (close(fd) != 0) { int saved = errno; unlink(tmpl); errno = saved; return NULL; } /* Caller must free the returned path and unlink when done. */ return strdup(tmpl); } static char *create_temp_file_with_str(const char *s) { return create_temp_file_with_bytes(s, strlen(s)); } /* Capture stdout while invoking paste_serial(nfiles, files). Returns 0 on success; nonzero on failure to set up capturing. The out_buf is malloc'ed and must be free()'d by caller. Important: This function does not perform any Unity assertions while stdout is redirected. */ static int run_and_capture(char **files, size_t nfiles, char **out_buf, size_t *out_len, bool *ok_ret) { if (!out_buf || !out_len || !ok_ret) return -1; fflush(stdout); int saved_stdout = dup(STDOUT_FILENO); if (saved_stdout < 0) return -1; FILE *tmp = tmpfile(); if (!tmp) { int saved = errno; close(saved_stdout); errno = saved; return -1; } if (dup2(fileno(tmp), STDOUT_FILENO) < 0) { int saved = errno; fclose(tmp); close(saved_stdout); errno = saved; return -1; } /* Call the function under test. */ bool ok = paste_serial(nfiles, files); /* Flush and read captured output. */ fflush(stdout); long endpos = ftell(tmp); if (endpos < 0) { /* Restore stdout before returning. */ dup2(saved_stdout, STDOUT_FILENO); close(saved_stdout); fclose(tmp); return -1; } size_t len = (size_t)endpos; if (fseek(tmp, 0, SEEK_SET) != 0) { dup2(saved_stdout, STDOUT_FILENO); close(saved_stdout); fclose(tmp); return -1; } char *buf = NULL; if (len > 0) { buf = (char *)malloc(len); if (!buf) { dup2(saved_stdout, STDOUT_FILENO); close(saved_stdout); fclose(tmp); return -1; } size_t rd = fread(buf, 1, len, tmp); if (rd != len) { free(buf); dup2(saved_stdout, STDOUT_FILENO); close(saved_stdout); fclose(tmp); return -1; } } else { /* Ensure we return a non-NULL pointer even for zero length? Not necessary. We'll return NULL with out_len==0. */ } /* Restore stdout. */ dup2(saved_stdout, STDOUT_FILENO); close(saved_stdout); fclose(tmp); *out_buf = buf; *out_len = len; *ok_ret = ok; return 0; } static void assert_bytes_equal(const void *expected, size_t expected_len, const void *actual, size_t actual_len) { TEST_ASSERT_EQUAL_size_t_MESSAGE(expected_len, actual_len, "Output length mismatch"); if (expected_len > 0) { TEST_ASSERT_EQUAL_MEMORY_MESSAGE(expected, actual, expected_len, "Output content mismatch"); } } void setUp(void) { /* Reset global state for each test. */ have_read_stdin = false; line_delim = '\n'; /* Default delimiter to TAB, as per paste. */ collapse_escapes("\t"); } void tearDown(void) { /* Nothing to clean specifically; test cases unlink their temp files. */ } /* 1) Basic serial paste with default TAB delimiter. */ void test_paste_serial_basic_tab(void) { char *f = create_temp_file_with_str("a\nb\nc\n"); TEST_ASSERT_NOT_NULL(f); char *files[] = { f }; char *out = NULL; size_t out_len = 0; bool ok = false; int rc = run_and_capture(files, 1, &out, &out_len, &ok); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_TRUE(ok); const char *expected = "a\tb\tc\n"; assert_bytes_equal(expected, strlen(expected), out, out_len); free(out); unlink(f); free(f); } /* 2) File without trailing newline still ends with newline in output. */ void test_paste_serial_no_trailing_newline(void) { char *f = create_temp_file_with_str("a\nb\nc"); TEST_ASSERT_NOT_NULL(f); char *files[] = { f }; char *out = NULL; size_t out_len = 0; bool ok = false; int rc = run_and_capture(files, 1, &out, &out_len, &ok); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_TRUE(ok); const char *expected = "a\tb\tc\n"; assert_bytes_equal(expected, strlen(expected), out, out_len); free(out); unlink(f); free(f); } /* 3) Empty file should produce just a newline. */ void test_paste_serial_empty_file_outputs_newline(void) { char *f = create_temp_file_with_str(""); TEST_ASSERT_NOT_NULL(f); char *files[] = { f }; char *out = NULL; size_t out_len = 0; bool ok = false; int rc = run_and_capture(files, 1, &out, &out_len, &ok); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_TRUE(ok); const char *expected = "\n"; assert_bytes_equal(expected, strlen(expected), out, out_len); free(out); unlink(f); free(f); } /* 4) Multiple files processed serially, outputs concatenated with newline after each file. */ void test_paste_serial_multiple_files(void) { char *f1 = create_temp_file_with_str("x\ny\n"); char *f2 = create_temp_file_with_str("1\n2\n"); TEST_ASSERT_NOT_NULL(f1); TEST_ASSERT_NOT_NULL(f2); char *files[] = { f1, f2 }; char *out = NULL; size_t out_len = 0; bool ok = false; int rc = run_and_capture(files, 2, &out, &out_len, &ok); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_TRUE(ok); const char *expected = "x\ty\n1\t2\n"; assert_bytes_equal(expected, strlen(expected), out, out_len); free(out); unlink(f1); unlink(f2); free(f1); free(f2); } /* 5) Custom delimiters cycle correctly. */ void test_paste_serial_custom_delims_cycle(void) { /* Use ",;" cycling: a,b;c,d\n for four lines. */ collapse_escapes(",;"); char *f = create_temp_file_with_str("a\nb\nc\nd\n"); TEST_ASSERT_NOT_NULL(f); char *files[] = { f }; char *out = NULL; size_t out_len = 0; bool ok = false; int rc = run_and_capture(files, 1, &out, &out_len, &ok); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_TRUE(ok); const char *expected = "a,b;c,d\n"; assert_bytes_equal(expected, strlen(expected), out, out_len); free(out); unlink(f); free(f); } /* 6) EMPTY_DELIM (\0) means no delimiter inserted between lines. */ void test_paste_serial_empty_delimiter(void) { collapse_escapes("\\0"); char *f = create_temp_file_with_str("a\nb\nc\n"); TEST_ASSERT_NOT_NULL(f); char *files[] = { f }; char *out = NULL; size_t out_len = 0; bool ok = false; int rc = run_and_capture(files, 1, &out, &out_len, &ok); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_TRUE(ok); const char *expected = "abc\n"; assert_bytes_equal(expected, strlen(expected), out, out_len); free(out); unlink(f); free(f); } /* 7) Zero-terminated records without trailing NUL: outputs newline at end. */ void test_paste_serial_zero_terminated_no_trailing_nul(void) { line_delim = '\0'; collapse_escapes("\t"); const char data[] = { 'a', '\0', 'b', '\0', 'c' }; /* no trailing NUL */ char *f = create_temp_file_with_bytes(data, sizeof(data)); TEST_ASSERT_NOT_NULL(f); char *files[] = { f }; char *out = NULL; size_t out_len = 0; bool ok = false; int rc = run_and_capture(files, 1, &out, &out_len, &ok); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_TRUE(ok); const char expected[] = { 'a', '\t', 'b', '\t', 'c', '\n' }; assert_bytes_equal(expected, sizeof(expected), out, out_len); free(out); unlink(f); free(f); } /* 8) Zero-terminated records with trailing NUL: no final newline (last char equals line_delim). */ void test_paste_serial_zero_terminated_with_trailing_nul(void) { line_delim = '\0'; collapse_escapes("\t"); const char data[] = { 'a', '\0', 'b', '\0' }; /* trailing NUL */ char *f = create_temp_file_with_bytes(data, sizeof(data)); TEST_ASSERT_NOT_NULL(f); char *files[] = { f }; char *out = NULL; size_t out_len = 0; bool ok = false; int rc = run_and_capture(files, 1, &out, &out_len, &ok); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_TRUE(ok); const char expected[] = { 'a', '\t', 'b', '\0' }; /* no final newline */ assert_bytes_equal(expected, sizeof(expected), out, out_len); free(out); unlink(f); free(f); } /* 9) Reading from standard input via "-" works. */ void test_paste_serial_stdin_input(void) { /* Reset line delimiter and delimiters. */ line_delim = '\n'; collapse_escapes("\t"); char *f = create_temp_file_with_str("p\nq\n"); TEST_ASSERT_NOT_NULL(f); /* Redirect stdin from file f. */ int saved_stdin = dup(STDIN_FILENO); TEST_ASSERT_TRUE_MESSAGE(saved_stdin >= 0, "Failed to save stdin"); int fd = open(f, O_RDONLY); TEST_ASSERT_TRUE_MESSAGE(fd >= 0, "Failed to open temp file for stdin"); TEST_ASSERT_TRUE_MESSAGE(dup2(fd, STDIN_FILENO) >= 0, "Failed to redirect stdin"); close(fd); char *dash = "-"; char *files[] = { dash }; char *out = NULL; size_t out_len = 0; bool ok = false; int rc = run_and_capture(files, 1, &out, &out_len, &ok); /* Restore stdin before any assertions that may output. */ dup2(saved_stdin, STDIN_FILENO); close(saved_stdin); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_TRUE(ok); const char *expected = "p\tq\n"; assert_bytes_equal(expected, strlen(expected), out, out_len); free(out); unlink(f); free(f); } /* 10) Nonexistent file: returns false and writes nothing to stdout. */ void test_paste_serial_open_error_returns_false(void) { char *missing = (char *)"__definitely_missing_paste_serial_test__"; char *files[] = { missing }; char *out = NULL; size_t out_len = 0; bool ok = true; /* expect false */ int rc = run_and_capture(files, 1, &out, &out_len, &ok); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_FALSE(ok); TEST_ASSERT_EQUAL_size_t(0, out_len); /* out may be NULL when out_len==0; that's fine. */ free(out); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_paste_serial_basic_tab); RUN_TEST(test_paste_serial_no_trailing_newline); RUN_TEST(test_paste_serial_empty_file_outputs_newline); RUN_TEST(test_paste_serial_multiple_files); RUN_TEST(test_paste_serial_custom_delims_cycle); RUN_TEST(test_paste_serial_empty_delimiter); RUN_TEST(test_paste_serial_zero_terminated_no_trailing_nul); RUN_TEST(test_paste_serial_zero_terminated_with_trailing_nul); RUN_TEST(test_paste_serial_stdin_input); RUN_TEST(test_paste_serial_open_error_returns_false); return UNITY_END(); }