7f5ea857be45e43cece2cfb37093c6c51b23f79d
braney
  Sun Sep 6 08:23:15 2026 -0700
docent: run the nightly regression out of a checkout of its own

The header of this script has said since it was written that once the tests were
on master the cron should read them out of a clone of its own, the way
catalogNightly has one under /hive/users/braney, so that an ordinary day's
editing in ~/kent cannot change what the cron measures.  The suite landed on
master yesterday, so this does it.  The clone is
/hive/users/braney/docentNightly/kent, beside the log directory the job already
writes to.  Nothing here is built, so it needs no submodules and no make.

--update brings that clone to origin/master and then re-runs this script from
the result.  The re-exec is why this is a flag here rather than a separate driver
script beside the crontab: the whole job stays in the tree where it can be
reviewed and committed, and a change to this file takes effect the same night as
a change to a test instead of a night later.

--update will only ever touch a checkout that is a pristine mirror of
origin/master.  A working tree with uncommitted edits, or one holding a commit
that has not been pushed, is left alone and reported rather than reset, because
`reset --hard` in ~/kent would throw away work.  That guard is what makes it safe
for the flag to live in a script that also sits in a working tree.  Both refusals
mail with the same subject shape as a finished run, since a night when nothing
ran must not read as a quiet night.

refs #38252

diff --git src/hg/utils/docent/tests/regress/nightly.sh src/hg/utils/docent/tests/regress/nightly.sh
index f3bc5d90ed2..c710d6e1ad0 100755
--- src/hg/utils/docent/tests/regress/nightly.sh
+++ src/hg/utils/docent/tests/regress/nightly.sh
@@ -1,95 +1,150 @@
 #!/bin/bash
 # Nightly Docent regression run against genome-test, for cron.  refs #38252
 #
 # Mails a report every night whether anything failed or not, on purpose and to match
 # the catalogNightly job: no mail means the cron itself has stopped, rather than
 # meaning the browser is fine.  Always exits 0, so cron does not send a second mail
 # of its own on top of this one.
 #
 # Runs the tests that are COMMITTED, listed from git rather than from the directory.
 # The directory also holds work in progress -- scripts written against a ticket whose
 # recipe is not right yet -- and those must not mail a failure every night.  A newly
 # committed test is picked up with no edit here.
 #
-# It runs out of whatever checkout this script itself lives in, so a second copy in a
-# second checkout needs no argument.  While the tests live only on branch
-# docentTests37892 that means Brian's worktree.  Once the branch is on master this
-# should move to a checkout of its own, the way catalogNightly has one under
-# /hive/users/braney, so that an ordinary day's editing cannot change what the cron
-# measures.
+# It runs out of whatever checkout this script itself lives in, so a copy in a second
+# checkout needs no argument.  The cron runs it out of a clone of its own,
+#
+#     /hive/users/braney/docentNightly/kent
+#
+# the way catalogNightly has one under /hive/users/braney, so that an ordinary day's
+# editing in ~/kent cannot change what the cron measures.  Nothing here is built, so
+# the clone needs no submodules and no make.
+#
+# --update brings that clone to origin/master and then re-runs this script from the
+# result.  Two reasons for the re-exec rather than a separate driver script beside the
+# crontab: the whole job stays in the tree where it can be reviewed and committed, and
+# a change to this file takes effect the same night as a change to a test, instead of
+# a night later.
+#
+# --update will only ever touch a checkout that is a pristine mirror of origin/master.
+# A working tree with uncommitted edits, or one holding a commit that has not been
+# pushed, is left alone and reported, because a `reset --hard` there would throw away
+# work.  That is what makes it safe for the flag to exist in a script that also sits in
+# a working tree.
 
 set -u
 export PATH=/usr/bin:/bin:/usr/local/bin:$PATH
 
 HERE=$(cd "$(dirname "$0")" && pwd)
 PW=/hive/groups/browser/uiTest/pw
 PW_ENV="env PLAYWRIGHT_BROWSERS_PATH=$PW/browsers NODE_PATH=$PW/node_modules"
 TO=${DOCENT_NIGHTLY_TO:-braney@ucsc.edu}
 LOGDIR=${DOCENT_NIGHTLY_LOGS:-/hive/users/braney/docentNightly/logs}
 STAMP=$(date +%Y-%m-%d_%H%M)
 
 mkdir -p "$LOGDIR"
 OUT="$LOGDIR/$STAMP.txt"
 
