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