b14d44f6f6031286fde5318abf67086dd29a1b43
braney
  Fri Sep 18 09:52:30 2026 -0700
hgTablesTest: follow the redirect on the starting url instead of failing later

A url with no scheme is fetched over http, and both hgwdev and a sandbox
answer plain http with a 301 to https.  The run parsed the redirect page,
found no form in it, and failed several steps later saying "Null form in
htmlPageSetVar", which named neither the url nor the redirect.

rootPageGet() now fetches through htmlPageForwarded(), says at verbose 1
when the url changed, and errAborts naming the code if the final status is
not 200.  Every later request is built from this page, so this puts the
whole run on the url the server asked for, not just the first fetch.

The robots were never exposed to this: both doHgTablesTestRobot.csh and
preview2TablesTestRobot.csh already pass https urls.

refs #38356

diff --git src/hg/hgTablesTest/hgTablesTest.c src/hg/hgTablesTest/hgTablesTest.c
index ff18efcbb52..633daa7eda1 100644
--- src/hg/hgTablesTest/hgTablesTest.c
+++ src/hg/hgTablesTest/hgTablesTest.c
@@ -1422,50 +1422,69 @@
 static void catchRootTest(void (*test)(struct htmlPage *rootPage), char *name,
 	struct htmlPage *rootPage)
 /* Run one of the whole-program uniProt tests, surviving an abort.  These run
  * last, so an abort in the first of them used to take the other two and the
  * summary with it. */
 {
 struct errCatch *errCatch = errCatchNew();
 if (errCatchStart(errCatch))
     test(rootPage);
 errCatchEnd(errCatch);
 if (errCatch->gotError)
     recordAbort(errCatch->message->string, name, NULL, "uniProt", NULL, NULL, NULL);
 errCatchFree(&errCatch);
 }
 
+static struct htmlPage *rootPageGet(char *url)
+/* Fetch the page the whole run starts from, following a redirect if the server
+ * sends one.  Both hgwdev and a sandbox answer plain http with a 301 to https,
+ * and a url given with no scheme is fetched over http, so without this the run
+ * parses the redirect page, finds no form in it, and dies several steps later
+ * saying "Null form in htmlPageSetVar", which names neither the url nor the
+ * redirect.  Every later request is built from this page, so following the
+ * redirect here also puts the rest of the run on the url the server asked for. */
+{
+struct htmlPage *page = htmlPageForwarded(url, NULL);
+if (page == NULL)
+    errAbort("Couldn't get %s", url);
+if (!sameString(page->url, url))
+    verbose(1, "%s redirected to %s\n", url, page->url);
+if (page->status->status != 200)
+    errAbort("%s returned HTTP status code %d", page->url, page->status->status);
+return page;
+}
+
 int hgTablesTest(char *url, char *logName)
 /* hgTablesTest - Test hgTables web page.  Returns the exit code: zero only if
  * the run finished and no test hit a hard error. */
 {
 /* Get default page, and open log. */
-struct htmlPage *rootPage = htmlPageGet(url);
+struct htmlPage *rootPage = rootPageGet(url);
 if (appendLog)
     logFile = mustOpen(logName, "a");
 else
     logFile = mustOpen(logName, "w");
 if (! endsWith(url, "hgTables"))
     warn("Warning: first argument should be a complete URL to hgTables, "
 	 "but doesn't look like one (%s)", url);
 
 fprintf(logFile,"seed=%d\n",seed);
  
 showRunningHostName();
 
-verbose(1, "Testing URL %s\n", url);
-fprintf(logFile, "Testing URL %s\n", url);
+verbose(1, "Testing URL %s\n", rootPage->url);
+fprintf(logFile, "Testing URL %s\n", rootPage->url);
 
 /* Show what database server we are connecting to. 
 Matters for expected rows in tables. */
 showConnectInfo("uniProt");
 
 htmlPageValidateOrAbort(rootPage);
 
 /* Go test what they've specified in command line. */
 if (clDb != NULL)
     {
     testDb(rootPage, NULL, clDb);
     }
 else
     {
     struct htmlForm *mainForm;