| | #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(); |
| | } |