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