coreutils / tests /csplit /tests_for_process_regexp.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 <errno.h>
/* The test file is included within the same translation unit as csplit.c,
so we can reference its internal static symbols directly. */
/* Helpers to manage input/output for tests */
static void test_prepare_stdin(const char *data)
{
int fds[2];
if (pipe(fds) != 0) {
/* Can't use Unity asserts if stdout is redirected, but it's not here. */
TEST_FAIL_MESSAGE("pipe() failed");
}
/* Write all data and close write end to signal EOF */
ssize_t len = (ssize_t)strlen(data);
ssize_t written = 0;
while (written < len) {
ssize_t w = write(fds[1], data + written, len - written);
if (w < 0) {
close(fds[0]);
close(fds[1]);
TEST_FAIL_MESSAGE("write() to pipe failed");
}
written += w;
}
close(fds[1]);
/* Replace stdin with read end of the pipe */
if (dup2(fds[0], STDIN_FILENO) < 0) {
close(fds[0]);
TEST_FAIL_MESSAGE("dup2() failed");
}
close(fds[0]);
}
/* Remove files created by csplit for current prefix/suffix configuration. */
static void test_remove_created_files(void)
{
/* delete_all_files is internal and deletes only on error if remove_files is set.
We manually unlink created files here. */
if (files_created > 0) {
for (int i = files_created - 1; i >= 0; --i) {
const char *name = make_filename(i);
unlink(name);
}
}
files_created = 0;
}
/* Read whole file into a malloc'ed buffer (null-terminated). Caller frees. */
static char *test_slurp_file(const char *path)
{
FILE *fp = fopen(path, "rb");
if (!fp) return NULL;
if (fseek(fp, 0, SEEK_END) != 0) {
fclose(fp);
return NULL;
}
long sz = ftell(fp);
if (sz < 0) {
fclose(fp);
return NULL;
}
if (fseek(fp, 0, SEEK_SET) != 0) {
fclose(fp);
return NULL;
}
char *buf = (char *)malloc((size_t)sz + 1);
if (!buf) {
fclose(fp);
return NULL;
}
size_t n = fread(buf, 1, (size_t)sz, fp);
fclose(fp);
buf[n] = '\0';
return buf;
}
/* Drain any remaining lines to fully free buffers and reset internal prev_buf. */
static void test_drain_all_input(void)
{
struct cstring *ln;
while ((ln = remove_line()) != NULL) {
(void)ln;
}
}
/* Minimal argv to satisfy any error path references (not used in success tests). */
static char *test_argv_storage[4] = { (char *)"csplit", (char *)"/dummy/", (char *)"%dummy%", NULL };
/* Common setup before each test. */
void setUp(void) {
/* Reset output behavior to avoid stdout noise from counts */
suppress_count = true;
elide_empty_files = false;
remove_files = false;
suppress_matched = false;
/* Filename generation setup */
if (!filename_space) {
filename_space = (char *)malloc(512);
TEST_ASSERT_NOT_NULL_MESSAGE(filename_space, "malloc filename_space failed");
}
prefix = "tstpr_";
suffix = NULL;
digits = 2;
/* Signals set to empty to avoid blocking issues */
sigemptyset(&caught_signals);
/* Input/buffer state reset */
head = NULL;
if (hold_area) { free(hold_area); hold_area = NULL; }
hold_count = 0;
have_read_eof = false;
last_line_number = 0;
current_line = 0;
/* Output state reset */
bytes_written = 0;
output_stream = NULL;
output_filename = NULL;
files_created = 0;
/* Argv context */
global_argv = test_argv_storage;
}
/* Cleanup after each test. */
void tearDown(void) {
/* Close any open output file cleanly */
close_output_file();
/* Drain any buffered input to reset internal state like prev_buf */
test_drain_all_input();
/* Remove any created files */
test_remove_created_files();
/* Reset key input state for safety */
head = NULL;
if (hold_area) { free(hold_area); hold_area = NULL; }
hold_count = 0;
have_read_eof = false;
last_line_number = 0;
current_line = 0;
}
/* Helpers to build control structures via existing parser */
static struct control *test_make_regexp_control(int argnum, const char *pattern_with_delims)
{
/* extract_regexp sets ignore based on delimiter: '/' -> false, '%' -> true */
struct control *p = extract_regexp(argnum, (pattern_with_delims[0] == '%'), pattern_with_delims);
return p;
}
/* Return last created filename (or NULL if none). */
static const char *test_last_filename(void)
{
if (files_created <= 0) return NULL;
return make_filename(files_created - 1);
}
/* Tests */
static void test_process_regexp_match_no_offset_writes_preceding_lines(void)
{
/* Input: lines before, match, after */
const char *input = "one\nfoo\nbar\n";
test_prepare_stdin(input);
/* Build control for /foo/ (no offset, not ignore) */
test_argv_storage[1] = (char *)"/foo/";
struct control *p = test_make_regexp_control(1, "/foo/");
/* Run */
process_regexp(p, 0);
/* Verify one file created containing only lines before the match */
TEST_ASSERT_EQUAL_INT(1, files_created);
const char *fname = test_last_filename();
TEST_ASSERT_NOT_NULL(fname);
char *content = test_slurp_file(fname);
TEST_ASSERT_NOT_NULL_MESSAGE(content, "Failed to read output file");
TEST_ASSERT_EQUAL_STRING("one\n", content);
free(content);
}
static void test_process_regexp_positive_offset_includes_matching_line(void)
{
const char *input = "a\nfoo\nc\n";
test_prepare_stdin(input);
/* /foo/+1 should include the matching line as well */
test_argv_storage[1] = (char *)"/foo/+1";
struct control *p = test_make_regexp_control(1, "/foo/+1");
process_regexp(p, 0);
TEST_ASSERT_EQUAL_INT(1, files_created);
const char *fname = test_last_filename();
TEST_ASSERT_NOT_NULL(fname);
char *content = test_slurp_file(fname);
TEST_ASSERT_NOT_NULL(content);
TEST_ASSERT_EQUAL_STRING("a\nfoo\n", content);
free(content);
}
static void test_process_regexp_ignore_true_creates_no_files(void)
{
const char *input = "a\nfoo\nb\n";
test_prepare_stdin(input);
/* %foo% -> ignore the section (no output file should be created) */
test_argv_storage[2] = (char *)"%foo%";
struct control *p = test_make_regexp_control(2, "%foo%");
process_regexp(p, 0);
TEST_ASSERT_EQUAL_INT(0, files_created);
/* And no file to check */
}
static void test_process_regexp_negative_offset_writes_up_to_before_previous_line(void)
{
const char *input = "a\nb\nc\nd\n";
test_prepare_stdin(input);
/* /c/-1: cut 1 line before the match "c", so only "a\n" is written */
test_argv_storage[1] = (char *)"/c/-1";
struct control *p = test_make_regexp_control(1, "/c/-1");
process_regexp(p, 0);
TEST_ASSERT_EQUAL_INT(1, files_created);
const char *fname = test_last_filename();
TEST_ASSERT_NOT_NULL(fname);
char *content = test_slurp_file(fname);
TEST_ASSERT_NOT_NULL(content);
TEST_ASSERT_EQUAL_STRING("a\n", content);
free(content);
}
static void test_process_regexp_suppress_matched_skips_matched_line(void)
{
const char *input = "before\nmatch\nafter\n";
test_prepare_stdin(input);
suppress_matched = true;
test_argv_storage[1] = (char *)"/match/";
struct control *p = test_make_regexp_control(1, "/match/");
process_regexp(p, 0);
/* File should contain only "before\n" */
TEST_ASSERT_EQUAL_INT(1, files_created);
const char *fname = test_last_filename();
TEST_ASSERT_NOT_NULL(fname);
char *content = test_slurp_file(fname);
TEST_ASSERT_NOT_NULL(content);
TEST_ASSERT_EQUAL_STRING("before\n", content);
free(content);
/* The matched line should be suppressed; next line should be "after\n" */
struct cstring *next = remove_line();
TEST_ASSERT_NOT_NULL(next);
TEST_ASSERT_EQUAL_INT_MESSAGE((int)strlen("after\n"), (int)next->len, "Unexpected next line length");
TEST_ASSERT_EQUAL_INT(0, strncmp(next->str, "after\n", next->len));
}
/* Unity main */
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_process_regexp_match_no_offset_writes_preceding_lines);
RUN_TEST(test_process_regexp_positive_offset_includes_matching_line);
RUN_TEST(test_process_regexp_ignore_true_creates_no_files);
RUN_TEST(test_process_regexp_negative_offset_writes_up_to_before_previous_line);
RUN_TEST(test_process_regexp_suppress_matched_skips_matched_line);
return UNITY_END();
}