| #include "../../unity/unity.h" |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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; |
| } |
| |
| return strdup(tmpl); |
| } |
|
|
| static char *create_temp_file_with_str(const char *s) |
| { |
| return create_temp_file_with_bytes(s, strlen(s)); |
| } |
|
|
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| bool ok = paste_serial(nfiles, files); |
|
|
| |
| fflush(stdout); |
| long endpos = ftell(tmp); |
| if (endpos < 0) |
| { |
| |
| 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 |
| { |
| |
| |
| } |
|
|
| |
| 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) { |
| |
| have_read_stdin = false; |
| line_delim = '\n'; |
| |
| collapse_escapes("\t"); |
| } |
|
|
| void tearDown(void) { |
| |
| } |
|
|
| |
| 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); |
| } |
|
|
| |
| 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); |
| } |
|
|
| |
| 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); |
| } |
|
|
| |
| 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); |
| } |
|
|
| |
| void test_paste_serial_custom_delims_cycle(void) |
| { |
| |
| 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); |
| } |
|
|
| |
| 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); |
| } |
|
|
| |
| void test_paste_serial_zero_terminated_no_trailing_nul(void) |
| { |
| line_delim = '\0'; |
| collapse_escapes("\t"); |
|
|
| const char data[] = { 'a', '\0', 'b', '\0', 'c' }; |
| 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); |
| } |
|
|
| |
| void test_paste_serial_zero_terminated_with_trailing_nul(void) |
| { |
| line_delim = '\0'; |
| collapse_escapes("\t"); |
|
|
| const char data[] = { 'a', '\0', 'b', '\0' }; |
| 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' }; |
| assert_bytes_equal(expected, sizeof(expected), out, out_len); |
|
|
| free(out); |
| unlink(f); |
| free(f); |
| } |
|
|
| |
| void test_paste_serial_stdin_input(void) |
| { |
| |
| line_delim = '\n'; |
| collapse_escapes("\t"); |
|
|
| char *f = create_temp_file_with_str("p\nq\n"); |
| TEST_ASSERT_NOT_NULL(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); |
|
|
| |
| 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); |
| } |
|
|
| |
| 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; |
|
|
| 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); |
| |
| 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(); |
| } |