d08323bb2ff524e4964a0922ecfb8336ab662007
braney
Wed Sep 16 10:25:56 2026 -0700
docent: two tests for gaps a cart-visibility branch went past, refs #37892
The #37547 branch broke quickLift and nine scripts in tests/regress caught it
without help. Two other things went past the whole suite, and these are the
tests for them.
heavysession is selftest on a cart with weight behind it. selftest saves a
session and loads it back, which is the right shape, but the cart it round-trips
holds two rows. Saving a cart is not a copy of it: outIfNotPresent() in
hg/hgSession/hgSession.c writes a trackDb default for every track that is
deliberately NOT in the cart, so the file says what is hidden as well as what is
shown, and a two-track cart barely reaches that code. A save-and-reload path
that returned a 38-row clinical session as 34 rows left selftest green.
The weight comes from a Recommended Track Set, which is where a clinical user
starts: View/Clinical_SNVs_hg38, out of
DOCUMENT_ROOT/data/recTrackSets/recTrackSets.hg38.tab. It draws 33 tracks and
the ruler, and the file the session: step writes out of that cart is 741
settings, 248 of them visibilities and 154 of those hide. Both halves of the
trip assert the row set with exact: true, because rows: alone would pass on a
reload that lost four of them, and a count cannot say which row went missing.
It is a saved session on the server, so make preflight already checks it is
still there -- a deleted one answers 200 with a page that has no track image, on
which every noText: check passes.
firstrequest is about a bug that lags by exactly one request: the visibility
reaches the cart, the image drawn in reply does not carry the row, and the next
request draws it. A script shaped track: -> go: -> expect: supplies that extra
request itself and passes on the broken build. So this one asserts with no
go:, open: or convert: between the track: step and the check.
Which track it names is the other half, and it is not free choice. A top-level
track passes on a build with the bug, because hgTracks adds every top-level
track as a lightweight stub so the track controls can list it -- microsat,
gtexGene and windowmaskerSdust all drew. A default-visible child passes too,
because its container is in the list already: wgEncodeRegMarkH3k27ac drew while
its sibling wgEncodeRegMarkH3k4me1 did not, same superTrack and same request.
So the script names wgEncodeRegMarkH3k4me1, which is visibility hide under a
superTrack that is itself superTrack on hide, and the hideKids step before it is
what stops the other members coming back at their own trackDb visibility.
expected/firstrequest.derive is committed with it, and it is not decoration.
The test only means anything while the step under test is ONE round:
step 5 track {"wgEncodeRegMarkH3k4me1":"full"}
round 1 (2 vars): wgEncodeRegMarkH3k4me1=full wgEncodeReg=show
If Docent ever splits that the way it splits a container-plus-subtrack-hide, the
browser test would go on passing and stop being able to see the bug. The
baseline says so out loud.
README.txt describes both, and adds hgCollection to "Still to write": no script
in either directory reaches that CGI, and hg/hgCollection/hgCollection.c carries
its own verbatim copy of isParentVisible() from hg/lib/trackHub.c. Nine scripts
caught the trackHub.c copy on the branch; nothing caught this one. Covering it
needs a collection: verb, since tracks go into a collection by dragging between
two jsTrees and drag: is the genomic drag-select on the track image.
Seventeen of seventeen pass in tests/, including the four xfails.
diff --git src/hg/utils/docent/tests/README.txt src/hg/utils/docent/tests/README.txt
index c3d117deba9..d4a5b3ffaa5 100644
--- src/hg/utils/docent/tests/README.txt
+++ src/hg/utils/docent/tests/README.txt
@@ -1,147 +1,171 @@
Docent tests
------------
Run by hand, not by the kent tree's `make test`:
make test # every *.docent.yaml here
make test T=composite # just one
make parity # one script FAST and slow, and twice over
make derive # the derivation alone, against expected/ (no browser)
make derive-accept # rewrite those baselines, then read `git diff expected/`
make test TARGET=hgwdev-demo9 # the same scripts, against another server
TARGET overrides the `target:` each script carries, for every target above, and takes
the same values it does: a shorthand (rr, genome-test, hgwdev, hgwbeta), a bare
hgwdev-<name> sandbox or demo, or a full .../cgi-bin URL. It is how you try a suite
against a branch build -- a sandbox, a ticket park from `ts`, a demo browser -- without
editing the scripts. `make preflight TARGET=...` checks that server rather than the one
the scripts name, so the fixture check and the run agree.
Read a redirected run's failures with the server in mind. A script asserts what its OWN
server draws, so a red one somewhere else can be the other machine's trackDb rather than
a bug: a demo sandbox that carries only one assembly fails every script on the others,
and a sandbox trackDb with a track the RR has not released changes what `exact: true`
counts. Redirecting is for trying a suite elsewhere, not for moving it: the committed
scripts stay pointed at the server they were written against, which is the one the
nightly reads.
Most tests drive a real browser against a real server, so they need the network and
the shared Playwright install (/hive/groups/browser/uiTest/pw; see ../README.md). That is why none of this is
part of the tree-wide test target: a broken network would fail the build.
A test is an ordinary Docent script that asserts with `expect:`. It passes by exiting
0. `expect:` is the only verb that CHECKS anything, so a test with no `expect:` step in
it tests nothing: `track:` accepts a name no assembly has and still exits 0.
Other verbs do fail a run, so do not read the line above as "nothing else can stop it".
A verb throws when it cannot do what it was told -- `mouseover:` cannot find the item,
`loadSession:` cannot find the file, `drag:`, `convert:` and `go:` likewise -- and
docent.js turns any step's throw into `step N (verb) failed` and exit 1. None of them
looks at whether the page came out right, which is the part only `expect:` does.
A script named *.xfail.docent.yaml is expected to FAIL, and the run fails if it passes.
That is how a trap gets pinned rather than merely written down.
`make derive` is the cheap half: DOCENT_DERIVE=1 resolves the `track:` steps against
trackDb and prints the cart variables without opening a browser, in about a second. It
is where Docent's own decisions live, and the baselines in expected/ are what catch a
change to visVars() or tdbHideTargets() that a rendered page would hide.
The trackDb listing is cached in $TMPDIR for a day (docent.js, TDB_TTL), and a cold
fetch prints one provenance line that a warm run does not. That line would make the
first `make derive` of the day differ from a baseline captured warm, for a reason that
has nothing to do with trackDb changing, so the makefile strips it from both the run and
the baseline. Everything else trackDb says about itself is kept, including the two lines
that report a hub genome or an unreachable hubApi.
What is covered
---------------
selftest session: -> expect: -> loadSession:, on hg38 at SHH. Saves the cart,
changes the view, restores it from the local file, checks rows both times.
+ heavysession the same three steps as selftest, on a Recommended Track Set: 34 rows in,
+ saved, moved away, loaded back, `exact: true` on both halves. selftest
+ round-trips two rows, which barely reaches outIfNotPresent() in hgSession
+ -- the function that writes a trackDb default for every track that is
+ deliberately NOT in the cart, and the one a broken save-and-reload path
+ shows up in. A path that dropped four rows left selftest green.
+ firstrequest a track turned on has to be drawn by the request that turned it on, with
+ no `go:`, `open:` or `convert:` in between. The bug it exists for lags by
+ exactly one request, so any script that navigates before asserting reads a
+ correct image and passes. It names wgEncodeRegMarkH3k4me1 for the reason
+ in its header: a top-level track or a default-visible child would pass on
+ the broken build too.
composite clinvar with clinvarCnv hidden: the two-request split (#37953). One
request would leave clinvarCnv_sel=1 and the CNV row drawn.
views hideKids on the VIEW that holds the subtrack, with the sibling views
hidden by name. Also covers the `_sel` checkbox, since the subtrack is
`parent wgEncodeRegDnaseSignal off`, and pins the superTrack side effect
below.
views.xfail the same thing aimed at the COMPOSITE instead, which loses the row.
Expected to fail.
supertrack varsInPubs hideKids + one member: `exact: true`, because a test that only
checked the member was present would pass with all six drawn.
urllen {cCREs: hideKids} must not become the 1701-variable, 42,020-character GET
that Apache answered with 414. Checks `noText: "Too Long"`, since a 414
renders as a perfectly good page; the derive baseline pins it at 3.
customtrack addCustomTrack: with inline BED, tabs and newlines surviving the trip.
scale a 3x run draws the same rows as a 1x one.
ordered `ordered: true` on rows:, and the fact that a row which was not drawn is
reported by rows: alone rather than failing the order check as well.
ordered.xfail the same two rows named the wrong way round. Expected to fail -- a flag
that cannot fail is not a check, it is a second copy of the set test.
pagechecks the `expect:` checks that read the PAGE rather than the track image --
`url:`/`noUrl:` on the address, `has:`/`noHas:` on a CSS selector -- plus
the positional form of `click:` (`{track:, frac:}`), which follows the
item box nearest a point. All four exist for bugs that rows:, height: and
text: cannot see: a search term's zero-width space stripped out of a URL
(#36387), a center label attached to the wrong row (#37785), and an item
that cannot be named at all because its track is `type bigBed 3` (#36335).
pagechecks the same four aimed the wrong way at once. Expected to fail. The message
.xfail names every check that failed, so one run says which of the four broke.
colorchecks `color:`, the one check that reads the track IMAGE: is:/not: on the color
a row is mostly drawn in, `part: label` for the center label instead of
the items, `at:` for one item rather than the whole row, and the list
form. It exists for #36212, where a track that sets both `itemRgb on` and
`color` draws its items in the wrong one -- same rows, same height, same
names, same tooltips, so nothing but the pixels can tell.
colorchecks the same six aimed wrong, all in ONE expect: step so the message has to
.xfail name all six. Expected to fail. The comment lists them in order; read the
log rather than trusting the exit code.
expectfail an assertion that is plainly false. Expected to fail -- if it ever passes,
.xfail `expect:` has stopped throwing and every other test here means nothing.
make parity FAST vs slow, and a rerun, on composite. FAST drops the dwells and the
recording and must not change what the page ends up showing; the rerun
catches state left behind in the cart.
Two things these tests found
----------------------------
Worth knowing before writing more:
* Turning on anything under a superTrack sends `<superTrack>=show`, and every OTHER
member then comes up at its own trackDb visibility -- so `hide: all` is undone for
them. views asserts wgEncodeRegMarkH3k27ac comes back, rather than working around it.
Whether Docent should be cleverer here is an open question, not a settled one.
* `hideKids` on a VIEW has to enumerate its leaves (a view holds no sub-containers to
stop at), so views sends 188 variables in a 6,986-character request. That is under
Apache's 8,190 limit with less room than is comfortable. Its `noText: "Too Long"` is
what turns a future overflow into a clear failure instead of a strange one.
Still to write
--------------
mouseover: by item: on stacked items, and the timing case where a neighbour's
tooltip is still up on arrival
pinShot: several tooltips in one figure, cursors drawn
convert: quickLift onto a GenArk haplotype, hideDefaults re-checked -- note a
session taken after it cannot be checked in, see #38046
drag: each of then: zoom / highlight / cancel
addHub:,
addPublicHub: the two hub attach paths (a stable hub URL is the hard part)
montage: panel order, lettering, a named shot that was never taken
goShow: the suggestion menu, including a `pick:` that matches nothing
loadSession: the three remote forms -- only the local-file form is covered
the YAML lint `{item:name}` with no space warns and drops the argument. This needs a
test that reads stderr, which the harness does not do yet.
+ hgCollection no script here or in regress/ reaches that CGI at all, and it shares
+ visibility logic with hgTracks by COPY rather than by call:
+ hg/hgCollection/hgCollection.c carries its own isParentVisible(), a
+ verbatim copy of the one in hg/lib/trackHub.c. The copy in trackHub.c
+ was caught by nine scripts in regress/ on the #37547 branch; the copy in
+ hgCollection.c was found by grep afterwards, and would have dropped a
+ container's children out of a saved collection in the same silent way.
+ A test needs a `collection:` verb: the page puts tracks into a
+ collection by dragging between two jsTrees, and `drag:` is the
+ genomic drag-select on the track image, not that. Its buttons are
+ #newCollection, #doNewCollection, #saveCollections and #discardChanges,
+ which is enough to open and save one but not to put a track in it.
A test that needs a stable server-side fixture (a hub, a custom track) should carry it
in the script rather than assume something on disk.
colorchecks is the one exception, and the reason is worth knowing before someone else
hits it. `color:` has to address a ROW by name, and a custom track cannot be addressed
by name at all: hgTracks assigns its row id (`ct_<name>_<number>`), which is why
customtrack asserts on label text instead of on `rows:`. So an inline custom track --
the self-contained way to get a known color onto the page -- is the one fixture this
check cannot use. It reads ~/public_html/docentFixtures/itemRgbHub/ instead, which
tests/regress/rm36212.xfail needs anyway, and which `make preflight` checks is still
there.