Add rsync-compatible existing option
CI / lint (pull_request) Successful in 11s
CI / sanitizers (undefined) (pull_request) Successful in 38s
CI / sanitizers (address) (pull_request) Successful in 39s
CI / fuzz-build (pull_request) Successful in 14s
CI / coverage (pull_request) Successful in 31s
CI / build-and-test (pull_request) Successful in 1m15s
CI / valgrind (pull_request) Successful in 33s

This commit is contained in:
2026-09-03 17:11:33 +02:00
parent 190fc5d300
commit f75db195bd
10 changed files with 127 additions and 14 deletions
+28
View File
@@ -214,6 +214,34 @@ class TestIncremental:
assert f.read() == b"hello world\n"
class TestExisting:
def test_existing_updates_existing_and_skips_new(self, shared_server):
clean_dir(DEST_DIR)
result, _ = run_client(SOURCE_DIR, DEST_DIR, flags=["-M"], port=shared_server.port)
assert result.returncode == 0, f"Initial sync failed: {(result.stderr or result.stdout)[:200]}"
received = get_dest_received_dir(DEST_DIR, SOURCE_DIR)
source_file = os.path.join(SOURCE_DIR, "small.txt")
new_source_file = os.path.join(SOURCE_DIR, "new-existing-test.txt")
with open(source_file, "wb") as f:
f.write(b"updated existing content\n")
with open(new_source_file, "wb") as f:
f.write(b"this file must not be created\n")
try:
result, _ = run_client(SOURCE_DIR, DEST_DIR,
flags=["-M", "--existing"], port=shared_server.port)
assert result.returncode == 0, f"--existing sync failed: {(result.stderr or result.stdout)[:200]}"
with open(os.path.join(received, "small.txt"), "rb") as f:
assert f.read() == b"updated existing content\n"
assert not os.path.exists(os.path.join(received, "new-existing-test.txt"))
finally:
os.unlink(new_source_file)
with open(source_file, "wb") as f:
f.write(b"hello world\n")
class TestDelete:
def test_delete_removes_extra_files(self, shared_server):
clean_dir(DEST_DIR)
+12
View File
@@ -339,6 +339,17 @@ static void test_parse_args_archive() {
config_delete(cfg);
}
static void test_parse_args_existing() {
Config* cfg = config_create();
char* argv[] = {"fastsync", "--existing", "/src", "/dst"};
int positional_args[2];
int positional_count = 0;
EXPECT_EQ_INT(parse_args(cfg, 4, argv, positional_args, &positional_count), 0);
EXPECT_TRUE(cfg->existing);
config_delete(cfg);
}
void test_client_cli() {
test_validate_config_required_paths();
test_validate_config_incompatible_options();
@@ -360,4 +371,5 @@ void test_client_cli() {
test_parse_args_unknown_option();
test_parse_args_rejects_unimplemented_options();
test_parse_args_archive();
test_parse_args_existing();
}
+3
View File
@@ -126,6 +126,7 @@ static void test_config_send_receive() {
send_cfg->use_metadata = true;
send_cfg->compression_level = 5;
send_cfg->chunk_size = 1024;
send_cfg->existing = true;
/* Use socketpair for bidirectional communication */
int p[2];
@@ -160,6 +161,8 @@ static void test_config_send_receive() {
ok = false;
if (recv_cfg->chunk_size != 1024)
ok = false;
if (!recv_cfg->existing)
ok = false;
}
config_delete(recv_cfg);
close(p[0]);
+45
View File
@@ -88,6 +88,50 @@ static void test_file_save_to_disk() {
rmdir("test_save_tmp");
}
static void test_file_save_to_disk_existing() {
const char* root = "test_existing_tmp";
const char* existing_path = "test_existing_tmp/existing.txt";
const char* missing_path = "test_existing_tmp/missing.txt";
EXPECT_TRUE(file_write_to_disk(existing_path, "old", 3, false, false));
Config* cfg = config_create();
EXPECT_NOT_NULL(cfg);
cfg->existing = true;
File* existing = file_create("existing.txt");
EXPECT_NOT_NULL(existing);
existing->data->data = malloc(3);
EXPECT_NOT_NULL(existing->data->data);
memcpy(existing->data->data, "new", 3);
existing->data->size = 3;
EXPECT_TRUE(file_save_to_disk(root, existing, cfg));
file_destroy(existing);
File* missing = file_create("missing.txt");
EXPECT_NOT_NULL(missing);
missing->data->data = malloc(7);
EXPECT_NOT_NULL(missing->data->data);
memcpy(missing->data->data, "skipped", 7);
missing->data->size = 7;
EXPECT_TRUE(file_save_to_disk(root, missing, cfg));
file_destroy(missing);
FILE* fp = fopen(existing_path, "rb");
char content[4] = {0};
EXPECT_NOT_NULL(fp);
// cppcheck-suppress knownConditionTrueFalse
if (fp) {
EXPECT_EQ_INT((int)fread(content, 1, 3, fp), 3);
fclose(fp);
}
EXPECT_EQ_STR(content, "new");
EXPECT_EQ_INT(access(missing_path, F_OK), -1);
config_delete(cfg);
unlink(existing_path);
rmdir(root);
}
static void test_file_write_to_disk_basic() {
const char* content = "Basic file_write_to_disk test";
EXPECT_TRUE(file_write_to_disk("test_file_write_to_disk_basic.txt", content, strlen(content),
@@ -458,6 +502,7 @@ void test_file() {
test_file_load_data();
test_file_load_data_missing_file();
test_file_save_to_disk();
test_file_save_to_disk_existing();
test_file_write_to_disk_basic();
test_file_write_to_disk_creates_dirs();
test_file_write_to_disk_does_not_follow_symlink();