|
|
#include "../../unity/unity.h" |
|
|
|
|
|
#include <stdio.h> |
|
|
#include <stdlib.h> |
|
|
#include <string.h> |
|
|
#include <unistd.h> |
|
|
#include <fcntl.h> |
|
|
#include <sys/wait.h> |
|
|
#include <errno.h> |
|
|
#include <locale.h> |
|
|
|
|
|
|
|
|
static char *create_tempfile_with_content(const void *data, size_t len) |
|
|
{ |
|
|
char template_path[] = "/tmp/comm_test_XXXXXX"; |
|
|
int fd = mkstemp(template_path); |
|
|
if (fd < 0) |
|
|
return NULL; |
|
|
|
|
|
if (len > 0) { |
|
|
const unsigned char *p = (const unsigned char *)data; |
|
|
size_t written = 0; |
|
|
while (written < len) { |
|
|
ssize_t w = write(fd, p + written, len - written); |
|
|
if (w < 0) { close(fd); unlink(template_path); return NULL; } |
|
|
written += (size_t)w; |
|
|
} |
|
|
} |
|
|
if (close(fd) != 0) { |
|
|
unlink(template_path); |
|
|
return NULL; |
|
|
} |
|
|
|
|
|
char *ret = (char *)malloc(strlen(template_path) + 1); |
|
|
if (!ret) { unlink(template_path); return NULL; } |
|
|
strcpy(ret, template_path); |
|
|
return ret; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static char *read_all_from_fd(int fd, size_t *out_len) |
|
|
{ |
|
|
size_t cap = 4096; |
|
|
size_t size = 0; |
|
|
char *buf = (char *)malloc(cap + 1); |
|
|
if (!buf) return NULL; |
|
|
|
|
|
for (;;) { |
|
|
if (size == cap) { |
|
|
cap *= 2; |
|
|
char *nb = (char *)realloc(buf, cap + 1); |
|
|
if (!nb) { free(buf); return NULL; } |
|
|
buf = nb; |
|
|
} |
|
|
ssize_t r = read(fd, buf + size, cap - size); |
|
|
if (r < 0) { |
|
|
if (errno == EINTR) continue; |
|
|
free(buf); |
|
|
return NULL; |
|
|
} |
|
|
if (r == 0) break; |
|
|
size += (size_t)r; |
|
|
} |
|
|
buf[size] = '\0'; |
|
|
if (out_len) *out_len = size; |
|
|
return buf; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int run_compare_files_capture(const char *file1, const char *file2, |
|
|
const unsigned char *stdin_data, size_t stdin_len, |
|
|
char **out_buf, size_t *out_len, |
|
|
char **err_buf, size_t *err_len) |
|
|
{ |
|
|
int out_pipe[2], err_pipe[2], in_pipe[2] = {-1, -1}; |
|
|
if (pipe(out_pipe) < 0) return -1; |
|
|
if (pipe(err_pipe) < 0) { close(out_pipe[0]); close(out_pipe[1]); return -1; } |
|
|
|
|
|
int need_stdin = ((file1 && strcmp(file1, "-") == 0) || (file2 && strcmp(file2, "-") == 0)); |
|
|
if (need_stdin) { |
|
|
if (pipe(in_pipe) < 0) { |
|
|
close(out_pipe[0]); close(out_pipe[1]); |
|
|
close(err_pipe[0]); close(err_pipe[1]); |
|
|
return -1; |
|
|
} |
|
|
} |
|
|
|
|
|
fflush(stdout); |
|
|
fflush(stderr); |
|
|
pid_t pid = fork(); |
|
|
if (pid < 0) { |
|
|
close(out_pipe[0]); close(out_pipe[1]); |
|
|
close(err_pipe[0]); close(err_pipe[1]); |
|
|
if (need_stdin) { close(in_pipe[0]); close(in_pipe[1]); } |
|
|
return -1; |
|
|
} |
|
|
|
|
|
if (pid == 0) { |
|
|
|
|
|
|
|
|
setenv("LC_ALL", "C", 1); |
|
|
|
|
|
|
|
|
dup2(out_pipe[1], STDOUT_FILENO); |
|
|
dup2(err_pipe[1], STDERR_FILENO); |
|
|
|
|
|
close(out_pipe[0]); close(out_pipe[1]); |
|
|
close(err_pipe[0]); close(err_pipe[1]); |
|
|
|
|
|
if (need_stdin) { |
|
|
dup2(in_pipe[0], STDIN_FILENO); |
|
|
close(in_pipe[0]); |
|
|
close(in_pipe[1]); |
|
|
} |
|
|
|
|
|
|
|
|
char *infiles[2]; |
|
|
infiles[0] = (char *)file1; |
|
|
infiles[1] = (char *)file2; |
|
|
|
|
|
|
|
|
compare_files(infiles); |
|
|
|
|
|
|
|
|
_exit(255); |
|
|
} |
|
|
|
|
|
|
|
|
close(out_pipe[1]); |
|
|
close(err_pipe[1]); |
|
|
if (need_stdin) { |
|
|
close(in_pipe[0]); |
|
|
|
|
|
if (stdin_data && stdin_len > 0) { |
|
|
size_t written = 0; |
|
|
while (written < stdin_len) { |
|
|
ssize_t w = write(in_pipe[1], stdin_data + written, stdin_len - written); |
|
|
if (w < 0) { |
|
|
if (errno == EINTR) continue; |
|
|
break; |
|
|
} |
|
|
written += (size_t)w; |
|
|
} |
|
|
} |
|
|
close(in_pipe[1]); |
|
|
} |
|
|
|
|
|
|
|
|
size_t o_len = 0, e_len = 0; |
|
|
char *o_buf = read_all_from_fd(out_pipe[0], &o_len); |
|
|
char *e_buf = read_all_from_fd(err_pipe[0], &e_len); |
|
|
close(out_pipe[0]); |
|
|
close(err_pipe[0]); |
|
|
|
|
|
int status = -1; |
|
|
int rc = waitpid(pid, &status, 0); |
|
|
int exit_code = -1; |
|
|
if (rc >= 0) { |
|
|
if (WIFEXITED(status)) exit_code = WEXITSTATUS(status); |
|
|
else if (WIFSIGNALED(status)) exit_code = 128 + WTERMSIG(status); |
|
|
else exit_code = -1; |
|
|
} |
|
|
|
|
|
if (out_buf) *out_buf = o_buf; else free(o_buf); |
|
|
if (out_len) *out_len = o_len; |
|
|
if (err_buf) *err_buf = e_buf; else free(e_buf); |
|
|
if (err_len) *err_len = e_len; |
|
|
|
|
|
return exit_code; |
|
|
} |
|
|
|
|
|
|
|
|
void setUp(void) |
|
|
{ |
|
|
|
|
|
only_file_1 = true; |
|
|
only_file_2 = true; |
|
|
both = true; |
|
|
seen_unpairable = false; |
|
|
issued_disorder_warning[0] = false; |
|
|
issued_disorder_warning[1] = false; |
|
|
hard_LC_COLLATE = false; |
|
|
check_input_order = CHECK_ORDER_DEFAULT; |
|
|
col_sep = "\t"; |
|
|
col_sep_len = 1; |
|
|
delim = '\n'; |
|
|
total_option = false; |
|
|
} |
|
|
|
|
|
void tearDown(void) |
|
|
{ |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
void test_compare_files_basic_three_columns(void) |
|
|
{ |
|
|
const char *f1c = "apple\nbanana\ncarrot\n"; |
|
|
const char *f2c = "banana\ncarrot\ndate\n"; |
|
|
char *p1 = create_tempfile_with_content(f1c, strlen(f1c)); |
|
|
char *p2 = create_tempfile_with_content(f2c, strlen(f2c)); |
|
|
TEST_ASSERT_NOT_NULL(p1); |
|
|
TEST_ASSERT_NOT_NULL(p2); |
|
|
|
|
|
char *out = NULL, *err = NULL; |
|
|
size_t out_len = 0, err_len = 0; |
|
|
|
|
|
int ec = run_compare_files_capture(p1, p2, NULL, 0, &out, &out_len, &err, &err_len); |
|
|
|
|
|
const char *expected = "apple\n\t\tbanana\n\t\tcarrot\n\tdate\n"; |
|
|
TEST_ASSERT_EQUAL_INT(0, ec); |
|
|
TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len); |
|
|
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, out, out_len); |
|
|
TEST_ASSERT_EQUAL_size_t(0, err_len); |
|
|
|
|
|
free(out); free(err); |
|
|
unlink(p1); unlink(p2); |
|
|
free(p1); free(p2); |
|
|
} |
|
|
|
|
|
|
|
|
void test_compare_files_suppress_columns_effect(void) |
|
|
{ |
|
|
const char *f1c = "a\nb\nc\n"; |
|
|
const char *f2c = "b\nd\n"; |
|
|
char *p1 = create_tempfile_with_content(f1c, strlen(f1c)); |
|
|
char *p2 = create_tempfile_with_content(f2c, strlen(f2c)); |
|
|
TEST_ASSERT_NOT_NULL(p1); |
|
|
TEST_ASSERT_NOT_NULL(p2); |
|
|
|
|
|
only_file_1 = false; |
|
|
only_file_2 = true; |
|
|
both = false; |
|
|
|
|
|
char *out = NULL, *err = NULL; |
|
|
size_t out_len = 0, err_len = 0; |
|
|
|
|
|
int ec = run_compare_files_capture(p1, p2, NULL, 0, &out, &out_len, &err, &err_len); |
|
|
|
|
|
const char *expected = "d\n"; |
|
|
TEST_ASSERT_EQUAL_INT(0, ec); |
|
|
TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len); |
|
|
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, out, out_len); |
|
|
TEST_ASSERT_EQUAL_size_t(0, err_len); |
|
|
|
|
|
free(out); free(err); |
|
|
unlink(p1); unlink(p2); |
|
|
free(p1); free(p2); |
|
|
} |
|
|
|
|
|
|
|
|
void test_compare_files_custom_delim_and_total(void) |
|
|
{ |
|
|
const char *f1c = "a\nx\n"; |
|
|
const char *f2c = "a\ny\n"; |
|
|
char *p1 = create_tempfile_with_content(f1c, strlen(f1c)); |
|
|
char *p2 = create_tempfile_with_content(f2c, strlen(f2c)); |
|
|
TEST_ASSERT_NOT_NULL(p1); |
|
|
TEST_ASSERT_NOT_NULL(p2); |
|
|
|
|
|
col_sep = "::"; |
|
|
col_sep_len = 2; |
|
|
total_option = true; |
|
|
|
|
|
char *out = NULL, *err = NULL; |
|
|
size_t out_len = 0, err_len = 0; |
|
|
|
|
|
int ec = run_compare_files_capture(p1, p2, NULL, 0, &out, &out_len, &err, &err_len); |
|
|
|
|
|
const char *expected = |
|
|
"::::a\n" |
|
|
"x\n" |
|
|
"::y\n" |
|
|
"1::1::1::total\n"; |
|
|
TEST_ASSERT_EQUAL_INT(0, ec); |
|
|
TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len); |
|
|
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, out, out_len); |
|
|
TEST_ASSERT_EQUAL_size_t(0, err_len); |
|
|
|
|
|
free(out); free(err); |
|
|
unlink(p1); unlink(p2); |
|
|
free(p1); free(p2); |
|
|
} |
|
|
|
|
|
|
|
|
void test_compare_files_zero_terminated(void) |
|
|
{ |
|
|
const unsigned char f1c[] = { 'a', '\0', 'c', '\0' }; |
|
|
const unsigned char f2c[] = { 'b', '\0', 'c', '\0' }; |
|
|
char *p1 = create_tempfile_with_content(f1c, sizeof(f1c)); |
|
|
char *p2 = create_tempfile_with_content(f2c, sizeof(f2c)); |
|
|
TEST_ASSERT_NOT_NULL(p1); |
|
|
TEST_ASSERT_NOT_NULL(p2); |
|
|
|
|
|
delim = '\0'; |
|
|
col_sep = "\t"; |
|
|
col_sep_len = 1; |
|
|
|
|
|
char *out = NULL, *err = NULL; |
|
|
size_t out_len = 0, err_len = 0; |
|
|
|
|
|
int ec = run_compare_files_capture(p1, p2, NULL, 0, &out, &out_len, &err, &err_len); |
|
|
|
|
|
unsigned char expected[] = { 'a', 0, '\t', 'b', 0, '\t', '\t', 'c', 0 }; |
|
|
TEST_ASSERT_EQUAL_INT(0, ec); |
|
|
TEST_ASSERT_EQUAL_size_t(sizeof(expected), out_len); |
|
|
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, out, out_len); |
|
|
TEST_ASSERT_EQUAL_size_t(0, err_len); |
|
|
|
|
|
free(out); free(err); |
|
|
unlink(p1); unlink(p2); |
|
|
free(p1); free(p2); |
|
|
} |
|
|
|
|
|
|
|
|
void test_compare_files_check_order_default_warns_and_exits_nonzero(void) |
|
|
{ |
|
|
const char *f1c = "b\nA\n"; |
|
|
char *p1 = create_tempfile_with_content(f1c, strlen(f1c)); |
|
|
char *p2 = create_tempfile_with_content("", 0); |
|
|
TEST_ASSERT_NOT_NULL(p1); |
|
|
TEST_ASSERT_NOT_NULL(p2); |
|
|
|
|
|
check_input_order = CHECK_ORDER_DEFAULT; |
|
|
hard_LC_COLLATE = false; |
|
|
|
|
|
char *out = NULL, *err = NULL; |
|
|
size_t out_len = 0, err_len = 0; |
|
|
|
|
|
int ec = run_compare_files_capture(p1, p2, NULL, 0, &out, &out_len, &err, &err_len); |
|
|
|
|
|
const char *expected_out = "b\nA\n"; |
|
|
TEST_ASSERT_TRUE(ec != 0); |
|
|
TEST_ASSERT_EQUAL_size_t(strlen(expected_out), out_len); |
|
|
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_out, out, out_len); |
|
|
|
|
|
TEST_ASSERT_TRUE(err_len > 0); |
|
|
|
|
|
free(out); free(err); |
|
|
unlink(p1); unlink(p2); |
|
|
free(p1); free(p2); |
|
|
} |
|
|
|
|
|
|
|
|
void test_compare_files_check_order_enabled_fatal(void) |
|
|
{ |
|
|
const char *f1c = "b\nA\n"; |
|
|
char *p1 = create_tempfile_with_content(f1c, strlen(f1c)); |
|
|
char *p2 = create_tempfile_with_content("", 0); |
|
|
TEST_ASSERT_NOT_NULL(p1); |
|
|
TEST_ASSERT_NOT_NULL(p2); |
|
|
|
|
|
check_input_order = CHECK_ORDER_ENABLED; |
|
|
hard_LC_COLLATE = false; |
|
|
|
|
|
char *out = NULL, *err = NULL; |
|
|
size_t out_len = 0, err_len = 0; |
|
|
|
|
|
int ec = run_compare_files_capture(p1, p2, NULL, 0, &out, &out_len, &err, &err_len); |
|
|
|
|
|
|
|
|
const char *expected_out = "b\n"; |
|
|
TEST_ASSERT_TRUE(ec != 0); |
|
|
TEST_ASSERT_EQUAL_size_t(strlen(expected_out), out_len); |
|
|
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_out, out, out_len); |
|
|
TEST_ASSERT_TRUE(err_len > 0); |
|
|
|
|
|
free(out); free(err); |
|
|
unlink(p1); unlink(p2); |
|
|
free(p1); free(p2); |
|
|
} |
|
|
|
|
|
|
|
|
void test_compare_files_check_order_disabled_allows_unsorted(void) |
|
|
{ |
|
|
const char *f1c = "b\nA\n"; |
|
|
char *p1 = create_tempfile_with_content(f1c, strlen(f1c)); |
|
|
char *p2 = create_tempfile_with_content("", 0); |
|
|
TEST_ASSERT_NOT_NULL(p1); |
|
|
TEST_ASSERT_NOT_NULL(p2); |
|
|
|
|
|
check_input_order = CHECK_ORDER_DISABLED; |
|
|
hard_LC_COLLATE = false; |
|
|
|
|
|
char *out = NULL, *err = NULL; |
|
|
size_t out_len = 0, err_len = 0; |
|
|
|
|
|
int ec = run_compare_files_capture(p1, p2, NULL, 0, &out, &out_len, &err, &err_len); |
|
|
|
|
|
const char *expected_out = "b\nA\n"; |
|
|
TEST_ASSERT_EQUAL_INT(0, ec); |
|
|
TEST_ASSERT_EQUAL_size_t(strlen(expected_out), out_len); |
|
|
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_out, out, out_len); |
|
|
TEST_ASSERT_EQUAL_size_t(0, err_len); |
|
|
|
|
|
free(out); free(err); |
|
|
unlink(p1); unlink(p2); |
|
|
free(p1); free(p2); |
|
|
} |
|
|
|
|
|
|
|
|
void test_compare_files_stdin_handling(void) |
|
|
{ |
|
|
const char *f1c = "a\nb\n"; |
|
|
const char *stdin_c = "b\nc\n"; |
|
|
char *p1 = create_tempfile_with_content(f1c, strlen(f1c)); |
|
|
TEST_ASSERT_NOT_NULL(p1); |
|
|
|
|
|
char *out = NULL, *err = NULL; |
|
|
size_t out_len = 0, err_len = 0; |
|
|
|
|
|
int ec = run_compare_files_capture(p1, "-", (const unsigned char *)stdin_c, strlen(stdin_c), |
|
|
&out, &out_len, &err, &err_len); |
|
|
|
|
|
const char *expected = "a\n\t\tb\n\tc\n"; |
|
|
TEST_ASSERT_EQUAL_INT(0, ec); |
|
|
TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len); |
|
|
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, out, out_len); |
|
|
TEST_ASSERT_EQUAL_size_t(0, err_len); |
|
|
|
|
|
free(out); free(err); |
|
|
unlink(p1); |
|
|
free(p1); |
|
|
} |
|
|
|
|
|
int main(void) |
|
|
{ |
|
|
UNITY_BEGIN(); |
|
|
RUN_TEST(test_compare_files_basic_three_columns); |
|
|
RUN_TEST(test_compare_files_suppress_columns_effect); |
|
|
RUN_TEST(test_compare_files_custom_delim_and_total); |
|
|
RUN_TEST(test_compare_files_zero_terminated); |
|
|
RUN_TEST(test_compare_files_check_order_default_warns_and_exits_nonzero); |
|
|
RUN_TEST(test_compare_files_check_order_enabled_fatal); |
|
|
RUN_TEST(test_compare_files_check_order_disabled_allows_unsorted); |
|
|
RUN_TEST(test_compare_files_stdin_handling); |
|
|
return UNITY_END(); |
|
|
} |