Merge remote-tracking branch 'origin/feat/existing' into dev
This commit is contained in:
@@ -104,6 +104,7 @@ partial, alternate, and planned behavior.
|
|||||||
| `--max-size <n>` | Skip files larger than n bytes |
|
| `--max-size <n>` | Skip files larger than n bytes |
|
||||||
| `--min-size <n>` | Skip files smaller than n bytes |
|
| `--min-size <n>` | Skip files smaller than n bytes |
|
||||||
| `--incremental` | Skip files unchanged since last transfer (size + mtime). Auto-enables `--preserve`. Incompatible with `-s`. |
|
| `--incremental` | Skip files unchanged since last transfer (size + mtime). Auto-enables `--preserve`. Incompatible with `-s`. |
|
||||||
|
| `--existing` | Skip files not already present at the destination; update existing files normally. |
|
||||||
| `--bwlimit <KB/s>` | Bandwidth limit in kilobytes per second |
|
| `--bwlimit <KB/s>` | Bandwidth limit in kilobytes per second |
|
||||||
| `--chunk-size <n>` | Chunk size in bytes (default: 10485760) |
|
| `--chunk-size <n>` | Chunk size in bytes (default: 10485760) |
|
||||||
| `--timeout <sec>` | I/O timeout in seconds (default: 30) |
|
| `--timeout <sec>` | I/O timeout in seconds (default: 30) |
|
||||||
|
|||||||
+3
-3
@@ -10,7 +10,7 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
| 🔀 Alt Arg | 4 | Functionality exists but under different flag/semantics |
|
| 🔀 Alt Arg | 4 | Functionality exists but under different flag/semantics |
|
||||||
| ⚠️ Partial | 5 | Flag parsed/stored but behavior incomplete |
|
| ⚠️ Partial | 5 | Flag parsed/stored but behavior incomplete |
|
||||||
| 🔄 Compatibility No-op | 1 | Flag is accepted for CLI compatibility but has no effect |
|
| 🔄 Compatibility No-op | 1 | Flag is accepted for CLI compatibility but has no effect |
|
||||||
| ❌ Not Implemented | 96 | Flag not recognized or no behavior |
|
| ❌ Not Implemented | 95 | Flag not recognized or no behavior |
|
||||||
| **Total** | **141** | |
|
| **Total** | **141** | |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -61,7 +61,7 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
| `-I`, `--ignore-times` | Don't skip files matching size+time | ❌ Not Implemented | |
|
| `-I`, `--ignore-times` | Don't skip files matching size+time | ❌ Not Implemented | |
|
||||||
| `--size-only` | Skip based on size only | ✅ Implemented | With `--incremental`, ignores mtime |
|
| `--size-only` | Skip based on size only | ✅ Implemented | With `--incremental`, ignores mtime |
|
||||||
| `-@`, `--modify-window=NUM` | Mod-time comparison accuracy | ✅ Implemented | Whole-second tolerance with nanosecond-aware comparisons |
|
| `-@`, `--modify-window=NUM` | Mod-time comparison accuracy | ✅ Implemented | Whole-second tolerance with nanosecond-aware comparisons |
|
||||||
| `--existing` | Skip creating new files on receiver | ❌ Not Implemented | |
|
| `--existing` | Skip creating new files on receiver | ✅ Implemented | Existing destination files continue through normal update handling |
|
||||||
| `--ignore-existing` | Skip updating existing files | ❌ Not Implemented | |
|
| `--ignore-existing` | Skip updating existing files | ❌ Not Implemented | |
|
||||||
| `--remove-source-files` | Sender removes regular files after confirmed transfer | ✅ Implemented | |
|
| `--remove-source-files` | Sender removes regular files after confirmed transfer | ✅ Implemented | |
|
||||||
|
|
||||||
@@ -248,7 +248,7 @@ Ranked by user demand, implementation complexity, and interoperability impact:
|
|||||||
| 1 | `--whole-file` / `-W` | Low | High — users expect opt-out of delta |
|
| 1 | `--whole-file` / `-W` | Low | High — users expect opt-out of delta |
|
||||||
| 2 | `--ignore-times` / `-I` | Low | Medium — useful for forcing re-transfer |
|
| 2 | `--ignore-times` / `-I` | Low | Medium — useful for forcing re-transfer |
|
||||||
| 3 | `--size-only` | Low | Medium — common migration scenario |
|
| 3 | `--size-only` | Low | Medium — common migration scenario |
|
||||||
| 4 | `--existing` / `--ignore-existing` | Low | Medium — common sync patterns |
|
| 4 | `--ignore-existing` | Low | Medium — common sync patterns |
|
||||||
| 5 | `--delete-during` | Medium | High — performance improvement |
|
| 5 | `--delete-during` | Medium | High — performance improvement |
|
||||||
| 6 | `--delay-updates` | Medium | High — atomic updates |
|
| 6 | `--delay-updates` | Medium | High — atomic updates |
|
||||||
| 7 | `--chmod` | Low | Medium — permission flexibility |
|
| 7 | `--chmod` | Low | Medium — permission flexibility |
|
||||||
|
|||||||
@@ -229,6 +229,7 @@ static const OptionEntry OPTION_TABLE[] = {
|
|||||||
{"--fsync", NULL, OPT_FLAG, offsetof(Config, use_fsync)},
|
{"--fsync", NULL, OPT_FLAG, offsetof(Config, use_fsync)},
|
||||||
{"--checksum", NULL, OPT_FLAG, offsetof(Config, checksum)},
|
{"--checksum", NULL, OPT_FLAG, offsetof(Config, checksum)},
|
||||||
{"--8-bit-output", "-8", OPT_FLAG, offsetof(Config, eight_bit_output)},
|
{"--8-bit-output", "-8", OPT_FLAG, offsetof(Config, eight_bit_output)},
|
||||||
|
{"--existing", NULL, OPT_FLAG, offsetof(Config, existing)},
|
||||||
|
|
||||||
{"--source-dir", NULL, OPT_STRING, offsetof(Config, send_directory)},
|
{"--source-dir", NULL, OPT_STRING, offsetof(Config, send_directory)},
|
||||||
{"--dest-dir", NULL, OPT_STRING, offsetof(Config, receive_root_directory)},
|
{"--dest-dir", NULL, OPT_STRING, offsetof(Config, receive_root_directory)},
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ void print_usage(void) {
|
|||||||
printf(" -I, --ignore-times Transfer files even when size and mtime match\n");
|
printf(" -I, --ignore-times Transfer files even when size and mtime match\n");
|
||||||
printf(" -@, --modify-window <sec> Modification time tolerance\n");
|
printf(" -@, --modify-window <sec> Modification time tolerance\n");
|
||||||
printf(" -u, --update Skip files newer than the source on receiver\n");
|
printf(" -u, --update Skip files newer than the source on receiver\n");
|
||||||
|
printf(" --existing Skip files not already present at destination\n");
|
||||||
printf(" --delta Delta transfer for changed files (requires --incremental)\n");
|
printf(" --delta Delta transfer for changed files (requires --incremental)\n");
|
||||||
printf(" -W, --whole-file Transfer changed files without delta processing\n");
|
printf(" -W, --whole-file Transfer changed files without delta processing\n");
|
||||||
printf(" --delta-block <n> Delta block size in bytes (default: %d)\n",
|
printf(" --delta-block <n> Delta block size in bytes (default: %d)\n",
|
||||||
|
|||||||
+10
-6
@@ -74,6 +74,7 @@ static void config_set_defaults(Config* config) {
|
|||||||
config->list_only = false;
|
config->list_only = false;
|
||||||
config->human_readable = false;
|
config->human_readable = false;
|
||||||
config->eight_bit_output = false;
|
config->eight_bit_output = false;
|
||||||
|
config->existing = false;
|
||||||
config->update = false;
|
config->update = false;
|
||||||
config->inplace = false;
|
config->inplace = false;
|
||||||
config->use_fsync = false;
|
config->use_fsync = false;
|
||||||
@@ -130,9 +131,10 @@ static bool validate_received_config(const Config* config) {
|
|||||||
valid_wire_bool(config->safe_links) && valid_wire_bool(config->copy_unsafe_links) &&
|
valid_wire_bool(config->safe_links) && valid_wire_bool(config->copy_unsafe_links) &&
|
||||||
valid_wire_bool(config->preserve_hard_links) && valid_wire_bool(config->preserve_acls) &&
|
valid_wire_bool(config->preserve_hard_links) && valid_wire_bool(config->preserve_acls) &&
|
||||||
valid_wire_bool(config->preserve_xattrs) && valid_wire_bool(config->preserve_devices) &&
|
valid_wire_bool(config->preserve_xattrs) && valid_wire_bool(config->preserve_devices) &&
|
||||||
valid_wire_bool(config->preserve_sparse) && valid_wire_bool(config->update) &&
|
valid_wire_bool(config->preserve_sparse) && valid_wire_bool(config->existing) &&
|
||||||
valid_wire_bool(config->inplace) && valid_wire_bool(config->append) &&
|
valid_wire_bool(config->update) && valid_wire_bool(config->inplace) &&
|
||||||
valid_wire_bool(config->use_fsync) && valid_wire_bool(config->append_verify) &&
|
valid_wire_bool(config->append) && valid_wire_bool(config->use_fsync) &&
|
||||||
|
valid_wire_bool(config->append_verify) &&
|
||||||
valid_wire_bool(config->delete_excluded) && valid_wire_bool(config->delete_after) &&
|
valid_wire_bool(config->delete_excluded) && valid_wire_bool(config->delete_after) &&
|
||||||
valid_wire_bool(config->relative) && valid_wire_bool(config->prune_empty_dirs) &&
|
valid_wire_bool(config->relative) && valid_wire_bool(config->prune_empty_dirs) &&
|
||||||
valid_wire_bool(config->partial) && valid_wire_bool(config->delete_before) &&
|
valid_wire_bool(config->partial) && valid_wire_bool(config->delete_before) &&
|
||||||
@@ -255,8 +257,9 @@ static bool send_file_options(int fd, const Config* c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool send_selection_options(int fd, const Config* c) {
|
static bool send_selection_options(int fd, const Config* c) {
|
||||||
return send_int(fd, c->update) && send_int(fd, c->inplace) && send_int(fd, c->append) &&
|
return send_int(fd, c->existing) && send_int(fd, c->update) && send_int(fd, c->inplace) &&
|
||||||
send_int(fd, c->use_fsync) && send_int(fd, c->append_verify) &&
|
send_int(fd, c->append) && send_int(fd, c->use_fsync) &&
|
||||||
|
send_int(fd, c->append_verify) &&
|
||||||
send_int(fd, c->delete_excluded) && send_int(fd, c->delete_after) &&
|
send_int(fd, c->delete_excluded) && send_int(fd, c->delete_after) &&
|
||||||
send_n_data(fd, &c->max_delete, sizeof(c->max_delete)) && send_int(fd, c->relative) &&
|
send_n_data(fd, &c->max_delete, sizeof(c->max_delete)) && send_int(fd, c->relative) &&
|
||||||
send_int(fd, c->prune_empty_dirs);
|
send_int(fd, c->prune_empty_dirs);
|
||||||
@@ -325,7 +328,8 @@ static bool receive_file_options(int fd, Config* c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool receive_selection_options(int fd, Config* c) {
|
static bool receive_selection_options(int fd, Config* c) {
|
||||||
bool* flags[] = {&c->update, &c->inplace, &c->append, &c->use_fsync,
|
bool* flags[] = {&c->existing, &c->update, &c->inplace, &c->append,
|
||||||
|
&c->use_fsync,
|
||||||
&c->append_verify, &c->delete_excluded, &c->delete_after};
|
&c->append_verify, &c->delete_excluded, &c->delete_after};
|
||||||
for (size_t i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
|
for (size_t i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
|
||||||
if (!receive_wire_bool(fd, flags[i]))
|
if (!receive_wire_bool(fd, flags[i]))
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ typedef struct Config {
|
|||||||
bool eight_bit_output;
|
bool eight_bit_output;
|
||||||
|
|
||||||
// Issue #127: Transfer modes
|
// Issue #127: Transfer modes
|
||||||
|
bool existing;
|
||||||
bool update;
|
bool update;
|
||||||
bool inplace;
|
bool inplace;
|
||||||
bool use_fsync;
|
bool use_fsync;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ bool file_save_to_disk(const char* root_directory, const File* file, const Confi
|
|||||||
const char* backup_dir = (config && config->backup_dir) ? config->backup_dir : NULL;
|
const char* backup_dir = (config && config->backup_dir) ? config->backup_dir : NULL;
|
||||||
const char* partial_dir = (config && config->partial_dir) ? config->partial_dir : NULL;
|
const char* partial_dir = (config && config->partial_dir) ? config->partial_dir : NULL;
|
||||||
char *confined_backup = NULL, *confined_partial = NULL, *disk_path = NULL;
|
char *confined_backup = NULL, *confined_partial = NULL, *disk_path = NULL;
|
||||||
|
char* destination_path = NULL;
|
||||||
char *backup_path = NULL, *parent_copy = NULL;
|
char *backup_path = NULL, *parent_copy = NULL;
|
||||||
|
|
||||||
if (!file || !file->path || !file->data || (file->data->size != 0 && !file->data->data) ||
|
if (!file || !file->path || !file->data || (file->data->size != 0 && !file->data->data) ||
|
||||||
@@ -54,13 +55,27 @@ bool file_save_to_disk(const char* root_directory, const File* file, const Confi
|
|||||||
|
|
||||||
const char* actual_root =
|
const char* actual_root =
|
||||||
(partial_dir && config && config->partial) ? confined_partial : root_directory;
|
(partial_dir && config && config->partial) ? confined_partial : root_directory;
|
||||||
|
destination_path = path_cat(root_directory, file->path);
|
||||||
disk_path = path_cat(actual_root, file->path);
|
disk_path = path_cat(actual_root, file->path);
|
||||||
if (disk_path == NULL) {
|
if (destination_path == NULL || disk_path == NULL) {
|
||||||
free(confined_backup);
|
free(confined_backup);
|
||||||
free(confined_partial);
|
free(confined_partial);
|
||||||
|
free(destination_path);
|
||||||
|
free(disk_path);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --existing checks the final destination, not a temporary partial path. */
|
||||||
|
if (config && config->existing && !file_path_exists_secure(destination_path)) {
|
||||||
|
free(confined_backup);
|
||||||
|
free(confined_partial);
|
||||||
|
free(destination_path);
|
||||||
|
free(disk_path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
free(destination_path);
|
||||||
|
destination_path = NULL;
|
||||||
|
|
||||||
/* --update is receiver-side policy: never replace a newer destination.
|
/* --update is receiver-side policy: never replace a newer destination.
|
||||||
The secure stat does not require read permission on the destination. */
|
The secure stat does not require read permission on the destination. */
|
||||||
if (config && config->update && file_destination_is_newer_secure(disk_path, file->metadata)) {
|
if (config && config->update && file_destination_is_newer_secure(disk_path, file->metadata)) {
|
||||||
@@ -110,6 +125,7 @@ bool file_save_to_disk(const char* root_directory, const File* file, const Confi
|
|||||||
free(backup_path);
|
free(backup_path);
|
||||||
free(confined_backup);
|
free(confined_backup);
|
||||||
free(confined_partial);
|
free(confined_partial);
|
||||||
|
free(destination_path);
|
||||||
free(disk_path);
|
free(disk_path);
|
||||||
return ok;
|
return ok;
|
||||||
|
|
||||||
@@ -118,6 +134,7 @@ fail:
|
|||||||
free(backup_path);
|
free(backup_path);
|
||||||
free(confined_backup);
|
free(confined_backup);
|
||||||
free(confined_partial);
|
free(confined_partial);
|
||||||
|
free(destination_path);
|
||||||
free(disk_path);
|
free(disk_path);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -412,66 +412,6 @@ class TestIncremental:
|
|||||||
mismatches, missing = verify_transfer(SOURCE_DIR, received)
|
mismatches, missing = verify_transfer(SOURCE_DIR, received)
|
||||||
assert not missing, f"Missing: {missing}"
|
assert not missing, f"Missing: {missing}"
|
||||||
assert not mismatches, f"Mismatch: {mismatches}"
|
assert not mismatches, f"Mismatch: {mismatches}"
|
||||||
clean_dir(DEST_DIR)
|
|
||||||
result, _ = run_client(SOURCE_DIR, DEST_DIR, flags=["-M"], port=shared_server.port)
|
|
||||||
assert result.returncode == 0
|
|
||||||
|
|
||||||
received = get_dest_received_dir(DEST_DIR, SOURCE_DIR)
|
|
||||||
source_file = os.path.join(SOURCE_DIR, "small.txt")
|
|
||||||
received_file = os.path.join(received, "small.txt")
|
|
||||||
source_stat = os.stat(source_file)
|
|
||||||
with open(received_file, "wb") as f:
|
|
||||||
f.write(b"stale data!\n")
|
|
||||||
os.utime(received_file, (source_stat.st_atime, source_stat.st_mtime))
|
|
||||||
|
|
||||||
result, _ = run_client(SOURCE_DIR, DEST_DIR,
|
|
||||||
flags=["-M", "--incremental", "--ignore-times"],
|
|
||||||
port=shared_server.port)
|
|
||||||
assert result.returncode == 0, f"Ignore-times sync failed: {result.stderr[:200]}"
|
|
||||||
with open(received_file, "rb") as f:
|
|
||||||
assert f.read() == b"hello world\n"
|
|
||||||
|
|
||||||
def test_modify_window_allows_subsecond_mtime_difference(self, shared_server):
|
|
||||||
clean_dir(DEST_DIR)
|
|
||||||
result, _ = run_client(SOURCE_DIR, DEST_DIR, flags=["-M"], port=shared_server.port)
|
|
||||||
assert result.returncode == 0
|
|
||||||
|
|
||||||
received = get_dest_received_dir(DEST_DIR, SOURCE_DIR)
|
|
||||||
source_file = os.path.join(SOURCE_DIR, "small.txt")
|
|
||||||
received_file = os.path.join(received, "small.txt")
|
|
||||||
source_stat = os.stat(source_file)
|
|
||||||
with open(received_file, "wb") as f:
|
|
||||||
f.write(b"modified!!!\n")
|
|
||||||
os.utime(received_file, ns=(source_stat.st_atime_ns,
|
|
||||||
source_stat.st_mtime_ns - 1500000000))
|
|
||||||
|
|
||||||
result, _ = run_client(SOURCE_DIR, DEST_DIR,
|
|
||||||
flags=["-M", "--incremental", "--modify-window=2"],
|
|
||||||
port=shared_server.port)
|
|
||||||
assert result.returncode == 0, f"Modify-window sync failed: {result.stderr[:200]}"
|
|
||||||
with open(received_file, "rb") as f:
|
|
||||||
assert f.read() == b"modified!!!\n"
|
|
||||||
|
|
||||||
def test_whole_file_disables_delta_and_keeps_compression(self, shared_server):
|
|
||||||
clean_dir(DEST_DIR)
|
|
||||||
result, _ = run_client(SOURCE_DIR, DEST_DIR, flags=["-M"], port=shared_server.port)
|
|
||||||
assert result.returncode == 0
|
|
||||||
|
|
||||||
source_file = os.path.join(SOURCE_DIR, "medium.txt")
|
|
||||||
with open(source_file, "wb") as f:
|
|
||||||
f.write(b"whole-file replacement\n" * 5000)
|
|
||||||
|
|
||||||
result, _ = run_client(
|
|
||||||
SOURCE_DIR,
|
|
||||||
DEST_DIR,
|
|
||||||
flags=["-M", "--incremental", "--delta", "-W", "-c"],
|
|
||||||
port=shared_server.port,
|
|
||||||
)
|
|
||||||
assert result.returncode == 0, f"Whole-file sync failed: {(result.stderr or result.stdout)[:200]}"
|
|
||||||
received = get_dest_received_dir(DEST_DIR, SOURCE_DIR)
|
|
||||||
mismatches, missing = verify_transfer(SOURCE_DIR, received)
|
|
||||||
assert not missing, f"Missing: {missing}"
|
|
||||||
assert not mismatches, f"Mismatch: {mismatches}"
|
|
||||||
|
|
||||||
|
|
||||||
class TestUpdate:
|
class TestUpdate:
|
||||||
@@ -529,6 +469,34 @@ class TestUpdate:
|
|||||||
assert f.read() == b"hello world\n"
|
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:
|
class TestDelete:
|
||||||
def test_delete_removes_extra_files(self, shared_server):
|
def test_delete_removes_extra_files(self, shared_server):
|
||||||
clean_dir(DEST_DIR)
|
clean_dir(DEST_DIR)
|
||||||
|
|||||||
@@ -504,6 +504,17 @@ static void test_parse_args_fsync() {
|
|||||||
config_delete(cfg);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
static void test_parse_args_ignore_times() {
|
static void test_parse_args_ignore_times() {
|
||||||
Config* cfg = config_create();
|
Config* cfg = config_create();
|
||||||
char* argv[] = {"fastsync", "-I", "/src", "/dst"};
|
char* argv[] = {"fastsync", "-I", "/src", "/dst"};
|
||||||
@@ -649,6 +660,7 @@ void test_client_cli() {
|
|||||||
test_parse_args_update();
|
test_parse_args_update();
|
||||||
test_parse_args_archive();
|
test_parse_args_archive();
|
||||||
test_parse_args_fsync();
|
test_parse_args_fsync();
|
||||||
|
test_parse_args_existing();
|
||||||
test_parse_args_ignore_times();
|
test_parse_args_ignore_times();
|
||||||
test_parse_args_8_bit_output();
|
test_parse_args_8_bit_output();
|
||||||
test_parse_args_stderr_modes();
|
test_parse_args_stderr_modes();
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ static void test_config_send_receive() {
|
|||||||
send_cfg->chunk_size = 1024;
|
send_cfg->chunk_size = 1024;
|
||||||
send_cfg->eight_bit_output = true;
|
send_cfg->eight_bit_output = true;
|
||||||
send_cfg->modify_window = 4;
|
send_cfg->modify_window = 4;
|
||||||
|
send_cfg->existing = true;
|
||||||
|
|
||||||
/* Use socketpair for bidirectional communication */
|
/* Use socketpair for bidirectional communication */
|
||||||
int p[2];
|
int p[2];
|
||||||
@@ -178,6 +179,8 @@ static void test_config_send_receive() {
|
|||||||
ok = false;
|
ok = false;
|
||||||
if (recv_cfg->modify_window != 4)
|
if (recv_cfg->modify_window != 4)
|
||||||
ok = false;
|
ok = false;
|
||||||
|
if (!recv_cfg->existing)
|
||||||
|
ok = false;
|
||||||
}
|
}
|
||||||
config_delete(recv_cfg);
|
config_delete(recv_cfg);
|
||||||
close(p[0]);
|
close(p[0]);
|
||||||
|
|||||||
@@ -113,6 +113,50 @@ static void test_file_save_to_disk_with_fsync_config() {
|
|||||||
rmdir("test_save_fsync_tmp");
|
rmdir("test_save_fsync_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() {
|
static void test_file_write_to_disk_basic() {
|
||||||
const char* content = "Basic file_write_to_disk test";
|
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),
|
EXPECT_TRUE(file_write_to_disk("test_file_write_to_disk_basic.txt", content, strlen(content),
|
||||||
@@ -495,6 +539,7 @@ void test_file() {
|
|||||||
test_file_load_data_missing_file();
|
test_file_load_data_missing_file();
|
||||||
test_file_save_to_disk();
|
test_file_save_to_disk();
|
||||||
test_file_save_to_disk_with_fsync_config();
|
test_file_save_to_disk_with_fsync_config();
|
||||||
|
test_file_save_to_disk_existing();
|
||||||
test_file_write_to_disk_basic();
|
test_file_write_to_disk_basic();
|
||||||
test_file_write_to_disk_with_fsync();
|
test_file_write_to_disk_with_fsync();
|
||||||
test_file_write_to_disk_creates_dirs();
|
test_file_write_to_disk_creates_dirs();
|
||||||
|
|||||||
Reference in New Issue
Block a user