| #include "../../unity/unity.h" |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
|
|
| |
| static void clear_outlist(void) |
| { |
| |
| while (outlist_head.next) { |
| struct outlist *tmp = outlist_head.next; |
| outlist_head.next = tmp->next; |
| free(tmp); |
| } |
| outlist_end = &outlist_head; |
| } |
|
|
| |
| static void reset_join_state(void) |
| { |
| |
| output_separator = " "; |
| output_seplen = 1; |
| empty_filler = NULL; |
|
|
| |
| tab.ch = 0; |
| tab.len = 0; |
|
|
| |
| ignore_case = false; |
| hard_LC_COLLATE = false; |
| autoformat = false; |
| join_header_lines = false; |
| print_pairables = true; |
| print_unpairables_1 = false; |
| print_unpairables_2 = false; |
| seen_unpairable = false; |
| issued_disorder_warning[0] = false; |
| issued_disorder_warning[1] = false; |
| |
| check_input_order = CHECK_ORDER_DEFAULT; |
|
|
| |
| join_field_1 = 0; |
| join_field_2 = 0; |
|
|
| |
| prevline[0] = NULL; |
| prevline[1] = NULL; |
| line_no[0] = 0; |
| line_no[1] = 0; |
| spareline[0] = NULL; |
| spareline[1] = NULL; |
|
|
| |
| eolchar = '\n'; |
|
|
| |
| clear_outlist(); |
|
|
| |
| g_names[0] = (char *)"f1"; |
| g_names[1] = (char *)"f2"; |
| } |
|
|
| |
| static FILE* make_stream_from_str(const char* data) |
| { |
| FILE* f = tmpfile(); |
| if (!f) return NULL; |
| size_t len = strlen(data); |
| if (len > 0) { |
| if (fwrite(data, 1, len, f) != len) { |
| fclose(f); |
| return NULL; |
| } |
| } |
| rewind(f); |
| return f; |
| } |
|
|
| |
| static char* capture_join_output(FILE* fp1, FILE* fp2) |
| { |
| fflush(stdout); |
| int saved_stdout = dup(STDOUT_FILENO); |
| if (saved_stdout < 0) { |
| return NULL; |
| } |
|
|
| FILE* cap = tmpfile(); |
| if (!cap) { |
| close(saved_stdout); |
| return NULL; |
| } |
| int cap_fd = fileno(cap); |
| if (cap_fd < 0) { |
| fclose(cap); |
| close(saved_stdout); |
| return NULL; |
| } |
|
|
| if (dup2(cap_fd, STDOUT_FILENO) < 0) { |
| fclose(cap); |
| close(saved_stdout); |
| return NULL; |
| } |
|
|
| |
| join(fp1, fp2); |
|
|
| |
| fflush(stdout); |
| fflush(cap); |
| if (fseek(cap, 0, SEEK_END) != 0) { |
| |
| dup2(saved_stdout, STDOUT_FILENO); |
| close(saved_stdout); |
| fclose(cap); |
| return NULL; |
| } |
| long sz = ftell(cap); |
| if (sz < 0) { |
| dup2(saved_stdout, STDOUT_FILENO); |
| close(saved_stdout); |
| fclose(cap); |
| return NULL; |
| } |
| rewind(cap); |
|
|
| char* buf = (char*)malloc((size_t)sz + 1); |
| if (!buf) { |
| dup2(saved_stdout, STDOUT_FILENO); |
| close(saved_stdout); |
| fclose(cap); |
| return NULL; |
| } |
| size_t rd = fread(buf, 1, (size_t)sz, cap); |
| buf[rd] = '\0'; |
|
|
| |
| dup2(saved_stdout, STDOUT_FILENO); |
| close(saved_stdout); |
| fclose(cap); |
|
|
| return buf; |
| } |
|
|
| void setUp(void) { |
| reset_join_state(); |
| } |
| void tearDown(void) { |
| |
| } |
|
|
| |
| void test_join_basic_pairables(void) |
| { |
| const char* s1 = "a 1\nb 2\n"; |
| const char* s2 = "a x\nb y\n"; |
|
|
| FILE* f1 = make_stream_from_str(s1); |
| FILE* f2 = make_stream_from_str(s2); |
| TEST_ASSERT_NOT_NULL(f1); |
| TEST_ASSERT_NOT_NULL(f2); |
|
|
| print_pairables = true; |
| join_field_1 = 0; |
| join_field_2 = 0; |
|
|
| char* out = capture_join_output(f1, f2); |
|
|
| fclose(f1); |
| fclose(f2); |
|
|
| TEST_ASSERT_NOT_NULL(out); |
| TEST_ASSERT_EQUAL_STRING("a 1 x\nb 2 y\n", out); |
| free(out); |
| } |
|
|
| |
| void test_join_unpairables_file1(void) |
| { |
| const char* s1 = "a 1\nb 2\nc 3\n"; |
| const char* s2 = "a x\nb y\n"; |
|
|
| FILE* f1 = make_stream_from_str(s1); |
| FILE* f2 = make_stream_from_str(s2); |
| TEST_ASSERT_NOT_NULL(f1); |
| TEST_ASSERT_NOT_NULL(f2); |
|
|
| print_pairables = true; |
| print_unpairables_1 = true; |
| join_field_1 = 0; |
| join_field_2 = 0; |
|
|
| char* out = capture_join_output(f1, f2); |
|
|
| fclose(f1); |
| fclose(f2); |
|
|
| TEST_ASSERT_NOT_NULL(out); |
| TEST_ASSERT_EQUAL_STRING("a 1 x\nb 2 y\nc 3\n", out); |
| free(out); |
| } |
|
|
| |
| void test_join_multiple_matches_cross_product(void) |
| { |
| const char* s1 = "a 1\na 2\n"; |
| const char* s2 = "a x\na y\n"; |
|
|
| FILE* f1 = make_stream_from_str(s1); |
| FILE* f2 = make_stream_from_str(s2); |
| TEST_ASSERT_NOT_NULL(f1); |
| TEST_ASSERT_NOT_NULL(f2); |
|
|
| print_pairables = true; |
|
|
| char* out = capture_join_output(f1, f2); |
|
|
| fclose(f1); |
| fclose(f2); |
|
|
| TEST_ASSERT_NOT_NULL(out); |
| |
| TEST_ASSERT_EQUAL_STRING("a 1 x\na 1 y\na 2 x\na 2 y\n", out); |
| free(out); |
| } |
|
|
| |
| void test_join_header_and_autoformat(void) |
| { |
| const char* s1 = "ID A\n1 AA\n2 BB\n"; |
| const char* s2 = "ID B\n1 XX\n3 YY\n"; |
|
|
| FILE* f1 = make_stream_from_str(s1); |
| FILE* f2 = make_stream_from_str(s2); |
| TEST_ASSERT_NOT_NULL(f1); |
| TEST_ASSERT_NOT_NULL(f2); |
|
|
| join_header_lines = true; |
| autoformat = true; |
| print_pairables = true; |
|
|
| char* out = capture_join_output(f1, f2); |
|
|
| fclose(f1); |
| fclose(f2); |
|
|
| TEST_ASSERT_NOT_NULL(out); |
| |
| TEST_ASSERT_EQUAL_STRING("ID A B\n1 AA XX\n", out); |
| free(out); |
| } |
|
|
| |
| void test_join_ignore_case(void) |
| { |
| const char* s1 = "A 1\n"; |
| const char* s2 = "a x\n"; |
|
|
| FILE* f1 = make_stream_from_str(s1); |
| FILE* f2 = make_stream_from_str(s2); |
| TEST_ASSERT_NOT_NULL(f1); |
| TEST_ASSERT_NOT_NULL(f2); |
|
|
| ignore_case = true; |
| print_pairables = true; |
|
|
| char* out = capture_join_output(f1, f2); |
|
|
| fclose(f1); |
| fclose(f2); |
|
|
| TEST_ASSERT_NOT_NULL(out); |
| TEST_ASSERT_EQUAL_STRING("A 1 x\n", out); |
| free(out); |
| } |
|
|
| |
| void test_join_empty_field_with_filler_and_tab(void) |
| { |
| const char* s1 = "a\t\t1\n"; |
| const char* s2 = "a\tx\n"; |
|
|
| FILE* f1 = make_stream_from_str(s1); |
| FILE* f2 = make_stream_from_str(s2); |
| TEST_ASSERT_NOT_NULL(f1); |
| TEST_ASSERT_NOT_NULL(f2); |
|
|
| |
| tab.ch = '\t'; |
| tab.len = 1; |
| empty_filler = "<>"; |
| print_pairables = true; |
|
|
| char* out = capture_join_output(f1, f2); |
|
|
| fclose(f1); |
| fclose(f2); |
|
|
| TEST_ASSERT_NOT_NULL(out); |
| |
| TEST_ASSERT_EQUAL_STRING("a <> 1 x\n", out); |
| free(out); |
| } |
|
|
| |
| void test_join_custom_outlist_selection(void) |
| { |
| const char* s1 = "a 1 2\n"; |
| const char* s2 = "a 3 4\n"; |
|
|
| FILE* f1 = make_stream_from_str(s1); |
| FILE* f2 = make_stream_from_str(s2); |
| TEST_ASSERT_NOT_NULL(f1); |
| TEST_ASSERT_NOT_NULL(f2); |
|
|
| print_pairables = true; |
|
|
| |
| add_field(2, 2); |
| add_field(0, 0); |
| add_field(1, 1); |
|
|
| char* out = capture_join_output(f1, f2); |
|
|
| fclose(f1); |
| fclose(f2); |
|
|
| TEST_ASSERT_NOT_NULL(out); |
| TEST_ASSERT_EQUAL_STRING("4 a 2\n", out); |
| free(out); |
| } |
|
|
| int main(void) |
| { |
| UNITY_BEGIN(); |
|
|
| RUN_TEST(test_join_basic_pairables); |
| RUN_TEST(test_join_unpairables_file1); |
| RUN_TEST(test_join_multiple_matches_cross_product); |
| RUN_TEST(test_join_header_and_autoformat); |
| RUN_TEST(test_join_ignore_case); |
| RUN_TEST(test_join_empty_field_with_filler_and_tab); |
| RUN_TEST(test_join_custom_outlist_selection); |
|
|
| return UNITY_END(); |
| } |