refactor(parity): address code-quality review findings
- cli: remove UB in --bwlimit scaling (range-check the double product before casting, drop atoi for the +/-1 form) and add huge/boundary unit tests - test: widen the CI throttle wall-clock band to [1.5, 4.5]s with a 2s cross-tolerance so a loaded runner cannot flake it - log: drop the unused LOG_INFO_BACKUP bit; --info=backup is accepted-but- silent like the other rsync-only categories - client_send: remove the duplicate delete_display_path forward declaration - utils: add non-allocating utils_strip_transfer_root and use it from scanner_note_nonreg and delete_display_path (was duplicated logic) - scanner: lstat() instead of stat() when re-reading an empty dir's metadata - file: drop the no-op else-if and the redundant ELOOP arm in file_ensure_directory_secure (symlinks are refused anyway) - format/stats: document literal_data as whole-file accurate (delta upper bound) instead of claiming literal bytes sent - docs: refresh stale protocol 2.26.0 labels to 2.27.0
This commit is contained in:
@@ -92,9 +92,13 @@ class TestBwlimitParity:
|
||||
result, fast_secs = run_client(source, dest, flags=["-a", "--bwlimit=2048"],
|
||||
port=shared_server.port)
|
||||
assert result.returncode == 0, (result.stderr or result.stdout)[:200]
|
||||
assert fast_secs > 1.0, f"fastsync throttled too little: {fast_secs:.2f}s"
|
||||
# Both rendezvous near 2 s; allow a generous band for CI scheduling.
|
||||
assert abs(fast_secs - rsync_secs) < 1.0, (
|
||||
# 4 MiB at 2 MiB/s rendezvous near 2 s. Use a coarse band on each side
|
||||
# (an unthrottled transfer finishes well under 1.5 s) plus a generous
|
||||
# cross-tolerance so a loaded CI runner cannot flake the parity assert.
|
||||
lo, hi = 1.5, 4.5
|
||||
assert lo <= fast_secs <= hi, f"fastsync throttle out of band: {fast_secs:.2f}s"
|
||||
assert lo <= rsync_secs <= hi, f"rsync throttle out of band: {rsync_secs:.2f}s"
|
||||
assert abs(fast_secs - rsync_secs) < 2.0, (
|
||||
f"fastsync {fast_secs:.2f}s vs rsync {rsync_secs:.2f}s"
|
||||
)
|
||||
|
||||
|
||||
+53
-3
@@ -1348,8 +1348,8 @@ static void test_parse_args_info_name_and_help() {
|
||||
|
||||
/* rsync 3.4.1's full --info/--debug vocabulary parses. The info categories
|
||||
* with a FastSync event set their flag; the remaining rsync-only categories
|
||||
* (mount/symsafe/syms) parse but stay silent. Every --debug category listed
|
||||
* here is FastSync-silent, so debug_level stays 0. */
|
||||
* (backup/mount/symsafe/syms) parse but stay silent. Every --debug category
|
||||
* listed here is FastSync-silent, so debug_level stays 0. */
|
||||
static void test_parse_args_rsync_flag_vocabulary_accepted() {
|
||||
Config* cfg = config_create();
|
||||
char* argv[] = {"fastsync", "--info=backup,del,flist,mount,nonreg,progress,remove,symsafe,syms",
|
||||
@@ -1361,7 +1361,7 @@ static void test_parse_args_rsync_flag_vocabulary_accepted() {
|
||||
int positional_count = 0;
|
||||
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv, positional_args, &positional_count), 0);
|
||||
EXPECT_EQ_INT(cfg->info_level, LOG_INFO_BACKUP | LOG_INFO_DEL | LOG_INFO_FLIST | LOG_INFO_NONREG |
|
||||
EXPECT_EQ_INT(cfg->info_level, LOG_INFO_DEL | LOG_INFO_FLIST | LOG_INFO_NONREG |
|
||||
LOG_INFO_PROGRESS | LOG_INFO_REMOVE);
|
||||
EXPECT_EQ_INT(cfg->debug_level, 0);
|
||||
config_delete(cfg);
|
||||
@@ -3932,6 +3932,55 @@ static void test_parse_args_bwlimit_rsync_units() {
|
||||
io_set_bwlimit(0);
|
||||
}
|
||||
|
||||
/* Huge/malformed --bwlimit values must be rejected (not accepted or UB) and
|
||||
* oversized-but-representable ones must still be accepted: the scaling used to
|
||||
* be done with an unchecked signed double->long long cast, which is undefined
|
||||
* when the product leaves long long's range. */
|
||||
static void test_parse_args_bwlimit_huge_and_boundary() {
|
||||
struct {
|
||||
const char* value;
|
||||
unsigned long long expected; /* bytes/sec, ignored when !ok */
|
||||
int ok;
|
||||
} cases[] = {
|
||||
/* Malformed / non-numeric prefixes. */
|
||||
{"1e300", 0, 0},
|
||||
{"99999999999999999999999999e3", 0, 0},
|
||||
/* Products that exceed LLONG_MAX at every suffix. */
|
||||
{"99999999999999999999999999", 0, 0},
|
||||
{"99999999999999999999999999K", 0, 0},
|
||||
{"100000000000000000000P", 0, 0},
|
||||
{"99999999999999999999999999999999999999999999999999B", 0, 0},
|
||||
/* `strtod` overflow to +inf must be caught by the isfinite() guard. */
|
||||
{"9999999999999999999999999999999999999999999999999999999999999999999999"
|
||||
"9999999999999999999999999999999999999999999999999999999999999999999999"
|
||||
"99999999999999999999999999999999999999999999999999999999999999999999999",
|
||||
0, 0},
|
||||
/* 2^52 KiB/s: the largest power-of-two scaling that still fits. */
|
||||
{"4503599627370496", 4611686018427387904ULL, 1},
|
||||
/* The +/-1 suffix forms accepted by rsync. */
|
||||
{"1+1", 1024ULL, 1},
|
||||
{"1-1", 1024ULL, 1},
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
|
||||
Config* cfg = config_create();
|
||||
EXPECT_NOT_NULL(cfg);
|
||||
int positional_args[2];
|
||||
int positional_count = 0;
|
||||
char option[1024];
|
||||
snprintf(option, sizeof(option), "--bwlimit=%s", cases[i].value);
|
||||
char* argv[] = {"fastsync", option, "/src", "/dst"};
|
||||
int rc = parse_args(cfg, 4, argv, positional_args, &positional_count);
|
||||
if (cases[i].ok) {
|
||||
EXPECT_EQ_INT(rc, 0);
|
||||
EXPECT_TRUE(io_get_bwlimit() == cases[i].expected);
|
||||
} else {
|
||||
EXPECT_EQ_INT(rc, -1);
|
||||
}
|
||||
config_delete(cfg);
|
||||
io_set_bwlimit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* --dry-run must not emit a batch file, so it is rejected alongside
|
||||
* --read-batch/--only-write-batch. */
|
||||
static void test_validate_config_dry_run_rejects_write_batch() {
|
||||
@@ -4642,6 +4691,7 @@ void test_client_cli() {
|
||||
test_parse_args_pattern_file_oversized_rejected();
|
||||
test_parse_args_unsigned_options_reject_sign();
|
||||
test_parse_args_bwlimit_rsync_units();
|
||||
test_parse_args_bwlimit_huge_and_boundary();
|
||||
test_validate_config_dry_run_rejects_write_batch();
|
||||
test_parse_args_short_clustering();
|
||||
test_parse_args_attached_short_values();
|
||||
|
||||
+1
-1
@@ -2921,7 +2921,7 @@ static unsigned long long capture_wire_hash(const Config* cfg, size_t* out_len)
|
||||
return h;
|
||||
}
|
||||
|
||||
/* Byte-for-byte wire compatibility guard (protocol 2.26.0). The expected hash
|
||||
/* Byte-for-byte wire compatibility guard (protocol 2.27.0). The expected hash
|
||||
* pins the pre-X-macro byte stream; the refactor MUST NOT change it. */
|
||||
static void test_config_wire_golden() {
|
||||
if (is_running_under_valgrind())
|
||||
|
||||
Reference in New Issue
Block a user