#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* Access program-internal variables and functions since this file is included into the same translation unit. We will use: - interrupt_handler(int) - make_filename(int) indirectly via files/variables - delete_all_files, etc. are called by interrupt_handler - variables: filename_space, prefix, suffix, digits, files_created, remove_files */ /* Dummy signal handler counter */ static volatile sig_atomic_t g_test_signal_count = 0; static void test_dummy_handler(int sig) { (void)sig; g_test_signal_count++; } /* Choose a signal whose default action is non-terminating so that raising it after setting SIG_DFL will not abort the test process. */ static int pick_safe_signal(void) { #ifdef SIGCHLD return SIGCHLD; /* default is ignore */ #elif defined(SIGURG) return SIGURG; /* default is ignore */ #elif defined(SIGWINCH) return SIGWINCH; /* default is ignore */ #elif defined(SIGCONT) return SIGCONT; /* default is continue */ #else /* As a last resort, use SIGCHLD value if available at runtime; otherwise, pick SIGCONT's typical value. But compilation should provide at least one. */ return SIGCONT; #endif } /* Create a temporary directory for the test; returns malloc'd path. */ static char* create_temp_dir(void) { char templ[] = "/tmp/csplit_interrupt_test.XXXXXX"; char *path = (char*)malloc(sizeof(templ)); TEST_ASSERT_NOT_NULL_MESSAGE(path, "malloc failed for temp dir path"); strcpy(path, templ); char *ret = mkdtemp(path); if (!ret) { free(path); TEST_FAIL_MESSAGE("mkdtemp failed to create temporary directory"); } return path; } /* Join two path components, returning a freshly allocated string */ static char* path_join2(const char* a, const char* b) { size_t la = strlen(a); size_t lb = strlen(b); size_t need = la + 1 + lb + 1; char *p = (char*)malloc(need); TEST_ASSERT_NOT_NULL_MESSAGE(p, "malloc failed in path_join2"); memcpy(p, a, la); p[la] = '/'; memcpy(p + la + 1, b, lb); p[la + 1 + lb] = '\0'; return p; } /* Prepare globals for filename generation. Allocates/replaces filename_space and sets prefix/suffix/digits/files_created. */ static void prepare_filename_env(const char* pre, const char* suf, int dig, int nfiles) { /* filename_space should be large enough; allocate a generous buffer */ if (filename_space) { free((void*)filename_space); filename_space = NULL; } filename_space = (char*)malloc(1024); TEST_ASSERT_NOT_NULL_MESSAGE((void*)filename_space, "malloc failed for filename_space"); /* Set prefix and suffix */ /* prefix is char const * volatile; suffix is char * volatile */ prefix = pre; if (suffix) { free((void*)suffix); suffix = NULL; } if (suf) { char *s = (char*)malloc(strlen(suf) + 1); TEST_ASSERT_NOT_NULL(s); strcpy(s, suf); suffix = s; } else { suffix = NULL; } digits = dig; files_created = nfiles; } /* Create N files based on current prefix/suffix/digits and return an array of allocated strings containing their paths. */ static char** create_numbered_files(int n) { char **names = (char**)calloc(n, sizeof(char*)); TEST_ASSERT_NOT_NULL_MESSAGE(names, "calloc failed for file names array"); for (int i = 0; i < n; i++) { char *name = strdup(make_filename(i)); TEST_ASSERT_NOT_NULL_MESSAGE(name, "strdup failed on make_filename"); FILE *fp = fopen(name, "w"); if (!fp) { /* Clean up partially created files on error */ for (int j = 0; j <= i; j++) { if (names[j]) { unlink(names[j]); free(names[j]); } } free(names); TEST_FAIL_MESSAGE("Failed to create test file"); } fclose(fp); names[i] = name; } return names; } static void free_names_and_dir(char **names, int n, const char* dir) { if (names) { for (int i = 0; i < n; i++) { if (names[i]) free(names[i]); } free(names); } if (dir) { rmdir(dir); } } /* Unity required hooks */ void setUp(void) { /* Nothing */ } void tearDown(void) { /* Ensure we don't leave a bad signal handler behind */ int sig = pick_safe_signal(); signal(sig, SIG_DFL); /* Reset globals to a safe state between tests */ remove_files = false; if (suffix) { free((void*)suffix); suffix = NULL; } prefix = NULL; if (filename_space) { free((void*)filename_space); filename_space = NULL; } files_created = 0; } /* Test 1: remove_files = true, suffix not used, files are deleted and handler reset */ static void test_interrupt_handler_deletes_files_and_resets_handler(void) { int sig = pick_safe_signal(); g_test_signal_count = 0; char *dir = create_temp_dir(); char *pre = path_join2(dir, "xx"); /* prefix like /tmp/.../xx */ prepare_filename_env(pre, NULL, 2, 3); remove_files = true; char **names = create_numbered_files(3); /* Install a dummy handler first */ signal(sig, test_dummy_handler); /* Call the handler under test */ interrupt_handler(sig); /* The dummy handler should not have been invoked */ TEST_ASSERT_EQUAL_INT_MESSAGE(0, g_test_signal_count, "dummy handler invoked unexpectedly"); /* files_created should be reset to 0 by delete_all_files(true) */ TEST_ASSERT_EQUAL_INT(0, files_created); /* All files should be deleted */ for (int i = 0; i < 3; i++) { int rc = access(names[i], F_OK); TEST_ASSERT_EQUAL_INT_MESSAGE(-1, rc, "Expected file to be deleted"); TEST_ASSERT_EQUAL_INT_MESSAGE(ENOENT, errno, "Expected ENOENT for deleted file"); } /* Raising the signal again should not hit our dummy handler either (still default) */ g_test_signal_count = 0; raise(sig); TEST_ASSERT_EQUAL_INT_MESSAGE(0, g_test_signal_count, "dummy handler invoked after reset"); /* Cleanup */ free_names_and_dir(names, 3, dir); free(pre); } /* Test 2: remove_files = false; files remain and handler reset; use suffix format */ static void test_interrupt_handler_keeps_files_when_remove_files_false(void) { int sig = pick_safe_signal(); g_test_signal_count = 0; char *dir = create_temp_dir(); char *pre = path_join2(dir, "part"); /* Use a suffix format to exercise that path: part + "%03d" */ prepare_filename_env(pre, "%03d", 2, 2); remove_files = false; char **names = create_numbered_files(2); /* Sanity: expect the files exist now */ for (int i = 0; i < 2; i++) { int rc = access(names[i], F_OK); TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Expected file to exist before handler"); } /* Install dummy handler and call interrupt_handler */ signal(sig, test_dummy_handler); interrupt_handler(sig); /* Dummy handler should not be invoked */ TEST_ASSERT_EQUAL_INT(0, g_test_signal_count); /* files_created should remain unchanged (2) since remove_files == false */ TEST_ASSERT_EQUAL_INT(2, files_created); /* Files should still exist */ for (int i = 0; i < 2; i++) { int rc = access(names[i], F_OK); TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "File should not have been deleted"); } /* Raising again should not hit our dummy handler (it's been reset to default) */ g_test_signal_count = 0; raise(sig); TEST_ASSERT_EQUAL_INT(0, g_test_signal_count); /* Cleanup: manually unlink files and dir */ for (int i = 0; i < 2; i++) { unlink(names[i]); } free_names_and_dir(names, 2, dir); free(pre); } /* Test 3: zero files; ensure no crash and handler reset */ static void test_interrupt_handler_zero_files(void) { int sig = pick_safe_signal(); g_test_signal_count = 0; char *dir = create_temp_dir(); char *pre = path_join2(dir, "xx"); prepare_filename_env(pre, NULL, 2, 0); remove_files = true; signal(sig, test_dummy_handler); interrupt_handler(sig); TEST_ASSERT_EQUAL_INT(0, g_test_signal_count); TEST_ASSERT_EQUAL_INT(0, files_created); /* Raising again should not hit our dummy handler */ g_test_signal_count = 0; raise(sig); TEST_ASSERT_EQUAL_INT(0, g_test_signal_count); /* Cleanup */ rmdir(dir); free(pre); free(dir); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_interrupt_handler_deletes_files_and_resets_handler); RUN_TEST(test_interrupt_handler_keeps_files_when_remove_files_false); RUN_TEST(test_interrupt_handler_zero_files); return UNITY_END(); }