cad0600bd259bae35de7b39e49c2f95af8b624ca
braney
  Mon Sep 14 16:14:40 2026 -0700
docent: point a whole test directory at another server with TARGET, refs #37892

A script says where it runs with `target:`, and until now that was the only way
to say it, so trying a suite against a branch build meant editing every script in
the directory.  DOCENT_TARGET overrides `target:` for the run, and docentTest.mk
turns a TARGET= variable into it for preflight, test, parity and derive:

make test TARGET=hgwdev-demo9
make preflight TARGET=hgwdev-demo9

It takes the same values `target:` does -- a shorthand, a bare hgwdev-<name>
sandbox or demo, or a full .../cgi-bin URL -- so a ts park works too.

preflight.js reads the same variable, so the fixture check names the server that
will actually be driven rather than the one the scripts name.  The trackDb cache
already keys on the resolved server, so a redirected run cannot read back a
listing fetched from somewhere else.

The committed scripts keep their own `target:`.  Redirecting is for trying a
suite elsewhere, not for moving it: the nightly reads what is in the file.  Both
READMEs say so, and say to read a redirected failure with the other server's
trackDb in mind, since a script asserts what its own server draws.

Verified against the ten methbase scripts: all ten pass with
TARGET=hgwdev-demo9, and mbMouse still passes with no TARGET, against the RR.

diff --git src/hg/utils/docent/tests/preflight.js src/hg/utils/docent/tests/preflight.js
index 1fbca9d6c12..61aa2fda210 100644
--- src/hg/utils/docent/tests/preflight.js
+++ src/hg/utils/docent/tests/preflight.js
@@ -82,31 +82,33 @@
 
 const named = process.argv.slice(3)
   .map(a => a.endsWith('.docent.yaml') ? a : `${a}.docent.yaml`);
 const scripts = (named.length ? named
                              : fs.readdirSync(DIR).filter(f => f.endsWith('.docent.yaml'))).sort();
 const seenServer = new Map();
 
 for (const f of scripts) {
   let doc;
   try {
     doc = yaml.load(fs.readFileSync(path.join(DIR, f), 'utf8')) || {};
   } catch (e) {
     add({ script: f, kind: 'script', label: f, check: async () => `unreadable YAML: ${e.message}` });
     continue;
   }
-  const server = resolveTarget(doc.target).replace(/\/$/, '');
+  // DOCENT_TARGET redirects the run, so the server fixture has to be the one that will
+  // actually be driven rather than the one the script names. Same override as docent.js.
+  const server = resolveTarget(process.env.DOCENT_TARGET || doc.target).replace(/\/$/, '');
   if (!seenServer.has(server)) seenServer.set(server, f);
   const base = path.basename(f, '.docent.yaml');
 
   for (const step of (doc.steps || [])) {
     if (!step || typeof step !== 'object') continue;
     const verb = Object.keys(step)[0];
     const arg = step[verb];
     const o = (arg && typeof arg === 'object') ? arg : null;
 
     if (verb === 'loadSession') {
       if (o && o.user && o.name) {
         add({ script: f, kind: 'session', label: `${o.user}/${o.name}`,
               check: sessionCheck(server, o.user, o.name) });
       } else if (o && o.file) {
         // A session: step earlier in the same script writes this, so it is only a