da449b4fe2039f200a2f93d5209c48fe98c0a534
braney
  Tue Sep 29 10:58:30 2026 -0700
cartPcrVarTester: which hgPcrResult_ cart variables survive a cart load, refs #38442

Feeds hgPcrResult_ variables through cartParseOverHash() and says which are
kept.  With the cart.c fix backed out it shows hgPcrResult_imgOrd dropped and
nothing else changes.  Registry row and catalog note added.

diff --git src/hg/lib/tests/cartPcrVarTester.c src/hg/lib/tests/cartPcrVarTester.c
new file mode 100644
index 00000000000..52e077f9fab
--- /dev/null
+++ src/hg/lib/tests/cartPcrVarTester.c
@@ -0,0 +1,74 @@
+/* cartPcrVarTester - check which hgPcrResult_ cart variables survive a cart load.
+ *
+ * hgPcr saves a result in hgPcrResult_<db> as two trash file names and an optional target
+ * name, and cart.c drops a value of that name that is not a pair of file names the server
+ * made.  The variable is matched by its prefix, because <db> is any assembly or hub name.
+ *
+ * But hgPcrResult is also the name of the track that shows the result, so the track's own
+ * settings begin with the same prefix.  When a user drags the track, hgTracks saves its place
+ * in the image as hgPcrResult_imgOrd, and the cart threw that away on the next load.  The
+ * track then went back to the bottom of the image on every zoom or scroll, however often it
+ * was dragged up again (#38442).  hgPcrResult_targetStyle, a display setting, is the other
+ * name with the prefix that is not a result.
+ *
+ * Nothing on the page says a value was dropped; the only trace is a line in the error log.
+ * So the cases here are fed through cartParseOverHash(), the same parse a saved cart or
+ * session goes through, and the output says which ones are kept.  No database is needed:
+ * with no merge, the parse only touches cart->hash.
+ *
+ * refs #38442, refs #37623 */
+
+/* Copyright (C) 2026 The Regents of the University of California
+ * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
+
+#include "common.h"
+#include "hash.h"
+#include "cheapcgi.h"
+#include "cart.h"
+#include "portable.h"
+
+static void check(char *var, char *val, char *what)
+/* Load one variable into an empty cart and say whether it is still there. */
+{
+struct cart *cart;
+AllocVar(cart);
+cart->hash = newHash(0);
+char contents[1024];
+safef(contents, sizeof(contents), "%s=%s", var, cgiEncodeFull(val));
+cartParseOverHash(cart, contents);
+char *got = hashFindVal(cart->hash, var);
+printf("  %-26s %-40s %s\n", var, what, (got != NULL) ? "kept" : "dropped");
+hashFree(&cart->hash);
+freez(&cart);
+}
+
+int main(int argc, char *argv[])
+{
+/* The trash directory is "../trash" in a CGI and something else from the command line, so
+ * the result files are named under whatever this process calls the trash. */
+char psl[512], primers[512], pair[1100], pairTarget[1200], badFirst[600], badSecond[600];
+safef(psl, sizeof(psl), "%s/hgPcr/hgPcr_a.psl", trashDir());
+safef(primers, sizeof(primers), "%s/hgPcr/hgPcr_a.txt", trashDir());
+safef(pair, sizeof(pair), "%s %s", psl, primers);
+safef(pairTarget, sizeof(pairTarget), "%s %s gencode", psl, primers);
+safef(badFirst, sizeof(badFirst), "/etc/passwd %s", primers);
+safef(badSecond, sizeof(badSecond), "%s /etc/passwd", psl);
+
+printf("track settings that share the prefix are kept\n");
+check("hgPcrResult_imgOrd", "3", "3");
+check("hgPcrResult_targetStyle", "box", "box");
+
+printf("a result pair of trash files is kept\n");
+check("hgPcrResult_hg38", pair, "two trash files");
+check("hgPcrResult_hg38", pairTarget, "two trash files and a target");
+
+printf("a result that does not name two trash files is dropped\n");
+check("hgPcrResult_hg38", "3", "3");
+check("hgPcrResult_hg38", badFirst, "a psl file outside the trash");
+check("hgPcrResult_hg38", badSecond, "a primer file outside the trash");
+
+printf("the exclusions are exact names, not suffixes\n");
+check("hgPcrResult_hg38_imgOrd", "/etc/passwd /etc/passwd", "two files outside the trash");
+check("hgPcrResult_imgOrdx", "/etc/passwd /etc/passwd", "two files outside the trash");
+return 0;
+}