test(config): add receive-side golden oracle and sharpen fixture

Address low-severity review findings on the X-macro config refactor:

1. The golden test only hashed config_send_wire_block(), so a
   receive-side KIND that reads a different width/order could still
   round-trip symmetrically.  Add test_config_wire_golden_receive():
   capture the same hash-pinned 633-byte frame and feed it through
   config_receive(), asserting every field (config_wire_equal) plus the
   derived use_delta/use_xattrs bits and representative bounded kinds.
   Add test_config_wire_receive_bounds() for bounds the symmetric
   round-trip cannot reach: an out-of-range BOOL (hand-built frame),
   RAW_MAXALLOC zero, a malformed STR_MODULE, an over-cap
   INT_IDMAPCOUNT, and an out-of-range INT_IDENTITY chown_uid.

2. golden_config_populate() set long runs of booleans to all-1, so an
   adjacent swap within a run produced identical bytes.  Alternate the
   boolean values and make the fixture receiver-valid (chmod grammar
   "u=rwx,go=rx" is the same 11 bytes; delta_max_file_size inside the
   bound).  Re-pin the golden: len stays 633, hash is now
   9160991280011164139 (computed, not guessed).

3. Document in config.h and client_cli.c that the CLI option tables
   remain hand-maintained and are deliberately not generated from the
   wire-field X-macro (client-only fields, flag/alias/negation
   semantics).  No CLI-table rewrite.

PROTOCOL_VERSION stays "2.20.0"; src/shared/config.c is untouched and
the wire bytes are unchanged apart from the fixture's own new values.
This commit is contained in:
2026-09-13 10:56:34 +02:00
parent 4e918a1b69
commit 6ea966781f
3 changed files with 289 additions and 52 deletions
+9 -1
View File
@@ -608,7 +608,15 @@ typedef struct {
size_t offset; /* offsetof of the boolean target field in Config */
} NegatableOption;
/* Options that map directly onto a Config field with no side effects. */
/* Options that map directly onto a Config field with no side effects.
*
* NOTE: these CLI tables are intentionally NOT generated from the wire-field
* X-macro table in config.h. The two sets only overlap partially: the CLI
* surface also carries client-only fields that never cross the wire (rsh,
* outbuf, remote-option, batch paths, trust-sender, ...) and needs flag/alias/
* negation semantics that the wire table does not model. Keeping them
* hand-maintained is deliberate; the shared contract is enforced at the wire
* boundary by config.[ch] and the golden test. */
static const OptionEntry OPTION_TABLE[] = {
{"--dry-run", "-n", OPT_FLAG, offsetof(Config, dry_run)},
{"--remove-source-files", NULL, OPT_FLAG, offsetof(Config, remove_source_files)},
+6
View File
@@ -100,6 +100,12 @@ typedef enum SuperMode { SUPER_MODE_AUTO = 0, SUPER_MODE_ON = 1, SUPER_MODE_OFF
* (STR_MODULE), repeated count+array blocks (BLOCK_*), --copy-as presence
* (COPY_AS_*), and the derived --delta / use_xattrs bits (DERIVED_DELTA,
* BOOL_XATTR_DERIVE).
*
* SCOPE: this table covers ONLY the serialized wire frame. The client CLI
* option tables in client_cli.c (OPTION_TABLE / NEGATABLE_OPTIONS) are still
* hand-maintained and are deliberately NOT generated from this table: the CLI
* surface carries client-only fields and flag/alias/negation semantics that
* have no wire representation. Do not assume the two are folded together.
* =========================================================================== */
#define CONFIG_WIRE_HEADER_FIELDS(X) X(version, char*, str_dup(PROTOCOL_VERSION), STR)