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