coreutils / tests /csplit /tests_for_interrupt_handler.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 <signal.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <limits.h>
/* 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();
}