| #include "../../unity/unity.h" |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <errno.h> |
|
|
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| static char *create_temp_file_with_bytes(const void *data, size_t len) |
| { |
| char tmpl[] = "/tmp/paste_test_in_XXXXXX"; |
| int fd = mkstemp(tmpl); |
| if (fd < 0) { |
| return NULL; |
| } |
| ssize_t w = write(fd, data, len); |
| if (w < 0 || (size_t)w != len) { |
| close(fd); |
| unlink(tmpl); |
| return NULL; |
| } |
| if (close(fd) != 0) { |
| unlink(tmpl); |
| return NULL; |
| } |
| char *path = (char *)malloc(strlen(tmpl) + 1); |
| if (!path) { |
| unlink(tmpl); |
| return NULL; |
| } |
| strcpy(path, tmpl); |
| return path; |
| } |
|
|
| |
| |
| |
| |
| static unsigned char *run_and_capture(char **files, size_t nfiles, |
| const char *delimspec, |
| int use_zero_terminated, |
| size_t *out_len, int *out_ok) |
| { |
| if (!delimspec) delimspec = "\t"; |
|
|
| |
| have_read_stdin = false; |
| line_delim = use_zero_terminated ? '\0' : '\n'; |
| |
| collapse_escapes(delimspec); |
|
|
| |
| char outtmpl[] = "/tmp/paste_test_out_XXXXXX"; |
| int outfd = mkstemp(outtmpl); |
| if (outfd < 0) { |
| return NULL; |
| } |
|
|
| |
| fflush(stdout); |
| int saved_stdout = dup(STDOUT_FILENO); |
| if (saved_stdout < 0) { |
| close(outfd); |
| unlink(outtmpl); |
| return NULL; |
| } |
| if (dup2(outfd, STDOUT_FILENO) < 0) { |
| close(saved_stdout); |
| close(outfd); |
| unlink(outtmpl); |
| return NULL; |
| } |
|
|
| |
| bool ok = paste_parallel(nfiles, files); |
|
|
| |
| fflush(stdout); |
| fsync(STDOUT_FILENO); |
| if (dup2(saved_stdout, STDOUT_FILENO) < 0) { |
| |
| close(saved_stdout); |
| close(outfd); |
| unlink(outtmpl); |
| return NULL; |
| } |
| close(saved_stdout); |
|
|
| |
| struct stat st; |
| if (fstat(outfd, &st) != 0) { |
| close(outfd); |
| unlink(outtmpl); |
| return NULL; |
| } |
| size_t len = (size_t)st.st_size; |
| if (lseek(outfd, 0, SEEK_SET) < 0) { |
| close(outfd); |
| unlink(outtmpl); |
| return NULL; |
| } |
| unsigned char *buf = (unsigned char *)malloc(len ? len : 1); |
| if (!buf) { |
| close(outfd); |
| unlink(outtmpl); |
| return NULL; |
| } |
| size_t total = 0; |
| while (total < len) { |
| ssize_t r = read(outfd, buf + total, len - total); |
| if (r < 0) { |
| free(buf); |
| close(outfd); |
| unlink(outtmpl); |
| return NULL; |
| } |
| if (r == 0) break; |
| total += (size_t)r; |
| } |
| close(outfd); |
| unlink(outtmpl); |
|
|
| *out_len = total; |
| *out_ok = ok ? 1 : 0; |
| return buf; |
| } |
|
|
| void setUp(void) { |
| |
| } |
|
|
| void tearDown(void) { |
| |
| } |
|
|
| static void free_and_unlink_paths(char **paths, size_t n) |
| { |
| if (!paths) return; |
| for (size_t i = 0; i < n; i++) { |
| if (paths[i]) { |
| unlink(paths[i]); |
| free(paths[i]); |
| } |
| } |
| } |
|
|
| |
| void test_paste_parallel_basic_two_files(void) |
| { |
| const char *a = "a1\na2\n"; |
| const char *b = "b1\nb2\n"; |
|
|
| char *paths[2]; |
| paths[0] = create_temp_file_with_bytes(a, strlen(a)); |
| paths[1] = create_temp_file_with_bytes(b, strlen(b)); |
| TEST_ASSERT_NOT_NULL_MESSAGE(paths[0], "failed to create temp file A"); |
| TEST_ASSERT_NOT_NULL_MESSAGE(paths[1], "failed to create temp file B"); |
|
|
| char *files[3] = { paths[0], paths[1], NULL }; |
|
|
| size_t out_len = 0; |
| int out_ok = 0; |
| unsigned char *out = run_and_capture(files, 2, "\t", 0, &out_len, &out_ok); |
|
|
| TEST_ASSERT_NOT_NULL(out); |
| TEST_ASSERT_EQUAL_INT(1, out_ok); |
|
|
| const char *expected = "a1\tb1\na2\tb2\n"; |
| TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len); |
| TEST_ASSERT_EQUAL_INT(0, memcmp(expected, out, out_len)); |
|
|
| free(out); |
| free_and_unlink_paths(paths, 2); |
| } |
|
|
| |
| void test_paste_parallel_second_file_longer(void) |
| { |
| const char *a = "a1\na2\n"; |
| const char *b = "b1\nb2\nb3\n"; |
|
|
| char *paths[2]; |
| paths[0] = create_temp_file_with_bytes(a, strlen(a)); |
| paths[1] = create_temp_file_with_bytes(b, strlen(b)); |
| TEST_ASSERT_NOT_NULL(paths[0]); |
| TEST_ASSERT_NOT_NULL(paths[1]); |
|
|
| char *files[3] = { paths[0], paths[1], NULL }; |
|
|
| size_t out_len = 0; |
| int out_ok = 0; |
| unsigned char *out = run_and_capture(files, 2, "\t", 0, &out_len, &out_ok); |
|
|
| TEST_ASSERT_NOT_NULL(out); |
| TEST_ASSERT_EQUAL_INT(1, out_ok); |
|
|
| const char *expected = "a1\tb1\na2\tb2\n\tb3\n"; |
| TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len); |
| TEST_ASSERT_EQUAL_INT(0, memcmp(expected, out, out_len)); |
|
|
| free(out); |
| free_and_unlink_paths(paths, 2); |
| } |
|
|
| |
| void test_paste_parallel_cycling_delimiters(void) |
| { |
| const char *a = "a\n"; |
| const char *b = "b\n"; |
| const char *c = "c\n"; |
|
|
| char *paths[3]; |
| paths[0] = create_temp_file_with_bytes(a, strlen(a)); |
| paths[1] = create_temp_file_with_bytes(b, strlen(b)); |
| paths[2] = create_temp_file_with_bytes(c, strlen(c)); |
| TEST_ASSERT_NOT_NULL(paths[0]); |
| TEST_ASSERT_NOT_NULL(paths[1]); |
| TEST_ASSERT_NOT_NULL(paths[2]); |
|
|
| char *files[4] = { paths[0], paths[1], paths[2], NULL }; |
|
|
| size_t out_len = 0; |
| int out_ok = 0; |
| unsigned char *out = run_and_capture(files, 3, ",;", 0, &out_len, &out_ok); |
|
|
| TEST_ASSERT_NOT_NULL(out); |
| TEST_ASSERT_EQUAL_INT(1, out_ok); |
|
|
| const char *expected = "a,b;c\n"; |
| TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len); |
| TEST_ASSERT_EQUAL_INT(0, memcmp(expected, out, out_len)); |
|
|
| free(out); |
| free_and_unlink_paths(paths, 3); |
| } |
|
|
| |
| void test_paste_parallel_empty_delimiter(void) |
| { |
| const char *a = "a\n"; |
| const char *b = "b\n"; |
|
|
| char *paths[2]; |
| paths[0] = create_temp_file_with_bytes(a, strlen(a)); |
| paths[1] = create_temp_file_with_bytes(b, strlen(b)); |
| TEST_ASSERT_NOT_NULL(paths[0]); |
| TEST_ASSERT_NOT_NULL(paths[1]); |
|
|
| char *files[3] = { paths[0], paths[1], NULL }; |
|
|
| size_t out_len = 0; |
| int out_ok = 0; |
| |
| unsigned char *out = run_and_capture(files, 2, "\\0", 0, &out_len, &out_ok); |
|
|
| TEST_ASSERT_NOT_NULL(out); |
| TEST_ASSERT_EQUAL_INT(1, out_ok); |
|
|
| const char *expected = "ab\n"; |
| TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len); |
| TEST_ASSERT_EQUAL_INT(0, memcmp(expected, out, out_len)); |
|
|
| free(out); |
| free_and_unlink_paths(paths, 2); |
| } |
|
|
| |
| void test_paste_parallel_missing_newline_on_last_file(void) |
| { |
| const char *a = "1\n2\n"; |
| const char *b = "a\nb"; |
|
|
| char *paths[2]; |
| paths[0] = create_temp_file_with_bytes(a, strlen(a)); |
| paths[1] = create_temp_file_with_bytes(b, strlen(b)); |
| TEST_ASSERT_NOT_NULL(paths[0]); |
| TEST_ASSERT_NOT_NULL(paths[1]); |
|
|
| char *files[3] = { paths[0], paths[1], NULL }; |
|
|
| size_t out_len = 0; |
| int out_ok = 0; |
| unsigned char *out = run_and_capture(files, 2, "\t", 0, &out_len, &out_ok); |
|
|
| TEST_ASSERT_NOT_NULL(out); |
| TEST_ASSERT_EQUAL_INT(1, out_ok); |
|
|
| const char *expected = "1\ta\n2\tb\n"; |
| TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len); |
| TEST_ASSERT_EQUAL_INT(0, memcmp(expected, out, out_len)); |
| |
| TEST_ASSERT_TRUE(out_len > 0); |
| TEST_ASSERT_EQUAL_CHAR('\n', out[out_len - 1]); |
|
|
| free(out); |
| free_and_unlink_paths(paths, 2); |
| } |
|
|
| |
| void test_paste_parallel_zero_terminated(void) |
| { |
| const unsigned char a[] = { 'a','1','\0','a','2','\0' }; |
| const unsigned char b[] = { 'b','1','\0','b','2','\0' }; |
|
|
| char *paths[2]; |
| paths[0] = create_temp_file_with_bytes(a, sizeof(a)); |
| paths[1] = create_temp_file_with_bytes(b, sizeof(b)); |
| TEST_ASSERT_NOT_NULL(paths[0]); |
| TEST_ASSERT_NOT_NULL(paths[1]); |
|
|
| char *files[3] = { paths[0], paths[1], NULL }; |
|
|
| size_t out_len = 0; |
| int out_ok = 0; |
| unsigned char *out = run_and_capture(files, 2, "\t", 1, &out_len, &out_ok); |
|
|
| TEST_ASSERT_NOT_NULL(out); |
| TEST_ASSERT_EQUAL_INT(1, out_ok); |
|
|
| const unsigned char expected[] = { 'a','1','\t','b','1','\0','a','2','\t','b','2','\0' }; |
| TEST_ASSERT_EQUAL_size_t(sizeof(expected), out_len); |
| TEST_ASSERT_EQUAL_INT(0, memcmp(expected, out, out_len)); |
|
|
| free(out); |
| free_and_unlink_paths(paths, 2); |
| } |
|
|
| |
| void test_paste_parallel_three_files_early_close_delbuf(void) |
| { |
| const char *a = "a1\n"; |
| const char *b = "b1\nb2\n"; |
| const char *c = "c1\nc2\nc3\n"; |
|
|
| char *paths[3]; |
| paths[0] = create_temp_file_with_bytes(a, strlen(a)); |
| paths[1] = create_temp_file_with_bytes(b, strlen(b)); |
| paths[2] = create_temp_file_with_bytes(c, strlen(c)); |
| TEST_ASSERT_NOT_NULL(paths[0]); |
| TEST_ASSERT_NOT_NULL(paths[1]); |
| TEST_ASSERT_NOT_NULL(paths[2]); |
|
|
| char *files[4] = { paths[0], paths[1], paths[2], NULL }; |
|
|
| size_t out_len = 0; |
| int out_ok = 0; |
| unsigned char *out = run_and_capture(files, 3, "\t", 0, &out_len, &out_ok); |
|
|
| TEST_ASSERT_NOT_NULL(out); |
| TEST_ASSERT_EQUAL_INT(1, out_ok); |
|
|
| const char *expected = "a1\tb1\tc1\n\tb2\tc2\n\t\tc3\n"; |
| TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len); |
| TEST_ASSERT_EQUAL_INT(0, memcmp(expected, out, out_len)); |
|
|
| free(out); |
| free_and_unlink_paths(paths, 3); |
| } |
|
|
| |
| |
| void test_paste_parallel_empty_delim_not_saved_in_delbuf(void) |
| { |
| const char *a = "a1\n"; |
| const char *b = "b1\nb2\n"; |
| const char *c = "c1\nc2\n"; |
|
|
| char *paths[3]; |
| paths[0] = create_temp_file_with_bytes(a, strlen(a)); |
| paths[1] = create_temp_file_with_bytes(b, strlen(b)); |
| paths[2] = create_temp_file_with_bytes(c, strlen(c)); |
| TEST_ASSERT_NOT_NULL(paths[0]); |
| TEST_ASSERT_NOT_NULL(paths[1]); |
| TEST_ASSERT_NOT_NULL(paths[2]); |
|
|
| char *files[4] = { paths[0], paths[1], paths[2], NULL }; |
|
|
| size_t out_len = 0; |
| int out_ok = 0; |
| |
| unsigned char *out = run_and_capture(files, 3, "\\0,", 0, &out_len, &out_ok); |
|
|
| TEST_ASSERT_NOT_NULL(out); |
| TEST_ASSERT_EQUAL_INT(1, out_ok); |
|
|
| const char *expected = "a1b1,c1\nb2,c2\n"; |
| TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len); |
| TEST_ASSERT_EQUAL_INT(0, memcmp(expected, out, out_len)); |
|
|
| free(out); |
| free_and_unlink_paths(paths, 3); |
| } |
|
|
| int main(void) |
| { |
| UNITY_BEGIN(); |
| RUN_TEST(test_paste_parallel_basic_two_files); |
| RUN_TEST(test_paste_parallel_second_file_longer); |
| RUN_TEST(test_paste_parallel_cycling_delimiters); |
| RUN_TEST(test_paste_parallel_empty_delimiter); |
| RUN_TEST(test_paste_parallel_missing_newline_on_last_file); |
| RUN_TEST(test_paste_parallel_zero_terminated); |
| RUN_TEST(test_paste_parallel_three_files_early_close_delbuf); |
| RUN_TEST(test_paste_parallel_empty_delim_not_saved_in_delbuf); |
| return UNITY_END(); |
| } |