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