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