feat(xce-test-cli-ux): generate and consolidate every command's help content

Wire flagspec_render_help() into cmd_help via new command_t fields
(flagspec/flagspec_count/usage_tail), so every flag-accepting command's
help composes description -> generated flag block -> example/related
notes, with hand-written per-flag lines removed as a duplicate source of
truth. Add missing purpose descriptions, examples, and confirmation-gate
related notes across ~20 commands; fix bug-1711's "matchs" grammar
(Req 11.2); confirm join's corrected maxmem default (bug-1557, Req 12.2)
and the --json flag (Req 5.4) now render correctly end-to-end.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019jaMf9QEwpugH2W9KAuc7f
This commit is contained in:
Rongsong Shen 2026-09-09 23:55:56 +00:00
parent 0d67f5cbde
commit 96746af3eb
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
5 changed files with 755 additions and 192 deletions

View file

@ -81,11 +81,46 @@
typedef int (*cmd_func_t)(int, char*[]);
/* command_t (xce-test-cli-ux task 3.5, Help Renderer, Requirements 8.1-8.7,
* 11.2, 12.2): `usage`/`flagspec`/`flagspec_count`/`usage_tail` together let
* cmd_help() assemble a command's detailed `help <cmd>` text in the order
* design.md mandates: description -> flags -> example -> related-command/
* context notes.
*
* usage For a command with a flagspec table (flagspec != NULL):
* its HEAD text only -- the usage-syntax line plus a
* 1-2 sentence purpose description (and any positional-
* argument documentation flagspec_render_help() can't
* express). For a command with NO flagspec table
* (flagspec == NULL): the ENTIRE detailed usage text, same
* as before this task -- printed as-is, nothing generated.
* flagspec NULL if this command takes no flags; otherwise the
* command's flagspec_t table (Flag Parsing Layer, tasks
* 1.1/1.2), rendered via flagspec_render_help() and
* printed immediately after `usage`.
* flagspec_count Element count of `flagspec`; meaningless when
* flagspec == NULL.
* usage_tail Printed after the generated flag block: the example
* invocation(s) and any related-command/scripting-state
* notes (Requirement 8.3/8.4). NULL when flagspec == NULL
* -- `usage` already carries everything for those
* commands, matching their pre-task-3.5 shape exactly.
*
* C aggregate-initializer rules zero-initialize any trailing members a
* `{ ... }` initializer omits, so every non-flag command_t entry (help,
* quit, echo, source, up, worker, scheduler, client, set, waldump,
* snapverify, snapdiff, configcompare, join_sched, getconf, expect-jobs,
* expect-job, wait-primary, ctrlstatus) needs NO changes at all: their
* initializers stay exactly 4 fields, and flagspec/flagspec_count/
* usage_tail all come out NULL/0 automatically. */
typedef struct {
char* name;
cmd_func_t func;
char* doc;
const char* usage;
char* name;
cmd_func_t func;
char* doc;
const char* usage;
const flagspec_t* flagspec;
size_t flagspec_count;
const char* usage_tail;
} command_t;
typedef struct {
@ -285,17 +320,18 @@ static const char USAGE_WALDUMP[] =
" found.\n"
"example: waldump /var/xce/ctrl-1/wal\n";
static const char USAGE_WALCOMPARE[] =
static const char USAGE_WALCOMPARE_HEAD[] =
"walcompare [--report] [--committed <N>] <memberA> <memberB> [more...]\n"
" --report also emit a full per-member (lsn,term,pcrc,status)\n"
" machine-checkable table, not just the verdict\n"
" --committed <N> use LSN N as the shared committed boundary instead\n"
" of deriving it as the minimum CRC-intact tip across\n"
" members\n"
" <memberX> 2-16 member paths (seg-*.wal file, wal/ dir, or\n"
" node/data dir)\n"
" Returns 0 if all members' committed WAL prefixes are identical,\n"
" nonzero on divergence or load error.\n"
" Diffs each member's committed WAL prefix (by LSN, term, and CRC) to\n"
" detect a durability divergence across replicas; --report\n"
" additionally emits a full per-member (lsn,term,pcrc,status)\n"
" machine-checkable table, not just the verdict. Returns 0 if all\n"
" members' committed WAL prefixes are identical, nonzero on\n"
" divergence or load error.\n";
static const char USAGE_WALCOMPARE_TAIL[] =
"example: walcompare --report /var/xce/ctrl-1/wal /var/xce/ctrl-2/wal\n";
static const char USAGE_SNAPVERIFY[] =
@ -384,117 +420,136 @@ static const char USAGE_SET[] =
" commands again (default)\n"
"example: set masters ctrl1=localhost:9991,ctrl2=localhost:9992\n";
static const char USAGE_JOIN_WORKER[] =
static const char USAGE_JOIN_WORKER_HEAD[] =
"join [-f <cpufactor>] [-c <maxcpus>] [-m <maxmem>] [-s <maxswap>]\n"
" [-t <maxtmp>] [-d <ndisks>]\n"
" -f <cpufactor> relative CPU speed factor (default 1.0)\n"
" -c <maxcpus> max CPU count (default 4)\n"
" -m <maxmem> max memory in bytes (default 64)\n"
" -s <maxswap> max swap in bytes (default 1024)\n"
" -t <maxtmp> max tmp space in bytes (default 2048)\n"
" -d <ndisks> number of local disks (default 1)\n"
" Joins the cluster as a worker (hosttype LINUX64, hostmodel Intel64).\n"
"example: join -c 8 -m 16384\n";
" Joins the cluster as a worker (hosttype LINUX64, hostmodel Intel64).\n";
static const char USAGE_JOBSTART[] =
static const char USAGE_JOIN_WORKER_TAIL[] = "example: join -c 8 -m 16384\n";
static const char USAGE_JOBSTART_HEAD[] =
"jstart -j <jobid> [-p <pid>]\n"
" -j <jobid> jobid to report a synthetic job-start fact for\n"
" (required)\n"
" -p <pid> pid to report (default: this xce_test process's own\n"
" pid)\n"
"example: jstart -j 12345 -p 4242\n";
" Reports a synthetic job-start fact to the controller, simulating\n"
" what a real worker would report after actually launching the job\n"
" -- this does NOT execute anything; it is a fake-worker test\n"
" primitive only.\n";
static const char USAGE_JOBFINISH[] =
static const char USAGE_JOBSTART_TAIL[] = "example: jstart -j 12345 -p 4242\n";
static const char USAGE_JOBFINISH_HEAD[] =
"jfinish -j <jobid> [-c <exitcode>]\n"
" -j <jobid> jobid to report a synthetic job-finish fact for\n"
" (required)\n"
" -c <exitcode> exit code to report (default 0)\n"
"example: jfinish -j 12345 -c 0\n";
" Reports a synthetic job-finish fact to the controller, simulating\n"
" what a real worker would report after the job actually exited --\n"
" this does NOT execute anything; it is a fake-worker test primitive\n"
" only.\n";
static const char USAGE_JOBFAIL[] =
static const char USAGE_JOBFINISH_TAIL[] = "example: jfinish -j 12345 -c 0\n";
static const char USAGE_JOBFAIL_HEAD[] =
"jfail -j <jobid> [-c <code>] [-s <stage>]\n"
" -j <jobid> jobid to report a synthetic job-fail fact for\n"
" (required)\n"
" -c <code> failure code to report (default 0)\n"
" -s <stage> exec-stage code to report (default\n"
" JOB_EXEC_STAGE_POST_EXEC)\n"
"example: jfail -j 12345 -c 1\n";
" Reports a synthetic job-fail fact to the controller, simulating\n"
" what a real worker would report after the job actually failed --\n"
" this does NOT execute anything; it is a fake-worker test primitive\n"
" only.\n";
static const char USAGE_RESUPDATE[] =
static const char USAGE_JOBFAIL_TAIL[] = "example: jfail -j 12345 -c 1\n";
static const char USAGE_RESUPDATE_HEAD[] =
"resupdate -n <resname> -v <value> [-t <numeric|string>]\n"
" [-n <resname> -v <value> [-t <numeric|string>] ...]\n"
" -n <resname> resource name to update; any name the cluster has\n"
" registered is accepted (e.g. slots, mem, cpuf,\n"
" maxswap, maxtmp, maxcpus, ndisks, type, model,\n"
" dynres1, dynres2, r1m, r15s, r15m, pg, ls, it, swp,\n"
" io, tmp); may repeat for multiple resources\n"
" -v <value> value for the immediately preceding -n (numeric for\n"
" most resources, string for type/model)\n"
" -t <numeric|string> optional: force the wire type for the\n"
" immediately preceding -n/-v pair. Only meaningful for\n"
" a resource name NOT already known to this tool -- a\n"
" known name (e.g. mem, type) always keeps its own\n"
" fixed type, and -t is ignored for it. An unrecognized\n"
" name defaults to numeric when -t is omitted.\n"
" Order matters: pair each -n with its -v (and optional -t) right\n"
" after it.\n"
" Injects or overrides a resource's reported value on this session's\n"
" simulated worker, for testing scheduler/resource-accounting\n"
" behavior without a real xcecd. Any resource name the cluster has\n"
" registered is accepted (e.g. slots, mem, cpuf, maxswap, maxtmp,\n"
" maxcpus, ndisks, type, model, dynres1, dynres2, r1m, r15s, r15m,\n"
" pg, ls, it, swp, io, tmp); -n may repeat to update multiple\n"
" resources in one call. -t forces the wire type for the\n"
" immediately preceding -n/-v pair; it is only meaningful for a\n"
" resource name NOT already known to this tool -- a known name\n"
" (e.g. mem, type) always keeps its own fixed type, and -t is\n"
" ignored for it, while an unrecognized name defaults to numeric\n"
" when -t is omitted. Order matters: pair each -n with its -v (and\n"
" optional -t) right after it.\n";
static const char USAGE_RESUPDATE_TAIL[] =
"example: resupdate -n slots -v 4 -n mem -v 8192\n"
"example: resupdate -n r15m -v 1.5\n"
"example: resupdate -n mylabel -v foo -t string\n";
static const char USAGE_HELLO[] =
static const char USAGE_HELLO_HEAD[] =
"hello [-c <count>] [-m <message>]\n"
" -c <count> number of times to send the hello (default 1)\n"
" -m <message> greeting text to send (default \"Greeting from xce\n"
" client\")\n"
"example: hello -c 3 -m \"ping\"\n";
" Sends a greeting RPC to the controller, optionally repeated, as a\n"
" basic connectivity smoke test for the active context's connection.\n";
static const char USAGE_RESLIST[] =
static const char USAGE_HELLO_TAIL[] = "example: hello -c 3 -m \"ping\"\n";
static const char USAGE_RESLIST_HEAD[] =
"reslist [--json]\n"
" --json emit each resource as a JSON object (same fields as the\n"
" table) instead of the human-readable table\n"
" Prints all cluster-wide resources (name, flags, interval,\n"
" description).\n";
" description). Use --json to emit the same fields as one JSON\n"
" object per resource instead of the human-readable table.\n";
static const char USAGE_WORKERLIST[] =
static const char USAGE_RESLIST_TAIL[] = "example: reslist --json\n";
static const char USAGE_WORKERLIST_HEAD[] =
"workerlist [--json]\n"
" --json emit each worker as a JSON object (same fields as the\n"
" table) instead of the human-readable table\n"
" Prints all joined workers (host, status, host type/model, cpu\n"
" factor, max cpu/mem/swap/tmp, ndisks).\n";
" factor, max cpu/mem/swap/tmp, ndisks). Use --json to emit the same\n"
" fields as one JSON object per worker instead of the human-readable\n"
" table.\n";
static const char USAGE_QCTRL[] =
static const char USAGE_WORKERLIST_TAIL[] = "example: workerlist --json\n";
static const char USAGE_QCTRL_HEAD[] =
"qctrl -q <queue> [-a] [-i] [-o] [-c]\n"
" -q <queue> queue name to control (required)\n"
" -a set queue ACTIVE (accepts dispatch)\n"
" -i set queue INACTIVE (rejects dispatch)\n"
" -o set queue OPEN (accepts new submissions)\n"
" -c set queue CLOSE (rejects new submissions)\n"
"example: qctrl -q normal -i\n";
" Controls a queue's dispatch/submission state: -a sets it ACTIVE\n"
" (accepts dispatch), -i sets it INACTIVE (rejects dispatch), -o\n"
" sets it OPEN (accepts new submissions), and -c sets it CLOSE\n"
" (rejects new submissions). Exactly one of -a/-i/-o/-c is required.\n";
static const char USAGE_HOSTCTRL[] =
static const char USAGE_QCTRL_TAIL[] =
"example: qctrl -q normal -i\n"
" This is a destructive action only for -i/--deactivate and\n"
" -c/--close, which prompt for confirmation by default (-a/--activate\n"
" and -o/--open never prompt); use -y/--yes to skip the prompt for\n"
" this invocation, or `set -y` to skip it for the rest of the\n"
" session (see `help set`).\n";
static const char USAGE_HOSTCTRL_HEAD[] =
"hostctrl -h <hostname> (-o|-c)\n"
" -h <hostname> host to administratively control (required)\n"
" -o open the host (allow scheduling)\n"
" -c close the host (admin-close; shows CLOSED in\n"
" workerlist)\n"
"example: hostctrl -h node01 -c\n";
" Administratively controls a host's scheduling eligibility: -o\n"
" opens it (allow scheduling), -c closes it (admin-close; shows\n"
" CLOSED in workerlist). Exactly one of -o/-c is required.\n";
static const char USAGE_UPGRADECONFIG[] =
"upgradeconfig <cluster.bin>\n"
static const char USAGE_HOSTCTRL_TAIL[] =
"example: hostctrl -h node01 -c\n"
" This is a destructive action only for -c/--close, which prompts\n"
" for confirmation by default (-o/--open never prompts); use\n"
" -y/--yes to skip the prompt for this invocation, or `set -y` to\n"
" skip it for the rest of the session (see `help set`).\n";
static const char USAGE_UPGRADECONFIG_HEAD[] =
"upgradeconfig [-y|--yes] <cluster.bin>\n"
" <cluster.bin> path to a capnp-encoded ClusterConfig blob (same\n"
" format `xceadmin config build` produces and\n"
" deploy-ha.sh installs)\n"
" Computes its SHA-256 and submits it as a configuration upgrade via\n"
" OP_upgradeConfig.\n"
"example: upgradeconfig /var/xce/cluster.bin\n";
" OP_upgradeConfig.\n";
static const char USAGE_JOBLIST[] =
static const char USAGE_UPGRADECONFIG_TAIL[] =
"example: upgradeconfig /var/xce/cluster.bin\n"
" This is a destructive action and prompts for confirmation by\n"
" default; use -y/--yes to skip the prompt for this invocation, or\n"
" `set -y` to skip it for the rest of the session (see `help set`).\n";
static const char USAGE_JOBLIST_HEAD[] =
"joblist [--json]\n"
" --json emit each job as a JSON object (same fields as the table)\n"
" instead of the human-readable table\n"
" Prints all jobs in the cluster (JobId, user, queue, state, times,\n"
" mintasks/maxtasks, command) and captures the count into $jobcount.\n";
" mintasks/maxtasks, command) and captures the count into $jobcount.\n"
" Use --json to emit the same fields as one JSON object per job\n"
" instead of the human-readable table.\n";
static const char USAGE_JOBLIST_TAIL[] = "example: joblist --json\n";
static const char USAGE_EXPECT_JOBS[] =
"expect-jobs <N>\n"
@ -530,74 +585,80 @@ static const char USAGE_CTRLSTATUS[] =
" committedLsn, appliedLsn, fenced) from a single any-node status\n"
" read.\n";
static const char USAGE_SHUTDOWN[] =
"shutdown\n"
" (no arguments)\n"
static const char USAGE_SHUTDOWN_HEAD[] =
"shutdown [-y|--yes]\n"
" Shuts down the xce controller and, on success, leaves the client\n"
" context (same as running `..`).\n";
static const char USAGE_JOBRUN[] =
static const char USAGE_SHUTDOWN_TAIL[] =
"example: shutdown\n"
" This is a destructive action and prompts for confirmation by\n"
" default; use -y/--yes to skip the prompt for this invocation, or\n"
" `set -y` to skip it for the rest of the session (see `help set`).\n";
static const char USAGE_JOBRUN_HEAD[] =
"jobrun -j <jobid> [-m <host>[:<ntasks>]] [-m ...]\n"
" -j <jobid> jobid to dispatch (required, must be > 0)\n"
" -m <host>[:<n>] host to run tasks on, optional :ntasks count\n"
" (default 1); may be repeated up to 5 times\n"
" Dispatches an already-submitted job (jobid must be > 0) to one or\n"
" more hosts, allocating the given task counts on each.\n";
static const char USAGE_JOBRUN_TAIL[] =
"example: jobrun -j 12345 -m node01:2 -m node02\n";
static const char USAGE_JOBSUB[] =
static const char USAGE_JOBSUB_HEAD[] =
"jobsub -u <user> -q <queue> -c <cmdline> [-R <resreq>]\n"
" [-n <min>[,<max>]] [-x <exdata>] [-a <arraysize>]\n"
" -u <user> submitting user (required)\n"
" -q <queue> target queue (required)\n"
" -c <cmdline> command line to run (required)\n"
" -R <resreq> resource requirement expression (default\n"
" \"type == any\")\n"
" -n <min>[,<max>] task count, or a min,max range (default 1)\n"
" -x <exdata> raw exdata JSON string (overrides the built-in\n"
" PATH env)\n"
" -a <arraysize> job array size (default 1)\n"
" Submits a new job to the cluster under the given user and queue;\n"
" -x's exdata JSON, when given, overrides the command's built-in\n"
" PATH env instead of the default.\n";
static const char USAGE_JOBSUB_TAIL[] =
" On success, captures the submitted JobId into the $jobid\n"
" auto-variable.\n"
"example: jobsub -u alice -q normal -c \"sleep 10\" -n 1,4\n";
static const char USAGE_RESVAL[] =
static const char USAGE_RESVAL_HEAD[] =
"resval -n <resname> [-m <host>]\n"
" -n <resname> resource name to query (required)\n"
" -m <host> host to query (required unless the resource is a\n"
" shared/cluster-wide resource, not per-host)\n"
"example: resval -n slots -m myhost\n";
" Queries the current value of one resource, cluster-wide or on a\n"
" specific host.\n";
static const char USAGE_JOBKILL[] =
static const char USAGE_RESVAL_TAIL[] = "example: resval -n slots -m myhost\n";
static const char USAGE_JOBKILL_HEAD[] =
"jobkill -j <jobid> [-s <signo>]\n"
" -j <jobid> jobid to signal (required)\n"
" -s <signo> signal number to send (default SIGTERM)\n"
"example: jobkill -j 12345 -s 9\n";
" Sends a signal to a running job, terminating it by default.\n";
static const char USAGE_JOBCKPT[] =
static const char USAGE_JOBKILL_TAIL[] =
"example: jobkill -j 12345 -s 9\n"
" This is a destructive action and prompts for confirmation by\n"
" default; use -y/--yes to skip the prompt for this invocation, or\n"
" `set -y` to skip it for the rest of the session (see `help set`).\n";
static const char USAGE_JOBCKPT_HEAD[] =
"jobckpt -j <jobid> [-k] [-m <method>]\n"
" -j <jobid> jobid to checkpoint (required)\n"
" -k kill the job immediately after checkpointing\n"
" -m <method> checkpoint method name (default \"default\")\n"
" Requests a checkpoint of a running job; -k additionally kills the\n"
" job immediately after checkpointing.\n";
static const char USAGE_JOBCKPT_TAIL[] =
"example: jobckpt -j 12345 -m default\n";
static const char USAGE_JOBINFO[] =
static const char USAGE_JOBINFO_HEAD[] =
"jobinfo -j <jobid> [-j <jobid> ...]\n"
" -j <jobid> jobid to show info for; may be repeated to show\n"
" several jobs in one call\n"
"example: jobinfo -j 12345\n";
" Prints detailed information (state, pend reasons, submit/exec\n"
" metadata) for one or more jobs; -j may be repeated to show\n"
" several jobs in one call.\n";
static const char USAGE_TASKRUN[] =
static const char USAGE_JOBINFO_TAIL[] = "example: jobinfo -j 12345\n";
static const char USAGE_TASKRUN_HEAD[] =
"taskrun [-j <jobid>] [-m <host>[:<n>]]... [-u <user>] [-c <cmdline>]\n"
" [-x <exdata>] [-d <dir>] [-t <launch_type>]\n"
" -j <jobid> jobid this task run belongs to\n"
" -m <host>[:<n>] host to run on, optional :ntasks count\n"
" (default 1); may be repeated up to 5 times\n"
" -u <user> exec user, applied to every host\n"
" -c <cmdline> command line, applied to every host\n"
" -x <exdata> exdata string, applied to every host\n"
" -d <dir> exec home/cwd directory, applied to every host\n"
" -t <launch_type> launch type (default \"mpi\" if the total task\n"
" count across all -m hosts is > 1, else a\n"
" single-task default)\n"
" Reports one or more task launches for an existing job to the\n"
" controller -- the same call a real worker makes after actually\n"
" starting a task -- letting a test simulate multi-host task\n"
" placement without a live worker; -u/-c/-x/-d apply uniformly to\n"
" every -m host.\n";
static const char USAGE_TASKRUN_TAIL[] =
"example: taskrun -j 12345 -m node01:2 -c \"hostname\" -u alice\n";
static const char USAGE_JOIN_SCHED[] =
@ -611,13 +672,81 @@ static const char USAGE_GETCONF[] =
" Prints the full cluster configuration (name, resources, queues,\n"
" workers, user/host groups, configs) known to the scheduler.\n";
static const char USAGE_SETPEND[] =
static const char USAGE_SETPEND_HEAD[] =
"setpend -j <jobid> -p <reason> [-p <reason> ...]\n"
" -j <jobid> jobid to set the pend reason for (required)\n"
" -p <reason> pend reason spec, may repeat: `<code>` sets the main\n"
" reason; `<host>:<code>` adds a host-based reason;\n"
" `<res>:<host>:<code>` adds a resource-based reason\n"
"example: setpend -j 12345 -p 5\n";
" Sets a job's pend reason(s), simulating what the scheduler reports\n"
" for a job that cannot yet be dispatched. -p may repeat: `<code>`\n"
" sets the main reason; `<host>:<code>` adds a host-based reason;\n"
" `<res>:<host>:<code>` adds a resource-based reason.\n";
static const char USAGE_SETPEND_TAIL[] = "example: setpend -j 12345 -p 5\n";
/* xce-test-cli-ux task 3.5 (Help Renderer): forward-declare every
* flag-accepting command's flagspec_t table -- each one is actually
* DEFINED later in this file, immediately next to its cmd_* handler, as
* before this task -- with its current flag count as an explicit array
* bound, and give that count a name via the same NFLAGS macro each
* handler already uses to size its own getopt_long() option table
* (`struct option long_opts[X_NFLAGS + 1]`). This lets top_commands[]/
* worker_commands[]/client_commands[]/scheduler_commands[] below wire
* each entry's `.flagspec`/`.flagspec_count` directly to its table and
* macro, without requiring the table's full definition to appear first --
* a file-scope initializer only needs a constant expression, which a
* literal NFLAGS macro is (an array's true element count from a later
* sizeof() is not known this early).
*
* Safety net, not a hand-counted magic number left free to drift: C
* requires two declarations of the same array to agree on size once
* either one specifies a constant size, so if a future edit changes a
* table's entry count below without updating its bound here, the build
* fails immediately ("conflicting types for ...") instead of silently
* rendering a wrong flag count in generated help text. Each table's own
* `#define X_NFLAGS (sizeof(x_flags) / sizeof(x_flags[0]))` -- used
* exactly this way for tasks 1.1/3.1 -- is superseded by (and removed at)
* that same later definition site; this is the one and only place each
* NFLAGS macro is now defined. */
#define WALCOMPARE_NFLAGS 2
static const flagspec_t walcompare_flags[WALCOMPARE_NFLAGS];
#define HELLO_NFLAGS 2
static const flagspec_t hello_flags[HELLO_NFLAGS];
#define SHUTDOWN_NFLAGS 1
static const flagspec_t shutdown_flags[SHUTDOWN_NFLAGS];
#define RESLIST_NFLAGS 1
static const flagspec_t reslist_flags[RESLIST_NFLAGS];
#define WORKERLIST_NFLAGS 1
static const flagspec_t workerlist_flags[WORKERLIST_NFLAGS];
#define JOBLIST_NFLAGS 1
static const flagspec_t joblist_flags[JOBLIST_NFLAGS];
#define JOBINFO_NFLAGS 1
static const flagspec_t jobinfo_flags[JOBINFO_NFLAGS];
#define JOBRUN_NFLAGS 2
static const flagspec_t jobrun_flags[JOBRUN_NFLAGS];
#define JOBSUB_NFLAGS 7
static const flagspec_t jobsub_flags[JOBSUB_NFLAGS];
#define RESVAL_NFLAGS 2
static const flagspec_t resval_flags[RESVAL_NFLAGS];
#define JOBKILL_NFLAGS 3
static const flagspec_t jobkill_flags[JOBKILL_NFLAGS];
#define JOBCKPT_NFLAGS 3
static const flagspec_t jobckpt_flags[JOBCKPT_NFLAGS];
#define QCTRL_NFLAGS 6
static const flagspec_t qctrl_flags[QCTRL_NFLAGS];
#define HOSTCTRL_NFLAGS 4
static const flagspec_t hostctrl_flags[HOSTCTRL_NFLAGS];
#define UPGRADECONFIG_NFLAGS 1
static const flagspec_t upgradeconfig_flags[UPGRADECONFIG_NFLAGS];
#define RUNTASKS_NFLAGS 7
static const flagspec_t runtasks_flags[RUNTASKS_NFLAGS];
#define SETPEND_NFLAGS 2
static const flagspec_t setpend_flags[SETPEND_NFLAGS];
#define JOBSTART_NFLAGS 2
static const flagspec_t jobstart_flags[JOBSTART_NFLAGS];
#define JOBFINISH_NFLAGS 2
static const flagspec_t jobfinish_flags[JOBFINISH_NFLAGS];
#define JOBFAIL_NFLAGS 3
static const flagspec_t jobfail_flags[JOBFAIL_NFLAGS];
#define RESUPDATE_NFLAGS 3
static const flagspec_t resupdate_flags[RESUPDATE_NFLAGS];
static command_t top_commands[] = {
{ "help", cmd_help, "Display this text", USAGE_HELP },
@ -628,7 +757,8 @@ static command_t top_commands[] = {
{ "echo", cmd_echo, "Print a message", USAGE_ECHO },
{ "waldump", cmd_waldump, "Inspect WAL segments", USAGE_WALDUMP },
{ "walcompare", cmd_walcompare, "Diff member committed WAL prefixes",
USAGE_WALCOMPARE },
USAGE_WALCOMPARE_HEAD, walcompare_flags, WALCOMPARE_NFLAGS,
USAGE_WALCOMPARE_TAIL },
{ "snapverify", cmd_snapverify, "Verify checkpoint file structure",
USAGE_SNAPVERIFY },
{ "snapdiff", cmd_snapdiff, "Byte-diff two checkpoint files",
@ -649,11 +779,20 @@ static command_t worker_commands[] = {
{ "..", cmd_up, "Goto upper context", USAGE_UP },
{ "help", cmd_help, "Display this text", USAGE_HELP },
{ "quit", cmd_quit, "Quit xce test", USAGE_QUIT },
{ "join", cmd_join_worker, "Join the cluster", USAGE_JOIN_WORKER },
{ "jstart", cmd_jobstart, "tell controller jobstart", USAGE_JOBSTART },
{ "jfinish", cmd_jobfinish, "tell controller jobfinish", USAGE_JOBFINISH },
{ "jfail", cmd_jobfail, "tell controller jobfail", USAGE_JOBFAIL },
{ "resupdate", cmd_resupdate, "update res vals", USAGE_RESUPDATE },
{ "join", cmd_join_worker, "Join the cluster",
USAGE_JOIN_WORKER_HEAD, join_flags, JOIN_FLAGS_COUNT,
USAGE_JOIN_WORKER_TAIL },
{ "jstart", cmd_jobstart, "tell controller jobstart",
USAGE_JOBSTART_HEAD, jobstart_flags, JOBSTART_NFLAGS,
USAGE_JOBSTART_TAIL },
{ "jfinish", cmd_jobfinish, "tell controller jobfinish",
USAGE_JOBFINISH_HEAD, jobfinish_flags, JOBFINISH_NFLAGS,
USAGE_JOBFINISH_TAIL },
{ "jfail", cmd_jobfail, "tell controller jobfail",
USAGE_JOBFAIL_HEAD, jobfail_flags, JOBFAIL_NFLAGS, USAGE_JOBFAIL_TAIL },
{ "resupdate", cmd_resupdate, "update res vals",
USAGE_RESUPDATE_HEAD, resupdate_flags, RESUPDATE_NFLAGS,
USAGE_RESUPDATE_TAIL },
{ "echo", cmd_echo, "Print a message", USAGE_ECHO },
{ "source", cmd_source, "Run commands from a file", USAGE_SOURCE },
{ (char*)NULL, (cmd_func_t)NULL, (char*)NULL, (const char*)NULL }
@ -666,14 +805,24 @@ static command_ctx_t workerctx = { .parent = &topctx,
static command_t client_commands[] = {
{ "..", cmd_up, "Goto upper context", USAGE_UP },
{ "hello", cmd_hello, "Say hello to xce controller", USAGE_HELLO },
{ "reslist", cmd_reslist, "Display cluster resources", USAGE_RESLIST },
{ "workerlist", cmd_workerlist, "list workers", USAGE_WORKERLIST },
{ "qctrl", cmd_qctrl, "queue control", USAGE_QCTRL },
{ "hostctrl", cmd_hostctrl, "host control", USAGE_HOSTCTRL },
{ "hello", cmd_hello, "Say hello to xce controller",
USAGE_HELLO_HEAD, hello_flags, HELLO_NFLAGS, USAGE_HELLO_TAIL },
{ "reslist", cmd_reslist, "Display cluster resources",
USAGE_RESLIST_HEAD, reslist_flags, RESLIST_NFLAGS, USAGE_RESLIST_TAIL },
{ "workerlist", cmd_workerlist, "list workers",
USAGE_WORKERLIST_HEAD, workerlist_flags, WORKERLIST_NFLAGS,
USAGE_WORKERLIST_TAIL },
{ "qctrl", cmd_qctrl, "queue control",
USAGE_QCTRL_HEAD, qctrl_flags, QCTRL_NFLAGS, USAGE_QCTRL_TAIL },
{ "hostctrl", cmd_hostctrl, "host control",
USAGE_HOSTCTRL_HEAD, hostctrl_flags, HOSTCTRL_NFLAGS,
USAGE_HOSTCTRL_TAIL },
{ "upgradeconfig", cmd_upgradeconfig,
"submit a config upgrade (cluster.bin file)", USAGE_UPGRADECONFIG },
{ "joblist", cmd_joblist, "list jobs", USAGE_JOBLIST },
"submit a config upgrade (cluster.bin file)",
USAGE_UPGRADECONFIG_HEAD, upgradeconfig_flags, UPGRADECONFIG_NFLAGS,
USAGE_UPGRADECONFIG_TAIL },
{ "joblist", cmd_joblist, "list jobs",
USAGE_JOBLIST_HEAD, joblist_flags, JOBLIST_NFLAGS, USAGE_JOBLIST_TAIL },
{ "expect-jobs", cmd_expect_jobs, "assert current job count", USAGE_EXPECT_JOBS },
{ "expect-job", cmd_expect_job, "assert a JobId is present", USAGE_EXPECT_JOB },
{ "wait-primary", cmd_wait_primary,
@ -681,21 +830,32 @@ static command_t client_commands[] = {
{ "ctrlstatus", cmd_ctrlstatus, "show controller/cluster status", USAGE_CTRLSTATUS },
{ "waldump", cmd_waldump, "Inspect WAL segments", USAGE_WALDUMP },
{ "walcompare", cmd_walcompare, "Diff member committed WAL prefixes",
USAGE_WALCOMPARE },
USAGE_WALCOMPARE_HEAD, walcompare_flags, WALCOMPARE_NFLAGS,
USAGE_WALCOMPARE_TAIL },
{ "snapverify", cmd_snapverify, "Verify checkpoint file structure",
USAGE_SNAPVERIFY },
{ "snapdiff", cmd_snapdiff, "Byte-diff two checkpoint files",
USAGE_SNAPDIFF },
{ "configcompare", cmd_configcompare, "Compare active config hash across members",
USAGE_CONFIGCOMPARE },
{ "shutdown", cmd_shutdown, "shutdown xce controller", USAGE_SHUTDOWN },
{ "jobrun", cmd_jobrun, "run a job", USAGE_JOBRUN },
{ "jobsub", cmd_jobsub, "submit a job", USAGE_JOBSUB },
{ "jobkill", cmd_jobkill, "signal a job", USAGE_JOBKILL },
{ "jobckpt", cmd_jobckpt, "checkpoint a job", USAGE_JOBCKPT },
{ "jobinfo", cmd_jobinfo, "job information", USAGE_JOBINFO },
{ "resval", cmd_resval, "get value of res", USAGE_RESVAL },
{ "taskrun", cmd_runtasks, "run tasks", USAGE_TASKRUN },
{ "shutdown", cmd_shutdown, "shutdown xce controller",
USAGE_SHUTDOWN_HEAD, shutdown_flags, SHUTDOWN_NFLAGS,
USAGE_SHUTDOWN_TAIL },
{ "jobrun", cmd_jobrun, "run a job",
USAGE_JOBRUN_HEAD, jobrun_flags, JOBRUN_NFLAGS, USAGE_JOBRUN_TAIL },
{ "jobsub", cmd_jobsub, "submit a job",
USAGE_JOBSUB_HEAD, jobsub_flags, JOBSUB_NFLAGS, USAGE_JOBSUB_TAIL },
{ "jobkill", cmd_jobkill, "signal a job",
USAGE_JOBKILL_HEAD, jobkill_flags, JOBKILL_NFLAGS, USAGE_JOBKILL_TAIL },
{ "jobckpt", cmd_jobckpt, "checkpoint a job",
USAGE_JOBCKPT_HEAD, jobckpt_flags, JOBCKPT_NFLAGS, USAGE_JOBCKPT_TAIL },
{ "jobinfo", cmd_jobinfo, "job information",
USAGE_JOBINFO_HEAD, jobinfo_flags, JOBINFO_NFLAGS, USAGE_JOBINFO_TAIL },
{ "resval", cmd_resval, "get value of res",
USAGE_RESVAL_HEAD, resval_flags, RESVAL_NFLAGS, USAGE_RESVAL_TAIL },
{ "taskrun", cmd_runtasks, "run tasks",
USAGE_TASKRUN_HEAD, runtasks_flags, RUNTASKS_NFLAGS,
USAGE_TASKRUN_TAIL },
{ "help", cmd_help, "Display this text", USAGE_HELP },
{ "echo", cmd_echo, "Print a message", USAGE_ECHO },
{ "source", cmd_source, "Run commands from a file", USAGE_SOURCE },
@ -714,7 +874,8 @@ static command_t scheduler_commands[] = {
{ "quit", cmd_quit, "Quit xce test", USAGE_QUIT },
{ "join", cmd_join_sched, "Join the cluster", USAGE_JOIN_SCHED },
{ "getconf", cmd_getconf, "Get conf of cluster", USAGE_GETCONF },
{ "setpend", cmd_setpend, "set job pend reason", USAGE_SETPEND },
{ "setpend", cmd_setpend, "set job pend reason",
USAGE_SETPEND_HEAD, setpend_flags, SETPEND_NFLAGS, USAGE_SETPEND_TAIL },
{ "echo", cmd_echo, "Print a message", USAGE_ECHO },
{ "source", cmd_source, "Run commands from a file", USAGE_SOURCE },
{ (char*)NULL, (cmd_func_t)NULL, (char*)NULL, (const char*)NULL }
@ -1312,6 +1473,9 @@ int cmd_help(int argc, char* argv[])
if (argc > 1) {
char* cmd = argv[1];
/* Requirement 8.7: scan ONLY active_ctx->commands -- a name valid in
* a different context must never be found here. Do not add a
* fallback lookup into another context's table. */
for (i = 0; active_ctx->commands[i].name != NULL; i++) {
if (strcmp(argv[1], active_ctx->commands[i].name) == 0) {
/* `help <cmd>` prints the detailed per-command usage (flags,
@ -1319,9 +1483,33 @@ int cmd_help(int argc, char* argv[])
* `doc` the bare `help` listing below still uses unchanged.
* Every table entry is expected to carry a non-NULL `usage`
* now; the `doc` fallback stays only as a defensive guard
* against a future entry that forgets to set it. */
* against a future entry that forgets to set it.
*
* xce-test-cli-ux task 3.5 (Help Renderer, design.md's
* "description -> flags -> example -> related" assembly
* order): when this entry carries a flagspec table, `usage`
* holds only the HEAD (syntax + description) -- print the
* generated flag block right after it, then `usage_tail`
* (example + related-command/context notes). A command with
* no flagspec table is unchanged from before this task:
* `usage` alone already carries everything. */
if (active_ctx->commands[i].usage != NULL) {
fputs(active_ctx->commands[i].usage, stdout);
if (active_ctx->commands[i].flagspec != NULL) {
char* flags_block = flagspec_render_help(
active_ctx->commands[i].flagspec,
active_ctx->commands[i].flagspec_count);
if (flags_block != NULL) {
fputs(flags_block, stdout);
free(flags_block);
}
if (active_ctx->commands[i].usage_tail != NULL) {
fputs(active_ctx->commands[i].usage_tail, stdout);
}
}
} else {
printf("%s\t\t%s.\n", active_ctx->commands[i].name,
active_ctx->commands[i].doc);
@ -1330,7 +1518,10 @@ int cmd_help(int argc, char* argv[])
}
}
printf("No commands matchs `%s'. Possibilities are:\n", cmd);
/* bug-1711 / Requirement 11.2: grammar fix only ("match", not
* "matchs") -- does NOT add the cross-context lookup Requirement
* 8.7 explicitly excludes. */
printf("No commands match `%s'. Possibilities are:\n", cmd);
rc = -1;
}
@ -1414,8 +1605,6 @@ static const flagspec_t walcompare_flags[] = {
{ 0, "committed", "committed", required_argument, 0,
"derive the shared boundary from members' CRC-intact tips" },
};
#define WALCOMPARE_NFLAGS \
(sizeof(walcompare_flags) / sizeof(walcompare_flags[0]))
int cmd_walcompare(int argc, char* argv[])
{
@ -1794,7 +1983,6 @@ static const flagspec_t hello_flags[] = {
{ 'm', "message", "message", required_argument, 0,
"\"Greeting from xce client\"" },
};
#define HELLO_NFLAGS (sizeof(hello_flags) / sizeof(hello_flags[0]))
/* xce-test-cli-ux task 3.2: per-call-site heap block wrapping the blocking
* xcecli_hello() call for xcetest_run_interruptible() (interruptcall.h,
@ -1922,7 +2110,6 @@ static int shutdown_thunk(void* ctxp)
static const flagspec_t shutdown_flags[] = {
{ 'y', "yes", "yes", no_argument, 0, NULL },
};
#define SHUTDOWN_NFLAGS (sizeof(shutdown_flags) / sizeof(shutdown_flags[0]))
int cmd_shutdown(int argc, char* argv[])
{
@ -2022,7 +2209,6 @@ enum { RESLIST_OPT_JSON = 0 };
static const flagspec_t reslist_flags[] = {
{ 0, "json", "json-output", no_argument, 0, "human-readable table" },
};
#define RESLIST_NFLAGS (sizeof(reslist_flags) / sizeof(reslist_flags[0]))
int cmd_reslist(int argc, char* argv[])
{
@ -2166,8 +2352,6 @@ enum { WORKERLIST_OPT_JSON = 0 };
static const flagspec_t workerlist_flags[] = {
{ 0, "json", "json-output", no_argument, 0, "human-readable table" },
};
#define WORKERLIST_NFLAGS \
(sizeof(workerlist_flags) / sizeof(workerlist_flags[0]))
int cmd_workerlist(int argc, char* argv[])
{
@ -2458,7 +2642,6 @@ enum { JOBLIST_OPT_JSON = 0 };
static const flagspec_t joblist_flags[] = {
{ 0, "json", "json-output", no_argument, 0, "human-readable table" },
};
#define JOBLIST_NFLAGS (sizeof(joblist_flags) / sizeof(joblist_flags[0]))
int cmd_joblist(int argc, char* argv[])
{
@ -3012,7 +3195,6 @@ static void print_jobinfo(xce_job_t* job)
static const flagspec_t jobinfo_flags[] = {
{ 'j', "jobid", "jobid", required_argument, 1, NULL },
};
#define JOBINFO_NFLAGS (sizeof(jobinfo_flags) / sizeof(jobinfo_flags[0]))
typedef struct {
xcecli_ctx_t conn;
@ -3150,7 +3332,6 @@ static const flagspec_t jobrun_flags[] = {
{ 'm', "host", "host", required_argument, 0, "1 task; repeatable up to 5" },
{ 'j', "jobid", "jobid", required_argument, 1, NULL },
};
#define JOBRUN_NFLAGS (sizeof(jobrun_flags) / sizeof(jobrun_flags[0]))
typedef struct {
xcecli_ctx_t conn;
@ -3315,7 +3496,6 @@ static const flagspec_t jobsub_flags[] = {
{ 'x', "exdata", "exdata", required_argument, 0, NULL },
{ 'a', "array-size", "arraysize", required_argument, 0, "1" },
};
#define JOBSUB_NFLAGS (sizeof(jobsub_flags) / sizeof(jobsub_flags[0]))
typedef struct {
xcecli_ctx_t conn;
@ -3668,7 +3848,6 @@ static const flagspec_t resval_flags[] = {
{ 'm', "host", "host", required_argument, 0,
"required unless the resource is cluster-wide" },
};
#define RESVAL_NFLAGS (sizeof(resval_flags) / sizeof(resval_flags[0]))
typedef struct {
xcecli_ctx_t conn;
@ -3816,7 +3995,6 @@ static const flagspec_t jobkill_flags[] = {
* commands (Requirement 6.2 cross-command consistency). */
{ 'y', "yes", "yes", no_argument, 0, NULL },
};
#define JOBKILL_NFLAGS (sizeof(jobkill_flags) / sizeof(jobkill_flags[0]))
typedef struct {
xcecli_ctx_t conn;
@ -3949,7 +4127,6 @@ static const flagspec_t jobckpt_flags[] = {
{ 'm', "checkpoint-method", "checkpoint-method", required_argument, 0,
"\"default\"" },
};
#define JOBCKPT_NFLAGS (sizeof(jobckpt_flags) / sizeof(jobckpt_flags[0]))
typedef struct {
xcecli_ctx_t conn;
@ -4073,7 +4250,6 @@ static const flagspec_t qctrl_flags[] = {
* the other four destructive commands (Requirement 6.2). */
{ 'y', "yes", "yes", no_argument, 0, NULL },
};
#define QCTRL_NFLAGS (sizeof(qctrl_flags) / sizeof(qctrl_flags[0]))
typedef struct {
xcecli_ctx_t conn;
@ -4234,7 +4410,6 @@ static const flagspec_t hostctrl_flags[] = {
* spelling as the other four destructive commands (Requirement 6.2). */
{ 'y', "yes", "yes", no_argument, 0, NULL },
};
#define HOSTCTRL_NFLAGS (sizeof(hostctrl_flags) / sizeof(hostctrl_flags[0]))
typedef struct {
xcecli_ctx_t conn;
@ -4403,8 +4578,6 @@ static int upgradeconfig_thunk(void* ctxp)
static const flagspec_t upgradeconfig_flags[] = {
{ 'y', "yes", "yes", no_argument, 0, NULL },
};
#define UPGRADECONFIG_NFLAGS \
(sizeof(upgradeconfig_flags) / sizeof(upgradeconfig_flags[0]))
int cmd_upgradeconfig(int argc, char* argv[])
{
@ -4544,7 +4717,6 @@ static const flagspec_t runtasks_flags[] = {
{ 't', "launch-type", "launch-type", required_argument, 0,
"\"mpi\" if >1 total tasks, else \"sequential\"" },
};
#define RUNTASKS_NFLAGS (sizeof(runtasks_flags) / sizeof(runtasks_flags[0]))
typedef struct {
xcecli_ctx_t conn;
@ -4756,7 +4928,6 @@ static const flagspec_t setpend_flags[] = {
{ 'j', "jobid", "jobid", required_argument, 1, NULL },
{ 'p', "reason", "reason", required_argument, 1, NULL },
};
#define SETPEND_NFLAGS (sizeof(setpend_flags) / sizeof(setpend_flags[0]))
typedef struct {
xcecli_ctx_t conn;
@ -4955,7 +5126,6 @@ static const flagspec_t jobstart_flags[] = {
{ 'p', "pid", "pid", required_argument, 0,
"this xce_test process's own pid" },
};
#define JOBSTART_NFLAGS (sizeof(jobstart_flags) / sizeof(jobstart_flags[0]))
typedef struct {
xcecli_ctx_t conn;
@ -5060,7 +5230,6 @@ static const flagspec_t jobfinish_flags[] = {
{ 'j', "jobid", "jobid", required_argument, 1, NULL },
{ 'c', "exit-code", "exit-code", required_argument, 0, "0" },
};
#define JOBFINISH_NFLAGS (sizeof(jobfinish_flags) / sizeof(jobfinish_flags[0]))
typedef struct {
xcecli_ctx_t conn;
@ -5173,7 +5342,6 @@ static const flagspec_t jobfail_flags[] = {
{ 's', "stage", "stage", required_argument, 0,
"JOB_EXEC_STAGE_POST_EXEC" },
};
#define JOBFAIL_NFLAGS (sizeof(jobfail_flags) / sizeof(jobfail_flags[0]))
typedef struct {
xcecli_ctx_t conn;
@ -5497,7 +5665,6 @@ static const flagspec_t resupdate_flags[] = {
{ 't', "value-type", "value-type", required_argument, 0,
"numeric unless overridden" },
};
#define RESUPDATE_NFLAGS (sizeof(resupdate_flags) / sizeof(resupdate_flags[0]))
/* Array sizes below are hardcoded to 16 to match cmd_resupdate's own
* MAX_N_RES -- that macro is #define'd inside cmd_resupdate's own body