coreutils / tests /join /tests_for_join.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>
#include <fcntl.h>
/* Helper to free any existing outlist entries and reset to defaults. */
static void clear_outlist(void)
{
/* outlist_head and outlist_end are defined in the program under test. */
while (outlist_head.next) {
struct outlist *tmp = outlist_head.next;
outlist_head.next = tmp->next;
free(tmp);
}
outlist_end = &outlist_head;
}
/* Reset relevant globals to safe defaults before each test. */
static void reset_join_state(void)
{
/* Output formatting */
output_separator = " ";
output_seplen = 1;
empty_filler = NULL;
/* Field separators: default to blanks (tab.len == 0) */
tab.ch = 0;
tab.len = 0;
/* Behavior flags */
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;
/* Default input-order checking behavior */
check_input_order = CHECK_ORDER_DEFAULT;
/* Join fields: use the first field (index 0) in both files */
join_field_1 = 0;
join_field_2 = 0;
/* Line tracking and buffers */
prevline[0] = NULL;
prevline[1] = NULL;
line_no[0] = 0;
line_no[1] = 0;
spareline[0] = NULL;
spareline[1] = NULL;
/* EOL */
eolchar = '\n';
/* Clear any outlist configuration */
clear_outlist();
/* Optional: set input names (only used in diagnostics) */
g_names[0] = (char *)"f1";
g_names[1] = (char *)"f2";
}
/* Create a temporary stream loaded with 'data'. */
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;
}
/* Capture stdout while calling join(fp1, fp2). Returns malloc'd buffer with output or NULL on error. */
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;
}
/* Call the function under test. Do not use Unity asserts while stdout is redirected. */
join(fp1, fp2);
/* Flush and collect the output. */
fflush(stdout);
fflush(cap);
if (fseek(cap, 0, SEEK_END) != 0) {
/* Restore stdout before returning */
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';
/* Restore stdout. */
dup2(saved_stdout, STDOUT_FILENO);
close(saved_stdout);
fclose(cap);
return buf;
}
void setUp(void) {
reset_join_state();
}
void tearDown(void) {
/* Nothing special; outlist cleared in setUp */
}
/* Test 1: Basic pairable join, default whitespace-separated fields. */
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);
}
/* Test 2: Print unpairable lines from file1. */
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);
}
/* Test 3: Multiple matches cross-product */
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);
/* Expected order: (1,x), (1,y), (2,x), (2,y) */
TEST_ASSERT_EQUAL_STRING("a 1 x\na 1 y\na 2 x\na 2 y\n", out);
free(out);
}
/* Test 4: Header-line handling with autoformat */
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);
/* Header line, then only the pairable 1 */
TEST_ASSERT_EQUAL_STRING("ID A B\n1 AA XX\n", out);
free(out);
}
/* Test 5: Case-insensitive join */
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);
}
/* Test 6: Empty field with filler using tab as the field separator */
void test_join_empty_field_with_filler_and_tab(void)
{
const char* s1 = "a\t\t1\n"; /* fields: "a", "", "1" */
const char* s2 = "a\tx\n"; /* fields: "a", "x" */
FILE* f1 = make_stream_from_str(s1);
FILE* f2 = make_stream_from_str(s2);
TEST_ASSERT_NOT_NULL(f1);
TEST_ASSERT_NOT_NULL(f2);
/* Configure tab as the input separator, ensure output sep is space. */
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);
/* Expect filler for the empty field */
TEST_ASSERT_EQUAL_STRING("a <> 1 x\n", out);
free(out);
}
/* Test 7: Custom outlist: select specific fields */
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;
/* Configure outlist: file2 field index 2 ("4"), then join field (0), then file1 field index 1 ("2") */
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();
}