+# A job that could not start has to arrive looking like the others, or a night when
+# nothing ran reads as a quiet night.  Same subject shape, same log file, exit 0.
+bail() {
+{
+    echo "Docent regression run, $(date)"
+    echo "checkout: $HERE"
+    echo
+    echo "$@"
+    echo
+    echo "Nothing was tested.  This is a problem with this job, not with the browser."
+} > "$OUT" 2>&1
+mail -s "docent regression: BROKEN (0 ok) $(date +%F)" "$TO" < "$OUT"
+exit 0
+}
+
+if [ "${1:-}" = --update ] && [ -z "${DOCENT_NIGHTLY_UPDATED:-}" ]; then
+    shift
+    dirty=$(git -C "$HERE" status --porcelain --untracked-files=no 2>&1)
+    if [ -n "$dirty" ]; then
+        bail "--update will not reset $HERE: it has uncommitted changes.
+
+$dirty"
+    fi
+    git -C "$HERE" fetch -q origin master 2>&1 || \
+        bail "--update could not fetch origin master into $HERE."
+    ahead=$(git -C "$HERE" rev-list --oneline FETCH_HEAD..HEAD 2>&1)
+    if [ -n "$ahead" ]; then
+        bail "--update will not reset $HERE: it holds commits that are not on origin/master.
+
+$ahead"
+    fi
+    git -C "$HERE" reset -q --hard FETCH_HEAD 2>&1 || \
+        bail "--update could not move $HERE to FETCH_HEAD."
+    # Re-open this file by name, which is now the copy that just arrived.
+    export DOCENT_NIGHTLY_UPDATED=1
+    exec "$0" "$@"
+fi
+
 # The committed scripts, as bare test names.  `make test T=` takes a list.
 TESTS=$(cd "$HERE" && git ls-files '*.docent.yaml' 2>/dev/null \
         | sed 's#.*/##; s#\.docent\.yaml$##' | tr '\n' ' ')
 
 {
   echo "Docent regression run, $(date)"
   echo "checkout: $HERE"
   echo "branch:   $(git -C "$HERE" rev-parse --abbrev-ref HEAD 2>/dev/null)"
   echo "commit:   $(git -C "$HERE" rev-parse --short HEAD 2>/dev/null)"
+  if [ -n "${DOCENT_NIGHTLY_UPDATED:-}" ]; then
+    echo "updated:  this checkout was reset to origin/master before the run"
+  fi
   echo
 
   if [ -z "${TESTS// /}" ]; then
     echo "NO COMMITTED TESTS FOUND -- git ls-files returned nothing in $HERE."
     echo "That is a problem with this job, not with the browser."
     subject_state="BROKEN"
   else
     echo "tests: $TESTS"
     echo
     echo "--- preflight (the sessions and hubs these tests depend on) ---"
     # Reported separately from the tests on purpose.  A session that has been deleted
     # or a hub that has moved is not a browser regression, and the two must not arrive
     # as the same red.
     #
     # preflight is given the committed list rather than left to scan the directory, so a
     # dead fixture belonging to a work-in-progress script is not reported as a problem
     # with a run that never included it.
     if (cd "$HERE" && $PW_ENV node ../preflight.js . $TESTS 2>&1); then
       pf=ok
     else
       pf=MISSING
     fi
     echo
     echo "--- tests ---"
     if (cd "$HERE" && make test T="$TESTS" 2>&1); then
       tests=pass
     else
       tests=FAIL
     fi
 
     if [ "$tests" = FAIL ] && [ "$pf" = MISSING ]; then
       subject_state="FAIL (fixtures missing too)"
     elif [ "$tests" = FAIL ]; then
       subject_state="FAIL"
     elif [ "$pf" = MISSING ]; then
       subject_state="pass, but a fixture is missing"
     else
       subject_state="pass"
     fi
   fi
 
   echo
   echo "full log kept at $OUT"
 } > "$OUT" 2>&1
 
 # subject_state is set inside the block above, which runs in this shell, so it survives.
 n=$(grep -c '^  ok$' "$OUT" 2>/dev/null || echo 0)
 mail -s "docent regression: ${subject_state:-unknown} ($n ok) $(date +%F)" "$TO" < "$OUT"
 
 # Keep two months of logs and no more.
 find "$LOGDIR" -name '*.txt' -mtime +60 -delete 2>/dev/null
 
 exit 0