test: delete-policy unit and integration coverage
Unit: walker all-or-nothing bounds (exceeded -> nothing removed + distinct result; exact bound -> deletes), protected-prefix skipping, config wire round-trip for force_delete/delete_excluded/prune_empty_dirs/max_delete, CLI parse/validation for the new flags. Integration (TestDeletePolicy): default delete-excluded protection and --delete-excluded opt-out (single-thread, -m, early --delete-before, excluded-dir subtrees), --max-delete all-or-nothing over and at the limit, --force file-over-nonempty-dir replacement, --prune-empty-dirs (--dirs mode + recursion-mode parity), and --ignore-errors keeping deletion active across a genuine scan I/O error (run as an unprivileged user).
This commit is contained in:
@@ -2,9 +2,204 @@
|
||||
#include "utils.h"
|
||||
#include "protocol.h"
|
||||
#include "test_utils.h"
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <threads.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* ---- delete-walker tests ---- */
|
||||
|
||||
static char* make_walk_root(const char* tag) {
|
||||
char* path = malloc(256);
|
||||
if (!path)
|
||||
return NULL;
|
||||
snprintf(path, 256, "/tmp/fastsync_walk_%s_%d", tag, (int)getpid());
|
||||
rmdir(path);
|
||||
if (mkdir(path, 0755) != 0) {
|
||||
free(path);
|
||||
return NULL;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
static bool write_file_at(const char* dir, const char* name, const char* content) {
|
||||
char* path = path_cat(dir, name);
|
||||
if (!path)
|
||||
return false;
|
||||
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
bool ok = fd >= 0;
|
||||
if (fd >= 0) {
|
||||
if (content) {
|
||||
const char* p = content;
|
||||
size_t remaining = strlen(content);
|
||||
while (remaining > 0) {
|
||||
ssize_t n = write(fd, p, remaining);
|
||||
if (n <= 0) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
p += n;
|
||||
remaining -= (size_t)n;
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
free(path);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool file_exists(const char* dir, const char* name) {
|
||||
char* path = path_cat(dir, name);
|
||||
bool exists = path && access(path, F_OK) == 0;
|
||||
free(path);
|
||||
return exists;
|
||||
}
|
||||
|
||||
static bool dir_exists(const char* dir, const char* name) {
|
||||
char* path = path_cat(dir, name);
|
||||
struct stat st;
|
||||
bool exists = path && stat(path, &st) == 0 && S_ISDIR(st.st_mode);
|
||||
free(path);
|
||||
return exists;
|
||||
}
|
||||
|
||||
static int make_subdir(const char* root, const char* name) {
|
||||
char* path = path_cat(root, name);
|
||||
int rc = -1;
|
||||
if (path) {
|
||||
rc = mkdir(path, 0755);
|
||||
free(path);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void remove_walk_tree(const char* path) {
|
||||
DIR* dir = opendir(path);
|
||||
if (!dir) {
|
||||
rmdir(path);
|
||||
return;
|
||||
}
|
||||
const struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
|
||||
continue;
|
||||
char* child = path_cat(path, entry->d_name);
|
||||
if (child) {
|
||||
struct stat st;
|
||||
if (lstat(child, &st) == 0 && S_ISDIR(st.st_mode))
|
||||
remove_walk_tree(child);
|
||||
else
|
||||
unlink(child);
|
||||
free(child);
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
rmdir(path);
|
||||
}
|
||||
|
||||
static ArrayList* make_manifest_strings(const char* const* entries, int count) {
|
||||
ArrayList* manifest = array_list_create(free);
|
||||
if (!manifest)
|
||||
return NULL;
|
||||
for (int i = 0; i < count; i++) {
|
||||
char* dup = str_dup(entries[i]);
|
||||
if (!dup || !array_list_add(manifest, dup)) {
|
||||
free(dup);
|
||||
array_list_delete(manifest);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return manifest;
|
||||
}
|
||||
|
||||
static void test_walker_removes_extras_keeps_manifest_and_protected() {
|
||||
char* root = make_walk_root("basic");
|
||||
EXPECT_NOT_NULL(root);
|
||||
EXPECT_TRUE(write_file_at(root, "a.txt", "extra"));
|
||||
EXPECT_TRUE(write_file_at(root, "keep.txt", "kept"));
|
||||
EXPECT_EQ_INT(make_subdir(root, "d"), 0);
|
||||
EXPECT_TRUE(write_file_at(root, "d/e.txt", "extra"));
|
||||
EXPECT_TRUE(write_file_at(root, "d/k.txt", "kept"));
|
||||
EXPECT_EQ_INT(make_subdir(root, "prot"), 0);
|
||||
EXPECT_TRUE(write_file_at(root, "prot/f.txt", "untouched"));
|
||||
|
||||
const char* keeps[] = {"keep.txt", "d/k.txt"};
|
||||
ArrayList* manifest = make_manifest_strings(keeps, 2);
|
||||
EXPECT_NOT_NULL(manifest);
|
||||
DeleteSkipEntry skip = {"prot", false};
|
||||
size_t deleted = 0;
|
||||
DeleteWalkResult result = delete_extras_limited(root, manifest, 100000, &skip, 1, &deleted);
|
||||
EXPECT_EQ_INT((int)result, (int)DELETE_WALK_OK);
|
||||
EXPECT_FALSE(file_exists(root, "a.txt"));
|
||||
EXPECT_TRUE(file_exists(root, "keep.txt"));
|
||||
EXPECT_FALSE(file_exists(root, "d/e.txt"));
|
||||
EXPECT_TRUE(file_exists(root, "d/k.txt"));
|
||||
EXPECT_TRUE(dir_exists(root, "d"));
|
||||
EXPECT_TRUE(file_exists(root, "prot/f.txt"));
|
||||
EXPECT_TRUE(deleted >= 2);
|
||||
array_list_delete(manifest);
|
||||
remove_walk_tree(root);
|
||||
}
|
||||
|
||||
static void test_walker_max_delete_exceeded_deletes_nothing() {
|
||||
char* root = make_walk_root("maxdel");
|
||||
EXPECT_NOT_NULL(root);
|
||||
EXPECT_TRUE(write_file_at(root, "a.txt", "extra"));
|
||||
EXPECT_TRUE(write_file_at(root, "b.txt", "extra"));
|
||||
EXPECT_TRUE(write_file_at(root, "c.txt", "extra"));
|
||||
const char* keeps[1] = {NULL};
|
||||
ArrayList* manifest = make_manifest_strings(keeps, 0);
|
||||
EXPECT_NOT_NULL(manifest);
|
||||
size_t deleted = 999;
|
||||
DeleteWalkResult result = delete_extras_limited(root, manifest, 2, NULL, 0, &deleted);
|
||||
EXPECT_EQ_INT((int)result, (int)DELETE_WALK_LIMIT_EXCEEDED);
|
||||
EXPECT_EQ_INT((int)deleted, 0);
|
||||
EXPECT_TRUE(file_exists(root, "a.txt"));
|
||||
EXPECT_TRUE(file_exists(root, "b.txt"));
|
||||
EXPECT_TRUE(file_exists(root, "c.txt"));
|
||||
array_list_delete(manifest);
|
||||
remove_walk_tree(root);
|
||||
}
|
||||
|
||||
static void test_walker_max_delete_exact_bound_deletes() {
|
||||
char* root = make_walk_root("maxdel2");
|
||||
EXPECT_NOT_NULL(root);
|
||||
EXPECT_TRUE(write_file_at(root, "a.txt", "extra"));
|
||||
EXPECT_TRUE(write_file_at(root, "b.txt", "extra"));
|
||||
const char* keeps[1] = {NULL};
|
||||
ArrayList* manifest = make_manifest_strings(keeps, 0);
|
||||
EXPECT_NOT_NULL(manifest);
|
||||
size_t deleted = 0;
|
||||
DeleteWalkResult result = delete_extras_limited(root, manifest, 2, NULL, 0, &deleted);
|
||||
EXPECT_EQ_INT((int)result, (int)DELETE_WALK_OK);
|
||||
EXPECT_EQ_INT((int)deleted, 2);
|
||||
EXPECT_FALSE(file_exists(root, "a.txt"));
|
||||
EXPECT_FALSE(file_exists(root, "b.txt"));
|
||||
array_list_delete(manifest);
|
||||
remove_walk_tree(root);
|
||||
}
|
||||
|
||||
static void test_walker_unlimited_deletes_all() {
|
||||
char* root = make_walk_root("unlim");
|
||||
EXPECT_NOT_NULL(root);
|
||||
EXPECT_TRUE(write_file_at(root, "a.txt", "extra"));
|
||||
EXPECT_TRUE(write_file_at(root, "b.txt", "extra"));
|
||||
EXPECT_EQ_INT(make_subdir(root, "emptydir"), 0);
|
||||
const char* keeps[1] = {NULL};
|
||||
ArrayList* manifest = make_manifest_strings(keeps, 0);
|
||||
EXPECT_NOT_NULL(manifest);
|
||||
EXPECT_TRUE(delete_extras(root, manifest));
|
||||
EXPECT_FALSE(file_exists(root, "a.txt"));
|
||||
EXPECT_FALSE(file_exists(root, "b.txt"));
|
||||
EXPECT_FALSE(dir_exists(root, "emptydir"));
|
||||
array_list_delete(manifest);
|
||||
remove_walk_tree(root);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
bool eight_bit_output;
|
||||
@@ -24,6 +219,11 @@ static int escape_thread(void* arg) {
|
||||
}
|
||||
|
||||
void test_shared_utils() {
|
||||
test_walker_removes_extras_keeps_manifest_and_protected();
|
||||
test_walker_max_delete_exceeded_deletes_nothing();
|
||||
test_walker_max_delete_exact_bound_deletes();
|
||||
test_walker_unlimited_deletes_all();
|
||||
|
||||
char formatted[32];
|
||||
EXPECT_TRUE(format_human_bytes(0, formatted, sizeof(formatted)));
|
||||
EXPECT_EQ_STR(formatted, "0 B");
|
||||
|
||||
Reference in New Issue
Block a user