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