#include "../../unity/unity.h" #include #include #include #include #include #include #include #include #include /* External/internal symbols from the program are visible here, since this file is included into the same translation unit. */ /* Forward declarations to silence potential warnings (they are defined above). */ static void split_file (void); /* Test helpers state */ static int start_files_created = 0; static char test_prefix[64] = {0}; /* Helper: create temp input file with given content and return strdup'ed path */ static char* create_temp_input_file(const char* content) { char templ[] = "/tmp/csplit_test_input_XXXXXX"; int fd = mkstemp(templ); if (fd < 0) { perror("mkstemp"); return NULL; } size_t len = strlen(content); if (len > 0) { ssize_t w = write(fd, content, len); if (w < 0 || (size_t)w != len) { close(fd); unlink(templ); perror("write temp input"); return NULL; } } if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { close(fd); unlink(templ); perror("lseek"); return NULL; } close(fd); return strdup(templ); } /* Helper: read entire file into newly malloc'd buffer (null-terminated). Returns NULL on error. */ static char* read_file_contents(const char* path) { struct stat st; if (stat(path, &st) != 0) return NULL; size_t sz = (size_t)st.st_size; FILE* f = fopen(path, "rb"); if (!f) return NULL; char* buf = (char*)malloc(sz + 1); if (!buf) { fclose(f); return NULL; } size_t r = fread(buf, 1, sz, f); fclose(f); if (r != sz) { free(buf); return NULL; } buf[sz] = '\0'; return buf; } /* Helper: build filename for file index i using program's make_filename. Returns pointer to internal filename_space. */ static const char* outfile_name_for_index(int i) { /* make_filename writes into filename_space and returns it */ return make_filename(i); } /* Reset input buffer state and related globals between tests */ static void reset_input_state(void) { /* Free any hold area from previous run */ if (hold_area) { free(hold_area); hold_area = NULL; } hold_count = 0; have_read_eof = false; head = NULL; last_line_number = 0; current_line = 0; } /* Prepare environment before each test call to split_file */ static void prepare_environment(void) { /* Ensure signals set is empty for critical sections */ sigemptyset(&caught_signals); /* Avoid printing byte counts to stdout */ suppress_count = true; /* Do not auto-remove files on error or elide empty files in tests */ remove_files = false; elide_empty_files = false; suppress_matched = false; /* Use numeric suffix with default digits */ digits = 2; suffix = NULL; /* Ensure filename_space exists and is large enough */ if (!filename_space) { filename_space = (char*)malloc(1024); } /* Use a stable test prefix unique(ish) to this process */ if (test_prefix[0] == '\0') { snprintf(test_prefix, sizeof(test_prefix), "utcs_%ld_", (long)getpid()); } prefix = test_prefix; /* Reset input stream state */ reset_input_state(); /* Start tracking created files from current state */ start_files_created = files_created; } /* Cleanup files created during the test and restore files_created */ static void cleanup_created_files(void) { int end = files_created; for (int i = start_files_created; i < end; i++) { const char* name = outfile_name_for_index(i); unlink(name); } files_created = start_files_created; } /* Unity required hooks */ void setUp(void) { prepare_environment(); } void tearDown(void) { cleanup_created_files(); } /* Test 1: Numeric line-count split: single control "3" Input: a\nb\nc\nd\ne\n Expect: two files: 00: "a\nb\n" 01: "c\nd\ne\n" */ void test_split_file_linecount_basic(void) { const char* input = "a\nb\nc\nd\ne\n"; char* path = create_temp_input_file(input); TEST_ASSERT_NOT_NULL_MESSAGE(path, "Failed to create temp input"); set_input_file(path); /* Build controls: one numeric control lines_required=3 */ control_used = 0; struct control* p = new_control_record(); p->lines_required = 3; /* split before line 3 */ p->argnum = 1; /* arbitrary */ /* Execute */ split_file(); /* Verify two files created */ int created = files_created - start_files_created; TEST_ASSERT_EQUAL_INT_MESSAGE(2, created, "Expected exactly 2 output files"); const char* f0 = outfile_name_for_index(start_files_created + 0); const char* f1 = outfile_name_for_index(start_files_created + 1); char* c0 = read_file_contents(f0); char* c1 = read_file_contents(f1); TEST_ASSERT_NOT_NULL_MESSAGE(c0, "Failed to read first output file"); TEST_ASSERT_NOT_NULL_MESSAGE(c1, "Failed to read second output file"); TEST_ASSERT_EQUAL_STRING("a\nb\n", c0); TEST_ASSERT_EQUAL_STRING("c\nd\ne\n", c1); free(c0); free(c1); unlink(path); free(path); } /* Test 2: Regexp basic split: control '/^c$/' Input: a\nb\nc\nd\ne\n Expect: two files: 00: "a\nb\n" 01: "c\nd\ne\n" */ void test_split_file_regexp_basic(void) { const char* input = "a\nb\nc\nd\ne\n"; char* path = create_temp_input_file(input); TEST_ASSERT_NOT_NULL_MESSAGE(path, "Failed to create temp input"); set_input_file(path); /* Build controls: one regexp control '/^c$/' */ control_used = 0; struct control* p = extract_regexp(1, false, "/^c$/"); (void)p; /* silence unused warning in case */ /* Execute */ split_file(); /* Verify two files created */ int created = files_created - start_files_created; TEST_ASSERT_EQUAL_INT_MESSAGE(2, created, "Expected exactly 2 output files"); const char* f0 = outfile_name_for_index(start_files_created + 0); const char* f1 = outfile_name_for_index(start_files_created + 1); char* c0 = read_file_contents(f0); char* c1 = read_file_contents(f1); TEST_ASSERT_NOT_NULL_MESSAGE(c0, "Failed to read first output file"); TEST_ASSERT_NOT_NULL_MESSAGE(c1, "Failed to read second output file"); TEST_ASSERT_EQUAL_STRING("a\nb\n", c0); TEST_ASSERT_EQUAL_STRING("c\nd\ne\n", c1); free(c0); free(c1); unlink(path); free(path); } /* Test 3: Regexp with suppress_matched: '/^c$/' suppress_matched = true Input: a\nb\nc\nd\ne\n Expect: two files: 00: "a\nb\n" 01: "d\ne\n" (matching line 'c' is suppressed) */ void test_split_file_regexp_suppress_matched(void) { const char* input = "a\nb\nc\nd\ne\n"; char* path = create_temp_input_file(input); TEST_ASSERT_NOT_NULL_MESSAGE(path, "Failed to create temp input"); set_input_file(path); /* Build controls: one regexp control '/^c$/' */ control_used = 0; struct control* p = extract_regexp(1, false, "/^c$/"); (void)p; /* Suppress matched lines */ suppress_matched = true; /* Execute */ split_file(); /* Verify two files created */ int created = files_created - start_files_created; TEST_ASSERT_EQUAL_INT_MESSAGE(2, created, "Expected exactly 2 output files"); const char* f0 = outfile_name_for_index(start_files_created + 0); const char* f1 = outfile_name_for_index(start_files_created + 1); char* c0 = read_file_contents(f0); char* c1 = read_file_contents(f1); TEST_ASSERT_NOT_NULL_MESSAGE(c0, "Failed to read first output file"); TEST_ASSERT_NOT_NULL_MESSAGE(c1, "Failed to read second output file"); TEST_ASSERT_EQUAL_STRING("a\nb\n", c0); TEST_ASSERT_EQUAL_STRING("d\ne\n", c1); free(c0); free(c1); unlink(path); free(path); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_split_file_linecount_basic); RUN_TEST(test_split_file_regexp_basic); RUN_TEST(test_split_file_regexp_suppress_matched); return UNITY_END(); }