#include "../../unity/unity.h" #include #include #include #include #include /* 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(); }