#291: - Compile --exclude/--include/--exclude-from/--include-from into the SAME ordered rule list as --filter/-f (first match wins), so the common `--include='*.txt' --exclude='*'` idiom and include-alone semantics match rsync. The legacy per-kind scanner arrays are no longer applied. - -x/--one-file-system emits the cross-device mount-point directory entry (empty) instead of dropping it, in both the sequential and parallel scanners. - Stop passing the legacy arrays to the scanner; document -f is --filter. #292: - New src/shared/format.c/.h: rsync "big_num" (comma-grouped integers) and decimal -h human sizes, %M/%t timestamp, and the STATUS_DEST_INFO codec. - Receiver answers each STATUS_CHECK with a pre-transfer destination snapshot (new report_dest_info wire field + STATUS_DEST_INFO, PROTOCOL_VERSION 2.23.0) so the sender can render true itemize columns. - Itemize now emits rsync-correct update/type chars and c/s/t/p/o/g columns for files, dirs, symlinks and hard links, comparing size/time/perms/owner/ group against the reported destination. - --out-format gains %i %n %f %l %b %M %t %o %p %B %U %G %L; %f is the relative display path, %M the YYYY/MM/DD-HH:MM:SS form, %b the literal bytes sent. - --list-only prints transfer-relative names, directory entries and ls-style grouped sizes. - --stats prints rsync's multi-line block on stdout; -h uses decimal units. Tests: unit tests for the filter ordering, format primitives, itemize columns; integration + differential tests against real rsync 3.4.1 for itemize/out-format/list-only/selection and -x. Golden wire len/hash and protocol version strings updated for 2.23.0.
192 lines
5.6 KiB
C
192 lines
5.6 KiB
C
#include "test_change_list.h"
|
|
#include "change_list.h"
|
|
#include "config.h"
|
|
#include "test_utils.h"
|
|
#include "utils.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
static ChangeEvent sample_event(void) {
|
|
ChangeEvent event;
|
|
memset(&event, 0, sizeof(event));
|
|
event.path = "src/sub/file.txt";
|
|
event.name = "sub/file.txt";
|
|
event.decision = CHANGE_SENT;
|
|
event.is_directory = false;
|
|
event.size = 12345;
|
|
event.bytes_sent = 999;
|
|
event.mtime_sec = 1700000000;
|
|
event.mtime_nsec = 0;
|
|
event.mode = 0100644;
|
|
event.uid = 1000;
|
|
event.gid = 1000;
|
|
return event;
|
|
}
|
|
|
|
/* Expected %M expansion computed independently with localtime_r. */
|
|
static void expected_mtime(time_t when, char out[32]) {
|
|
struct tm broken_down;
|
|
localtime_r(&when, &broken_down);
|
|
strftime(out, 32, "%Y/%m/%d-%H:%M:%S", &broken_down);
|
|
}
|
|
|
|
static void test_format_tokens() {
|
|
ChangeEvent event = sample_event();
|
|
Config* config = config_create();
|
|
char when[32];
|
|
expected_mtime(event.mtime_sec, when);
|
|
char* line = change_render_format("%f %n %l %b %M %%", config, &event);
|
|
EXPECT_NOT_NULL(line);
|
|
char expected[256];
|
|
snprintf(expected, sizeof(expected), "src/sub/file.txt sub/file.txt 12345 999 %s %%", when);
|
|
EXPECT_EQ_STR(line, expected);
|
|
free(line);
|
|
config_delete(config);
|
|
}
|
|
|
|
static void test_format_unknown_tokens_preserved() {
|
|
ChangeEvent event = sample_event();
|
|
Config* config = config_create();
|
|
char* line = change_render_format("x%q=%f%z", config, &event);
|
|
EXPECT_NOT_NULL(line);
|
|
EXPECT_EQ_STR(line, "x%q=src/sub/file.txt%z");
|
|
free(line);
|
|
config_delete(config);
|
|
}
|
|
|
|
static void test_format_directory_name_has_trailing_slash() {
|
|
ChangeEvent event = sample_event();
|
|
event.is_directory = true;
|
|
event.path = "src/sub";
|
|
event.name = "sub";
|
|
Config* config = config_create();
|
|
char* line = change_render_format("%n|%f", config, &event);
|
|
EXPECT_NOT_NULL(line);
|
|
EXPECT_EQ_STR(line, "sub/|src/sub");
|
|
free(line);
|
|
config_delete(config);
|
|
}
|
|
|
|
static void test_render_itemize_sent_file() {
|
|
ChangeEvent event = sample_event();
|
|
Config* config = config_create();
|
|
char* line = change_render_itemize(config, &event);
|
|
EXPECT_NOT_NULL(line);
|
|
EXPECT_EQ_STR(line, ">f+++++++++ sub/file.txt");
|
|
free(line);
|
|
config_delete(config);
|
|
}
|
|
|
|
static void test_render_itemize_directory() {
|
|
ChangeEvent event = sample_event();
|
|
event.is_directory = true;
|
|
event.path = "src/sub";
|
|
event.name = "sub";
|
|
Config* config = config_create();
|
|
char* line = change_render_itemize(config, &event);
|
|
EXPECT_NOT_NULL(line);
|
|
EXPECT_EQ_STR(line, "cd+++++++++ sub/");
|
|
free(line);
|
|
config_delete(config);
|
|
}
|
|
|
|
static void test_render_itemize_symlink() {
|
|
ChangeEvent event = sample_event();
|
|
event.is_symlink = true;
|
|
event.path = "src/link";
|
|
event.name = "link";
|
|
event.symlink_target = "a.txt";
|
|
Config* config = config_create();
|
|
char* line = change_render_itemize(config, &event);
|
|
EXPECT_NOT_NULL(line);
|
|
EXPECT_EQ_STR(line, "cL+++++++++ link -> a.txt");
|
|
free(line);
|
|
config_delete(config);
|
|
}
|
|
|
|
static void test_render_itemize_compares_destination() {
|
|
ChangeEvent event = sample_event();
|
|
Config* config = config_create();
|
|
config->preserve_perms = true;
|
|
config->preserve_owner = true;
|
|
config->preserve_group = true;
|
|
event.dest.known = true;
|
|
event.dest.existed = true;
|
|
event.dest.size = 1;
|
|
event.dest.mtime_sec = 1700000000;
|
|
event.dest.mtime_nsec = 0;
|
|
event.dest.mode = 0100600;
|
|
event.dest.uid = 1;
|
|
event.dest.gid = 2;
|
|
char* line = change_render_itemize(config, &event);
|
|
EXPECT_NOT_NULL(line);
|
|
/* size, perms, owner and group differ; time matches. */
|
|
EXPECT_EQ_STR(line, ">f.s.pog... sub/file.txt");
|
|
free(line);
|
|
config_delete(config);
|
|
}
|
|
|
|
static void test_render_itemize_up_to_date_is_empty() {
|
|
ChangeEvent event = sample_event();
|
|
Config* config = config_create();
|
|
event.decision = CHANGE_UP_TO_DATE;
|
|
char* line = change_render_itemize(config, &event);
|
|
EXPECT_NOT_NULL(line);
|
|
EXPECT_EQ_STR(line, "");
|
|
free(line);
|
|
config_delete(config);
|
|
}
|
|
|
|
static void test_render_list_line() {
|
|
ChangeEvent event;
|
|
memset(&event, 0, sizeof(event));
|
|
Config* config = config_create();
|
|
event.name = "sub/x.txt";
|
|
event.path = "sub/x.txt";
|
|
event.mode = 0100644;
|
|
event.size = 4096;
|
|
event.mtime_sec = 1700000000;
|
|
char* line = change_render_list_line(config, &event);
|
|
EXPECT_NOT_NULL(line);
|
|
EXPECT_TRUE(strncmp(line, "-rw-r--r--", 10) == 0);
|
|
EXPECT_TRUE(strstr(line, "4,096") != NULL);
|
|
EXPECT_TRUE(strstr(line, "sub/x.txt") != NULL);
|
|
free(line);
|
|
config_delete(config);
|
|
}
|
|
|
|
static void test_change_list_enabled() {
|
|
Config* config = config_create();
|
|
EXPECT_NOT_NULL(config);
|
|
EXPECT_FALSE(change_list_enabled(config));
|
|
config->itemize_changes = true;
|
|
EXPECT_TRUE(change_list_enabled(config));
|
|
config->itemize_changes = false;
|
|
config->out_format = str_dup("%f");
|
|
EXPECT_TRUE(change_list_enabled(config));
|
|
free(config->out_format);
|
|
config->out_format = NULL;
|
|
EXPECT_FALSE(change_list_enabled(config));
|
|
/* config_delete() closes log_file, so use a throwaway tmpfile. */
|
|
config->log_file = tmpfile();
|
|
EXPECT_NOT_NULL(config->log_file);
|
|
EXPECT_FALSE(change_list_enabled(config)); /* needs a format too */
|
|
config->log_file_format = str_dup("%n");
|
|
EXPECT_TRUE(change_list_enabled(config));
|
|
config_delete(config); /* closes config->log_file */
|
|
}
|
|
|
|
void test_change_list() {
|
|
test_format_tokens();
|
|
test_format_unknown_tokens_preserved();
|
|
test_format_directory_name_has_trailing_slash();
|
|
test_render_itemize_sent_file();
|
|
test_render_itemize_directory();
|
|
test_render_itemize_symlink();
|
|
test_render_itemize_compares_destination();
|
|
test_render_itemize_up_to_date_is_empty();
|
|
test_render_list_line();
|
|
test_change_list_enabled();
|
|
}
|