coreutils / tests /comm /tests_for_compare_files.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>
#include <sys/wait.h>
#include <errno.h>
#include <locale.h>
/* Helper: create a temporary file with given content (binary-safe) and return malloc'ed path */
static char *create_tempfile_with_content(const void *data, size_t len)
{
char template_path[] = "/tmp/comm_test_XXXXXX";
int fd = mkstemp(template_path);
if (fd < 0)
return NULL;
if (len > 0) {
const unsigned char *p = (const unsigned char *)data;
size_t written = 0;
while (written < len) {
ssize_t w = write(fd, p + written, len - written);
if (w < 0) { close(fd); unlink(template_path); return NULL; }
written += (size_t)w;
}
}
if (close(fd) != 0) {
unlink(template_path);
return NULL;
}
/* Return a heap copy of the path */
char *ret = (char *)malloc(strlen(template_path) + 1);
if (!ret) { unlink(template_path); return NULL; }
strcpy(ret, template_path);
return ret;
}
/* Helper: read all data from fd into malloc'ed buffer; returns buffer and sets *out_len.
The returned buffer is NUL-terminated for convenience, but may contain embedded NULs. */
static char *read_all_from_fd(int fd, size_t *out_len)
{
size_t cap = 4096;
size_t size = 0;
char *buf = (char *)malloc(cap + 1);
if (!buf) return NULL;
for (;;) {
if (size == cap) {
cap *= 2;
char *nb = (char *)realloc(buf, cap + 1);
if (!nb) { free(buf); return NULL; }
buf = nb;
}
ssize_t r = read(fd, buf + size, cap - size);
if (r < 0) {
if (errno == EINTR) continue;
free(buf);
return NULL;
}
if (r == 0) break;
size += (size_t)r;
}
buf[size] = '\0';
if (out_len) *out_len = size;
return buf;
}
/* Helper to run compare_files in a child, optionally providing stdin data.
Inputs:
file1, file2: paths or "-" for stdin
stdin_data/stdin_len: data to feed to child's stdin if either file is "-"
Outputs:
*out_buf/*out_len: captured stdout
*err_buf/*err_len: captured stderr
returns child's exit status (>=0) or -1 on failure
*/
static int run_compare_files_capture(const char *file1, const char *file2,
const unsigned char *stdin_data, size_t stdin_len,
char **out_buf, size_t *out_len,
char **err_buf, size_t *err_len)
{
int out_pipe[2], err_pipe[2], in_pipe[2] = {-1, -1};
if (pipe(out_pipe) < 0) return -1;
if (pipe(err_pipe) < 0) { close(out_pipe[0]); close(out_pipe[1]); return -1; }
int need_stdin = ((file1 && strcmp(file1, "-") == 0) || (file2 && strcmp(file2, "-") == 0));
if (need_stdin) {
if (pipe(in_pipe) < 0) {
close(out_pipe[0]); close(out_pipe[1]);
close(err_pipe[0]); close(err_pipe[1]);
return -1;
}
}
fflush(stdout);
fflush(stderr);
pid_t pid = fork();
if (pid < 0) {
close(out_pipe[0]); close(out_pipe[1]);
close(err_pipe[0]); close(err_pipe[1]);
if (need_stdin) { close(in_pipe[0]); close(in_pipe[1]); }
return -1;
}
if (pid == 0) {
/* Child */
/* Ensure deterministic messages */
setenv("LC_ALL", "C", 1);
/* Redirect stdout/stderr */
dup2(out_pipe[1], STDOUT_FILENO);
dup2(err_pipe[1], STDERR_FILENO);
close(out_pipe[0]); close(out_pipe[1]);
close(err_pipe[0]); close(err_pipe[1]);
if (need_stdin) {
dup2(in_pipe[0], STDIN_FILENO);
close(in_pipe[0]);
close(in_pipe[1]);
}
/* Call target function */
char *infiles[2];
infiles[0] = (char *)file1;
infiles[1] = (char *)file2;
/* compare_files never returns */
compare_files(infiles);
/* Fallback if it ever returned (shouldn't) */
_exit(255);
}
/* Parent */
close(out_pipe[1]);
close(err_pipe[1]);
if (need_stdin) {
close(in_pipe[0]);
/* Feed stdin data if provided */
if (stdin_data && stdin_len > 0) {
size_t written = 0;
while (written < stdin_len) {
ssize_t w = write(in_pipe[1], stdin_data + written, stdin_len - written);
if (w < 0) {
if (errno == EINTR) continue;
break;
}
written += (size_t)w;
}
}
close(in_pipe[1]);
}
/* Read outputs */
size_t o_len = 0, e_len = 0;
char *o_buf = read_all_from_fd(out_pipe[0], &o_len);
char *e_buf = read_all_from_fd(err_pipe[0], &e_len);
close(out_pipe[0]);
close(err_pipe[0]);
int status = -1;
int rc = waitpid(pid, &status, 0);
int exit_code = -1;
if (rc >= 0) {
if (WIFEXITED(status)) exit_code = WEXITSTATUS(status);
else if (WIFSIGNALED(status)) exit_code = 128 + WTERMSIG(status);
else exit_code = -1;
}
if (out_buf) *out_buf = o_buf; else free(o_buf);
if (out_len) *out_len = o_len;
if (err_buf) *err_buf = e_buf; else free(e_buf);
if (err_len) *err_len = e_len;
return exit_code;
}
/* Unity setup/teardown */
void setUp(void)
{
/* Reset globals to defaults akin to real program behavior */
only_file_1 = true;
only_file_2 = true;
both = true;
seen_unpairable = false;
issued_disorder_warning[0] = false;
issued_disorder_warning[1] = false;
hard_LC_COLLATE = false;
check_input_order = CHECK_ORDER_DEFAULT;
col_sep = "\t";
col_sep_len = 1;
delim = '\n';
total_option = false;
}
void tearDown(void)
{
/* Nothing */
}
/* Test 1: Basic three-column merge with default tab separator */
void test_compare_files_basic_three_columns(void)
{
const char *f1c = "apple\nbanana\ncarrot\n";
const char *f2c = "banana\ncarrot\ndate\n";
char *p1 = create_tempfile_with_content(f1c, strlen(f1c));
char *p2 = create_tempfile_with_content(f2c, strlen(f2c));
TEST_ASSERT_NOT_NULL(p1);
TEST_ASSERT_NOT_NULL(p2);
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int ec = run_compare_files_capture(p1, p2, NULL, 0, &out, &out_len, &err, &err_len);
const char *expected = "apple\n\t\tbanana\n\t\tcarrot\n\tdate\n";
TEST_ASSERT_EQUAL_INT(0, ec);
TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, out, out_len);
TEST_ASSERT_EQUAL_size_t(0, err_len);
free(out); free(err);
unlink(p1); unlink(p2);
free(p1); free(p2);
}
/* Test 2: Suppress columns 1 and 3, print only unique lines from file 2 without leading sep */
void test_compare_files_suppress_columns_effect(void)
{
const char *f1c = "a\nb\nc\n";
const char *f2c = "b\nd\n";
char *p1 = create_tempfile_with_content(f1c, strlen(f1c));
char *p2 = create_tempfile_with_content(f2c, strlen(f2c));
TEST_ASSERT_NOT_NULL(p1);
TEST_ASSERT_NOT_NULL(p2);
only_file_1 = false;
only_file_2 = true;
both = false;
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int ec = run_compare_files_capture(p1, p2, NULL, 0, &out, &out_len, &err, &err_len);
const char *expected = "d\n";
TEST_ASSERT_EQUAL_INT(0, ec);
TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, out, out_len);
TEST_ASSERT_EQUAL_size_t(0, err_len);
free(out); free(err);
unlink(p1); unlink(p2);
free(p1); free(p2);
}
/* Test 3: Custom output delimiter and total summary (col_sep_len > 1 path) */
void test_compare_files_custom_delim_and_total(void)
{
const char *f1c = "a\nx\n";
const char *f2c = "a\ny\n";
char *p1 = create_tempfile_with_content(f1c, strlen(f1c));
char *p2 = create_tempfile_with_content(f2c, strlen(f2c));
TEST_ASSERT_NOT_NULL(p1);
TEST_ASSERT_NOT_NULL(p2);
col_sep = "::";
col_sep_len = 2;
total_option = true;
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int ec = run_compare_files_capture(p1, p2, NULL, 0, &out, &out_len, &err, &err_len);
const char *expected =
"::::a\n" /* common "a" with two separators before */
"x\n" /* unique in file1 */
"::y\n" /* unique in file2 with one separator before */
"1::1::1::total\n"; /* summary */
TEST_ASSERT_EQUAL_INT(0, ec);
TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, out, out_len);
TEST_ASSERT_EQUAL_size_t(0, err_len);
free(out); free(err);
unlink(p1); unlink(p2);
free(p1); free(p2);
}
/* Test 4: Zero-terminated input/output; verify binary output including NUL and separators */
void test_compare_files_zero_terminated(void)
{
const unsigned char f1c[] = { 'a', '\0', 'c', '\0' };
const unsigned char f2c[] = { 'b', '\0', 'c', '\0' };
char *p1 = create_tempfile_with_content(f1c, sizeof(f1c));
char *p2 = create_tempfile_with_content(f2c, sizeof(f2c));
TEST_ASSERT_NOT_NULL(p1);
TEST_ASSERT_NOT_NULL(p2);
delim = '\0'; /* zero-terminated lines */
col_sep = "\t";
col_sep_len = 1;
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int ec = run_compare_files_capture(p1, p2, NULL, 0, &out, &out_len, &err, &err_len);
unsigned char expected[] = { 'a', 0, '\t', 'b', 0, '\t', '\t', 'c', 0 };
TEST_ASSERT_EQUAL_INT(0, ec);
TEST_ASSERT_EQUAL_size_t(sizeof(expected), out_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, out, out_len);
TEST_ASSERT_EQUAL_size_t(0, err_len);
free(out); free(err);
unlink(p1); unlink(p2);
free(p1); free(p2);
}
/* Test 5: Default check-order warning path with unsorted file, causes non-zero exit at end */
void test_compare_files_check_order_default_warns_and_exits_nonzero(void)
{
const char *f1c = "b\nA\n"; /* unsorted: 'b' > 'A' */
char *p1 = create_tempfile_with_content(f1c, strlen(f1c));
char *p2 = create_tempfile_with_content("", 0); /* empty file2 to guarantee unpairable lines */
TEST_ASSERT_NOT_NULL(p1);
TEST_ASSERT_NOT_NULL(p2);
check_input_order = CHECK_ORDER_DEFAULT;
hard_LC_COLLATE = false;
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int ec = run_compare_files_capture(p1, p2, NULL, 0, &out, &out_len, &err, &err_len);
const char *expected_out = "b\nA\n";
TEST_ASSERT_TRUE(ec != 0);
TEST_ASSERT_EQUAL_size_t(strlen(expected_out), out_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_out, out, out_len);
/* We expect some error output, but avoid locale-dependent substring checks */
TEST_ASSERT_TRUE(err_len > 0);
free(out); free(err);
unlink(p1); unlink(p2);
free(p1); free(p2);
}
/* Test 6: check-order enabled (fatal): exit happens upon first disorder detection */
void test_compare_files_check_order_enabled_fatal(void)
{
const char *f1c = "b\nA\n"; /* unsorted */
char *p1 = create_tempfile_with_content(f1c, strlen(f1c));
char *p2 = create_tempfile_with_content("", 0);
TEST_ASSERT_NOT_NULL(p1);
TEST_ASSERT_NOT_NULL(p2);
check_input_order = CHECK_ORDER_ENABLED;
hard_LC_COLLATE = false;
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int ec = run_compare_files_capture(p1, p2, NULL, 0, &out, &out_len, &err, &err_len);
/* Only the first line should have been printed before fatal error */
const char *expected_out = "b\n";
TEST_ASSERT_TRUE(ec != 0);
TEST_ASSERT_EQUAL_size_t(strlen(expected_out), out_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_out, out, out_len);
TEST_ASSERT_TRUE(err_len > 0);
free(out); free(err);
unlink(p1); unlink(p2);
free(p1); free(p2);
}
/* Test 7: nocheck-order: unsorted input does not error */
void test_compare_files_check_order_disabled_allows_unsorted(void)
{
const char *f1c = "b\nA\n"; /* unsorted */
char *p1 = create_tempfile_with_content(f1c, strlen(f1c));
char *p2 = create_tempfile_with_content("", 0);
TEST_ASSERT_NOT_NULL(p1);
TEST_ASSERT_NOT_NULL(p2);
check_input_order = CHECK_ORDER_DISABLED;
hard_LC_COLLATE = false;
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int ec = run_compare_files_capture(p1, p2, NULL, 0, &out, &out_len, &err, &err_len);
const char *expected_out = "b\nA\n";
TEST_ASSERT_EQUAL_INT(0, ec);
TEST_ASSERT_EQUAL_size_t(strlen(expected_out), out_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_out, out, out_len);
TEST_ASSERT_EQUAL_size_t(0, err_len);
free(out); free(err);
unlink(p1); unlink(p2);
free(p1); free(p2);
}
/* Test 8: Using "-" to read from stdin for one file */
void test_compare_files_stdin_handling(void)
{
const char *f1c = "a\nb\n";
const char *stdin_c = "b\nc\n";
char *p1 = create_tempfile_with_content(f1c, strlen(f1c));
TEST_ASSERT_NOT_NULL(p1);
char *out = NULL, *err = NULL;
size_t out_len = 0, err_len = 0;
int ec = run_compare_files_capture(p1, "-", (const unsigned char *)stdin_c, strlen(stdin_c),
&out, &out_len, &err, &err_len);
const char *expected = "a\n\t\tb\n\tc\n";
TEST_ASSERT_EQUAL_INT(0, ec);
TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, out, out_len);
TEST_ASSERT_EQUAL_size_t(0, err_len);
free(out); free(err);
unlink(p1);
free(p1);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_compare_files_basic_three_columns);
RUN_TEST(test_compare_files_suppress_columns_effect);
RUN_TEST(test_compare_files_custom_delim_and_total);
RUN_TEST(test_compare_files_zero_terminated);
RUN_TEST(test_compare_files_check_order_default_warns_and_exits_nonzero);
RUN_TEST(test_compare_files_check_order_enabled_fatal);
RUN_TEST(test_compare_files_check_order_disabled_allows_unsorted);
RUN_TEST(test_compare_files_stdin_handling);
return UNITY_END();
}