| #include "../../unity/unity.h" |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
|
|
| |
| static struct line *T_make_line(const char **fields, size_t n) |
| { |
| struct line *l = (struct line *)calloc(1, sizeof(*l)); |
| TEST_ASSERT_NOT_NULL_MESSAGE(l, "alloc line"); |
| l->nfields = (idx_t)n; |
| l->nfields_allocated = (idx_t)n; |
| if (n) { |
| l->fields = (struct field *)calloc(n, sizeof(struct field)); |
| TEST_ASSERT_NOT_NULL_MESSAGE(l->fields, "alloc fields"); |
| for (size_t i = 0; i < n; ++i) { |
| size_t len = strlen(fields[i]); |
| char *s = (char *)malloc(len + 1); |
| TEST_ASSERT_NOT_NULL_MESSAGE(s, "alloc field string"); |
| memcpy(s, fields[i], len + 1); |
| l->fields[i].beg = s; |
| l->fields[i].len = (idx_t)len; |
| } |
| } |
| return l; |
| } |
|
|
| static void T_free_line(struct line *l) |
| { |
| if (!l) return; |
| for (size_t i = 0; i < (size_t)l->nfields; ++i) { |
| free(l->fields[i].beg); |
| } |
| free(l->fields); |
| free(l); |
| } |
|
|
| |
| static void T_reset_outlist(void) |
| { |
| struct outlist *o = outlist_head.next; |
| while (o) { |
| struct outlist *next = o->next; |
| free(o); |
| o = next; |
| } |
| outlist_head.next = NULL; |
| outlist_end = &outlist_head; |
| } |
|
|
| |
| typedef struct { char *data; size_t len; } TCap; |
| static TCap T_capture_prjoin(const struct line *l1, const struct line *l2) |
| { |
| TCap r = {0, 0}; |
| fflush(stdout); |
|
|
| int saved_fd = dup(fileno(stdout)); |
| FILE *tmp = tmpfile(); |
| if (!tmp || saved_fd < 0) { |
| if (tmp) fclose(tmp); |
| return r; |
| } |
|
|
| int tmp_fd = fileno(tmp); |
| fflush(stdout); |
| dup2(tmp_fd, fileno(stdout)); |
|
|
| |
| prjoin(l1, l2); |
|
|
| fflush(stdout); |
| long pos = ftell(tmp); |
| if (pos < 0) pos = 0; |
| r.len = (size_t)pos; |
| r.data = (char *)malloc(r.len + 1); |
| if (r.data) { |
| memset(r.data, 0, r.len + 1); |
| rewind(tmp); |
| fread(r.data, 1, r.len, tmp); |
| } |
|
|
| fflush(stdout); |
| dup2(saved_fd, fileno(stdout)); |
| close(saved_fd); |
| fclose(tmp); |
|
|
| return r; |
| } |
|
|
| static void T_free_cap(TCap *c) |
| { |
| if (!c) return; |
| free(c->data); |
| c->data = NULL; |
| c->len = 0; |
| } |
|
|
| |
| static const char *saved_output_separator; |
| static idx_t saved_output_seplen; |
| static char saved_eolchar; |
| static const char *saved_empty_filler; |
| static ptrdiff_t saved_join_field_1; |
| static ptrdiff_t saved_join_field_2; |
| static bool saved_autoformat; |
| static idx_t saved_autocount_1; |
| static idx_t saved_autocount_2; |
|
|
| void setUp(void) |
| { |
| |
| saved_output_separator = output_separator; |
| saved_output_seplen = output_seplen; |
| saved_eolchar = eolchar; |
| saved_empty_filler = empty_filler; |
| saved_join_field_1 = join_field_1; |
| saved_join_field_2 = join_field_2; |
| saved_autoformat = autoformat; |
| saved_autocount_1 = autocount_1; |
| saved_autocount_2 = autocount_2; |
|
|
| |
| T_reset_outlist(); |
|
|
| |
| output_separator = " "; |
| output_seplen = 1; |
| eolchar = '\n'; |
| empty_filler = NULL; |
| join_field_1 = 0; |
| join_field_2 = 0; |
| autoformat = false; |
| autocount_1 = 0; |
| autocount_2 = 0; |
| } |
|
|
| void tearDown(void) |
| { |
| |
| T_reset_outlist(); |
|
|
| output_separator = saved_output_separator; |
| output_seplen = saved_output_seplen; |
| eolchar = saved_eolchar; |
| empty_filler = saved_empty_filler; |
| join_field_1 = saved_join_field_1; |
| join_field_2 = saved_join_field_2; |
| autoformat = saved_autoformat; |
| autocount_1 = saved_autocount_1; |
| autocount_2 = saved_autocount_2; |
| } |
|
|
| |
| static void test_prjoin_default_basic(void) |
| { |
| const char *f1[] = {"join", "a", "b"}; |
| const char *f2[] = {"join", "c"}; |
| struct line *l1 = T_make_line(f1, 3); |
| struct line *l2 = T_make_line(f2, 2); |
|
|
| |
| TCap cap = T_capture_prjoin(l1, l2); |
|
|
| TEST_ASSERT_NOT_NULL(cap.data); |
| TEST_ASSERT_EQUAL_STRING("join a b c\n", cap.data); |
|
|
| T_free_cap(&cap); |
| T_free_line(l1); |
| T_free_line(l2); |
| } |
|
|
| |
| static void test_prjoin_default_blank_first(void) |
| { |
| const char *f2[] = {"K", "x"}; |
| struct line *l2 = T_make_line(f2, 2); |
|
|
| |
| TCap cap = T_capture_prjoin(&uni_blank, l2); |
|
|
| TEST_ASSERT_NOT_NULL(cap.data); |
| TEST_ASSERT_EQUAL_STRING("K x\n", cap.data); |
|
|
| T_free_cap(&cap); |
| T_free_line(l2); |
| } |
|
|
| |
| static void test_prjoin_outlist_custom_order_and_separator(void) |
| { |
| const char *f1[] = {"J", "A", "B"}; |
| const char *f2[] = {"X", "C"}; |
| struct line *l1 = T_make_line(f1, 3); |
| struct line *l2 = T_make_line(f2, 2); |
|
|
| output_separator = ","; |
| output_seplen = 1; |
| join_field_1 = 0; |
|
|
| char spec[] = "1.3,0,2.2,1.2"; |
| add_field_list(spec); |
|
|
| TCap cap = T_capture_prjoin(l1, l2); |
|
|
| TEST_ASSERT_NOT_NULL(cap.data); |
| TEST_ASSERT_EQUAL_STRING("B,J,C,A\n", cap.data); |
|
|
| T_free_cap(&cap); |
| T_free_line(l1); |
| T_free_line(l2); |
| } |
|
|
| |
| static void test_prjoin_outlist_missing_and_empty_filler(void) |
| { |
| const char *f1[] = {"J"}; |
| const char *f2[] = {"J", ""}; |
| struct line *l1 = T_make_line(f1, 1); |
| struct line *l2 = T_make_line(f2, 2); |
|
|
| empty_filler = "<EMPTY>"; |
| output_separator = "|"; |
| output_seplen = 1; |
|
|
| char spec[] = "1.2,2.2,2.3"; |
| add_field_list(spec); |
|
|
| TCap cap = T_capture_prjoin(l1, l2); |
|
|
| TEST_ASSERT_NOT_NULL(cap.data); |
| TEST_ASSERT_EQUAL_STRING("<EMPTY>|<EMPTY>|<EMPTY>\n", cap.data); |
|
|
| T_free_cap(&cap); |
| T_free_line(l1); |
| T_free_line(l2); |
| } |
|
|
| |
| static void test_prjoin_autoformat_limits(void) |
| { |
| const char *f1[] = {"J", "A", "B"}; |
| const char *f2[] = {"J", "C", "D", "E"}; |
| struct line *l1 = T_make_line(f1, 3); |
| struct line *l2 = T_make_line(f2, 4); |
|
|
| autoformat = true; |
| autocount_1 = 1; |
| autocount_2 = 2; |
| join_field_1 = 0; |
| join_field_2 = 0; |
|
|
| TCap cap = T_capture_prjoin(l1, l2); |
|
|
| TEST_ASSERT_NOT_NULL(cap.data); |
| TEST_ASSERT_EQUAL_STRING("J C\n", cap.data); |
|
|
| T_free_cap(&cap); |
| T_free_line(l1); |
| T_free_line(l2); |
| } |
|
|
| |
| static void test_prjoin_zero_terminated_eol(void) |
| { |
| const char *f1[] = {"K"}; |
| const char *f2[] = {"K", "X"}; |
| struct line *l1 = T_make_line(f1, 1); |
| struct line *l2 = T_make_line(f2, 2); |
|
|
| eolchar = '\0'; |
| output_separator = "="; |
| output_seplen = 1; |
| join_field_1 = 0; |
| join_field_2 = 0; |
|
|
| TCap cap = T_capture_prjoin(l1, l2); |
|
|
| TEST_ASSERT_NOT_NULL(cap.data); |
| TEST_ASSERT_EQUAL_UINT32(4, (uint32_t)cap.len); |
| const char expected[4] = {'K', '=', 'X', '\0'}; |
| TEST_ASSERT_EQUAL_INT(0, memcmp(expected, cap.data, 4)); |
|
|
| T_free_cap(&cap); |
| T_free_line(l1); |
| T_free_line(l2); |
| } |
|
|
| int main(void) |
| { |
| UNITY_BEGIN(); |
| RUN_TEST(test_prjoin_default_basic); |
| RUN_TEST(test_prjoin_default_blank_first); |
| RUN_TEST(test_prjoin_outlist_custom_order_and_separator); |
| RUN_TEST(test_prjoin_outlist_missing_and_empty_filler); |
| RUN_TEST(test_prjoin_autoformat_limits); |
| RUN_TEST(test_prjoin_zero_terminated_eol); |
| return UNITY_END(); |
| } |