coreutils / tests /join /tests_for_prjoin.c
AryaWu's picture
Upload folder using huggingface_hub
78d2150 verified
#include "../../unity/unity.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/* Helpers to build and free minimal struct line objects for prjoin. */
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);
}
/* Reset and free the global outlist. */
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;
}
/* Capture stdout while invoking prjoin. Do not use Unity asserts while stdout is redirected. */
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));
/* Call the function under test. */
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;
}
/* Save/restore selected globals around tests. */
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)
{
/* Save state */
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;
/* Start with a clean outlist for each test. */
T_reset_outlist();
/* Default common settings */
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)
{
/* Clean any remaining outlist nodes and restore saved state. */
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;
}
/* Test 1: Default format, both lines present. */
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);
/* Defaults already set in setUp: space separator, '\n', join field 0 */
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);
}
/* Test 2: Default format, first line is uni_blank so join field is taken from second line. */
static void test_prjoin_default_blank_first(void)
{
const char *f2[] = {"K", "x"};
struct line *l2 = T_make_line(f2, 2);
/* join_field_2 = 0 already; expect: K x\n */
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);
}
/* Test 3: Custom outlist ordering, including '0' (join field), with custom separator. */
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; /* '0' should print J from l1 */
char spec[] = "1.3,0,2.2,1.2"; /* B,J,C,A */
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);
}
/* Test 4: Custom outlist with missing and empty fields, using empty_filler and custom separator. */
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"; /* missing in l1, empty in l2, missing in l2 */
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);
}
/* Test 5: Default format with autoformat limiting the number of fields printed from each line. */
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; /* Only 1 field (index 0) available from line1 */
autocount_2 = 2; /* Two fields total in line2 => only field 1 gets printed */
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);
}
/* Test 6: Zero-terminated records and non-space separator; verify bytes including NUL. */
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();
}