64675c24b3136d06f2123a1039a034114bd597b6 lrnassar Thu Feb 12 13:35:41 2026 -0800 Improving error messages mostly on hubCheck, with a few trackHub.c cases. Overall making the messages more informative to tell users what went wrong and what they can do to fix it, refs #35718 diff --git src/hg/utils/hubCheck/hubCheck.c src/hg/utils/hubCheck/hubCheck.c index 9ac7eebd5a6..62a38e38f57 100644 --- src/hg/utils/hubCheck/hubCheck.c +++ src/hg/utils/hubCheck/hubCheck.c @@ -435,31 +435,31 @@ if (options->extra && hashLookup(options->extra, setting)) return 0; /* check setting is supported in this version */ struct trackHubSettingSpec *hubSetting = hashFindVal(options->settings, setting); boolean hasComplexSetting = isComplexSetting(setting); if (hasComplexSetting) { verbose(5, "skipping validation for complex setting=%s.", setting); return 1; } if (hubSetting == NULL) { - dyStringPrintf(errors, "Setting '%s' is unknown/unsupported", setting); + dyStringPrintf(errors, "Setting '%s' is not recognized. Check for typos, or see https://genome.ucsc.edu/goldenPath/help/trackDb/trackDbHub.html for supported settings", setting); char *suggest = suggestSetting(setting, options); if (suggest != NULL) dyStringPrintf(errors, " (did you mean '%s' ?)", suggest); dyStringPrintf(errors, "\n"); retVal = 1; } else if (sameString(hubSetting->level, "deprecated")) { dyStringPrintf(errors, "Setting '%s' is deprecated\n", setting); retVal = 1; } else { /* check level */ struct trackHubSettingSpec *checkLevel = NULL; @@ -637,50 +637,50 @@ int i; char *subtrackName = trackHubSkipHubName(tdb->track); sortOrder_t *sortOrder = NULL; membership_t *membership = NULL; membersForAll_t *membersForAll = NULL; struct errCatch *errCatch = errCatchNew(); if (errCatchStart(errCatch)) { // check that subtrack group is the same as the parent group: char *subTrackGroup = tdb->grp; char *parentGroup = tdb->parent->grp; if (!sameString(subTrackGroup, parentGroup)) { - errAbort("assembly %s: track %s has a different group (%s) than parent %s (group %s). Please specify the group setting in both the parent and the subtrack stanzas", trackHubSkipHubName(genome->name), subtrackName, subTrackGroup, trackHubSkipHubName(tdb->parent->track), parentGroup); + errAbort("assembly %s: subtrack \"%s\" has 'group %s' but its parent \"%s\" has 'group %s'. Subtracks inherit their parent's group. Either remove the 'group' line from the subtrack, or set both parent and subtrack to the same group.", trackHubSkipHubName(genome->name), subtrackName, subTrackGroup, trackHubSkipHubName(tdb->parent->track), parentGroup); } // check subgroups settings membersForAll = membersForAllSubGroupsGet(tdb->parent, NULL); // membersForAllSubGroupsGet() warns about the parent stanza, turn it into an errAbort if (errCatch->gotWarning) { errAbort("%s", errCatch->message->string); } if (membersForAll && checkEmptyMembersForAll(membersForAll, tdb->parent)) { membership = subgroupMembershipGet(tdb); sortOrder = sortOrderGet(NULL, tdb->parent); if (membership == NULL) { - errAbort("missing 'subgroups' setting for subtrack %s", subtrackName); + errAbort("missing 'subgroups' setting for subtrack %s. Add a 'subGroups' line declaring this subtrack's group membership, e.g. 'subGroups view=signal'", subtrackName); } // if a sortOrder is defined, make sure every subtrack has that membership if (sortOrder) { for (i = 0; i < sortOrder->count; i++) { char *col = sortOrder->column[i]; if ( (!sameString(col, SUBTRACK_COLOR_SUBGROUP)) && (membership == NULL || stringArrayIx(col, membership->subgroups, membership->count) == -1)) warn("%s not a member of sortOrder subgroup %s", subtrackName, col); } } // now check that this subtrack is a member of every defined subgroup for (i = 0; i < ArraySize(membersForAll->members); i++) @@ -689,31 +689,31 @@ { char *subgroupName = membersForAll->members[i]->groupTag; if (stringArrayIx(subgroupName, membership->subgroups, membership->count) == -1) { warn("subtrack %s not a member of subgroup %s", subtrackName, subgroupName); } } } // check that the subtrack does not have any bogus subgroups that don't exist in the parent for (i = 0; i < membership->count; i++) { char *subgroupName = membership->subgroups[i]; if (!subgroupingExists(tdb->parent, subgroupName)) { - warn("subtrack \"%s\" has a subgroup \"%s\" not defined at parent level", subtrackName, subgroupName); + warn("subtrack \"%s\" declares subgroup \"%s\", but the parent composite does not define this subgroup. Check the 'subGroup' lines in the parent stanza for a matching group name.", subtrackName, subgroupName); } } } } errCatchEnd(errCatch); if (errCatch->gotError || errCatch->gotWarning) { retVal = 1; trackDbErr(errors, errCatch->message->string, genome, tdb, options->htmlOut); } errCatchFree(&errCatch); return retVal; } @@ -757,32 +757,34 @@ void hubCheckParentsAndChildren(struct trackDb *tdb) /* Check that a single trackDb stanza has the correct parent and subtrack pointers */ { if (tdbIsSuper(tdb) || tdbIsComposite(tdb) || tdbIsCompositeView(tdb) || tdbIsContainer(tdb)) { if (tdb->subtracks == NULL) { errAbort("Track \"%s\" is declared superTrack, compositeTrack, view or " "container, but has no subtracks", tdb->track); } // Containers should not have a bigDataUrl setting if (trackDbLocalSetting(tdb, "bigDataUrl")) { - errAbort("Track \"%s\" is declared superTrack, compositeTrack, view or " - "container, and also has a bigDataUrl", tdb->track); + errAbort("Track \"%s\" is a container (compositeTrack/superTrack/view) but also has " + "a 'bigDataUrl'. Container tracks organize subtracks and should not have data files. " + "Remove 'bigDataUrl' from this stanza, or remove the container declaration if this is " + "a data track.", tdb->track); } // multiWigs cannot be the child of a composite if (tdbIsContainer(tdb) && (tdb->parent != NULL && (tdbIsComposite(tdb->parent) || tdbIsCompositeView(tdb->parent)))) { errAbort("Track \"%s\" is declared container multiWig and has parent \"%s\"." " Container multiWig tracks cannot be children of composites or views", tdb->track, tdb->parent->track); } } else if (tdb->subtracks != NULL) { errAbort("Track \"%s\" has children tracks (e.g: \"%s\"), but is not a " @@ -804,31 +806,31 @@ if (!isParentTrack && // these are the valid trackDb types for hub data tracks: !(sameString("bigNarrowPeak", trackType) || sameString("bigBed", trackType) || sameString("bigGenePred", trackType) || sameString("bigPsl", trackType)|| sameString("bigChain", trackType)|| sameString("bigMaf", trackType) || sameString("bigBarChart", trackType) || sameString("bigInteract", trackType) || sameString("bigLolly", trackType) || sameString("bigRmsk", trackType) || sameString("bigWig", trackType) || sameString("longTabix", trackType) || sameString("vcfTabix", trackType) || sameString("vcfPhasedTrio", trackType) || sameString("bam", trackType) || sameString("hic", trackType) #ifdef USE_HAL || sameString("halSnake", trackType) #endif )) { - errAbort("error in type line \"%s\" for track \"%s\". The only valid types for tracks that are not composites, views or supertracks are: bigWig, bigBed and bigBed variants like bigGenePred/bigChain/bigBarChart/etc, longTabix, vcfTabix, vcfPhasedTrio, bam, hic and halSnake.", trackType, tdb->track); + errAbort("error: unrecognized type \"%s\" for track \"%s\". Valid types include: bigWig, bigBed, bigGenePred, bigPsl, bigChain, bigMaf, bigBarChart, bigInteract, vcfTabix, bam, hic, and longTabix. See https://genome.ucsc.edu/goldenPath/help/trackDb/trackDbHub.html#type for the full list.", trackType, tdb->track); } if (sameString(splitType[0], "bigBed")) { if (numWords > 1 && (strchr(splitType[1], '+') || strchr(splitType[1], '.'))) { errAbort("error in type line \"%s\" for track \"%s\". " "A space is needed after the \"+\" or \".\" character.", type, tdb->track); } if (numWords > 2 && (!sameString(splitType[2], "+") && !sameString(splitType[2], "."))) { errAbort("error in type line \"%s\" for track \"%s\". " "Only \"+\" or \".\" is allowed after bigBed numFields setting.", type, tdb->track); } } @@ -875,31 +877,31 @@ char *lower = words[0]; double lowerVal = 0; boolean validLower = isValidDouble(lower, &lowerVal); if (!validLower) errAbort("Invalid double range lower value '%s' in range '%s' in setting %s track %s", lower, setting, settingName, tdb->track); char *upper = words[1]; double upperVal = 0; boolean validUpper = isValidDouble(upper, &upperVal); if (!validUpper) errAbort("Invalid double range upper value '%s' in range '%s' in setting %s track %s", upper, setting, settingName, tdb->track); if (validUpper && validLower) { if (upperVal < lowerVal) - warn("upper < lower. Should swap lower and upper range values in '%s' in setting %s track %s", setting, settingName, tdb->track); + warn("in track \"%s\", setting '%s' has the range '%s' where the upper bound is less than the lower bound. Swap the values so the format is lower:upper (e.g. 'viewLimits 0:100').", tdb->track, settingName, setting); } } else errAbort("Missing a colon in range value '%s' in setting %s track %s", setting, settingName, tdb->track); } void checkViewLimitsSettings(struct trackDb *tdb) /* check viewLimits and viewLimitsMax and defaultViewLimits setting values */ { int i; for(i = 0; i < 3; i++) { char *settingName; if (i == 0) settingName = VIEWLIMITS; @@ -1000,36 +1002,34 @@ checkViewLimitsSettings(tdb); if (!sameString(tdb->track, "cytoBandIdeo")) { trackHubAddDescription(genome->trackDbFile, tdb); if (!tdb->html) warn("warning: missing description page for track. Add 'html %s.html' line to the '%s' track stanza. ", tdb->track, tdb->track); } if (!trackIsContainer && sameString(trackDbRequiredSetting(tdb, "type"), "bigWig")) { char *autoScaleSetting = trackDbLocalSetting(tdb, "autoScale"); if (autoScaleSetting && !sameString(autoScaleSetting, "off") && !sameString(autoScaleSetting, "on")) { - errAbort("track \"%s\" has value \"%s\" for autoScale setting, " - "valid autoScale values for individual bigWig tracks are \"off\" or \"on\" only. " - "If \"%s\" is part of a bigWig composite track and you want to use the " - "\"%s\" setting, only declare \"autoScale group\" in the parent stanza", - trackHubSkipHubName(tdb->track), autoScaleSetting, trackHubSkipHubName(tdb->track), - autoScaleSetting); + errAbort("track \"%s\" uses 'autoScale %s', but individual bigWig tracks only accept " + "'autoScale on' or 'autoScale off'. To use 'autoScale %s', move that setting to the " + "parent composite stanza instead.", + trackHubSkipHubName(tdb->track), autoScaleSetting, autoScaleSetting); } } } errCatchEnd(errCatch); if (errCatch->gotError || errCatch->gotWarning) { retVal = 1; trackDbErr(errors, errCatch->message->string, genome, tdb, options->htmlOut); if (errCatch->gotError) trackDbErrorCount += 1; } errCatchFree(&errCatch); if (options->htmlOut) { @@ -1171,33 +1171,33 @@ /* Check a track data hub for integrity. Put errors in dyString. * return 0 if hub has no errors, 1 otherwise * if options->checkTracks is TRUE, check remote files of individual tracks */ { struct errCatch *errCatch = errCatchNew(); struct trackHub *hub = NULL; struct dyString *hubErrors = dyStringNew(0); int retVal = 0; if (errCatchStart(errCatch)) { hub = trackHubOpen(hubUrl, ""); char *descUrl = hub->descriptionUrl; if (descUrl == NULL) - warn("warning: missing hub overview description page (descriptionUrl setting)"); + warn("warning: missing hub overview description page. Add a 'descriptionUrl hubDescription.html' line to hub.txt."); else if (!extFileExists(descUrl)) - warn("warning: %s descriptionUrl setting does not exist", hub->descriptionUrl); + warn("warning: descriptionUrl '%s' could not be accessed. Verify the file exists at the specified path and is publicly readable.", hub->descriptionUrl); } errCatchEnd(errCatch); if (errCatch->gotError || errCatch->gotWarning) { retVal = 1; hubErr(hubErrors, errCatch->message->string, hub, options->htmlOut); if (options->htmlOut) { if (hub && hub->shortLabel) { dyStringPrintf(errors, "trackData['#'] = [%s,", makeFolderObjectString(hub->shortLabel, "Hub Errors", "#", "Click to open node", TRUE, TRUE)); }