c2a6ef817930149ad6f55818fa0196d99c47e02b
braney
  Fri Sep 11 10:40:11 2026 -0700
cheapcgi: skip a CGI pair with no =value instead of aborting, refs #38335

Both query string parsers looked for the '=' across the whole rest of the
string rather than inside the current pair.  A pair with no '=' in it
therefore ran into the pair after it and took its value.  "g-catV2&db=hg38"
was stored as one variable named "g-catV2&db", so db was lost with no
warning, and that corrupt name was copied on into the cart.  The same pair
at the end of the string had no '=' left to find and aborted the whole
request, which is what the "Mangled CGI input string g-catV2" entries in the
hgw1 logs were.

Both parsers now find the end of the pair first, keeping the existing
separator precedence ('&', then ';' for DAS), and skip a pair with no '='.
A mixed "a=1;b=2&c=3" still parses the way it did.

Adds lib/tests/cgiParseTest, which runs 18 query strings through both
parsers.  It covers the empty pair of #38185 as well.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

diff --git src/lib/tests/makefile src/lib/tests/makefile
index cfa34defdc0..2863fea0cf7 100644
--- src/lib/tests/makefile
+++ src/lib/tests/makefile
@@ -1,446 +1,453 @@
 kentSrc = ../..
 include ../../inc/common.mk
 
 MYLIBDIR = ../../lib/${MACHTYPE}
 MYLIBS = ${MYLIBDIR}/jkweb.a
 BIN_DIR = bin/${MACHTYPE}
 
 pipelineTester = ${BIN_DIR}/pipelineTester
 
 test: errCatchTest htmlPageTest htmlExpandUrlTest htmlSanitizeTest pipelineTests dyStringTest \
     mimeTests base64Tests quotedPTests safeTest hashTest fetchUrlTest gff3Test \
     tabixTest vcfTest hacTreeTest mmHashTest testSumDoubles jsonQueryTest \
-    dnaCodonTest pathSimplifyTest faSpeedReadTest
+    dnaCodonTest pathSimplifyTest faSpeedReadTest cgiParseTest
 	rm -r output fetchUrlTest testSumDoubles
 	@echo tested all
 
 
 mkdirs:
 	${MKDIR} output ${BIN_DIR}
 
 testSumDoubles: testSumDoubles.o ${MYLIBS}
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ./testSumDoubles testSumDoubles.o ${MYLIBS} ${L}
 
 pathSimplifyTest: pathSimplifyTest.o ${MYLIBS} mkdirs
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/pathSimplifyTest pathSimplifyTest.o ${MYLIBS} ${L}
 	${STRIP} ${BIN_DIR}/pathSimplifyTest${EXE}
 	${BIN_DIR}/pathSimplifyTest > output/pathSimplifyTest
 	diff expected/pathSimplifyTest output/pathSimplifyTest
 
 faSpeedReadTest: faSpeedReadTest.o ${MYLIBS} mkdirs
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/faSpeedReadTest faSpeedReadTest.o ${MYLIBS} ${L}
 	${STRIP} ${BIN_DIR}/faSpeedReadTest${EXE}
 	${BIN_DIR}/faSpeedReadTest > output/faSpeedReadTest
 	diff expected/faSpeedReadTest output/faSpeedReadTest
 
 dnaCodonTest: dnaCodonTest.o ${MYLIBS} mkdirs
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/dnaCodonTest dnaCodonTest.o ${MYLIBS} ${L}
 	${STRIP} ${BIN_DIR}/dnaCodonTest${EXE}
 	${BIN_DIR}/dnaCodonTest > output/dnaCodonTest
 	diff expected/dnaCodonTest output/dnaCodonTest
 
+cgiParseTest: cgiParseTest.o ${MYLIBS} mkdirs
+	@${MKDIR} $(dir $@)
+	${CC} ${COPT} -o ${BIN_DIR}/cgiParseTest cgiParseTest.o ${MYLIBS} ${L}
+	${STRIP} ${BIN_DIR}/cgiParseTest${EXE}
+	${BIN_DIR}/cgiParseTest > output/cgiParseTest
+	diff expected/cgiParseTest output/cgiParseTest
+
 htmlSanitizeTest: htmlSanitizeTest.o ${MYLIBS} mkdirs
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/htmlSanitizeTest htmlSanitizeTest.o ${MYLIBS} ${L}
 	${STRIP} ${BIN_DIR}/htmlSanitizeTest${EXE}
 	${BIN_DIR}/htmlSanitizeTest > output/htmlSanitizeTest
 	diff expected/htmlSanitizeTest output/htmlSanitizeTest
 
 errCatchTest: errCatchTest.o ${MYLIBS} mkdirs
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/errCatchTest errCatchTest.o ${MYLIBS} ${L}
 	${STRIP} ${BIN_DIR}/errCatchTest${EXE}
 	${BIN_DIR}/errCatchTest secret > output/errCatch.good
 	diff expected/errCatch.good output/errCatch.good
 	${BIN_DIR}/errCatchTest bad > output/errCatch.bad
 	diff expected/errCatch.bad output/errCatch.bad
 
 htmlExpandUrlTest: htmlExpandUrlTest.o ${MYLIBS} mkdirs
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/htmlExpandUrlTest htmlExpandUrlTest.o ${MYLIBS} ${L}
 	${STRIP} ${BIN_DIR}/htmlExpandUrlTest${EXE}
 	${BIN_DIR}/htmlExpandUrlTest > output/htmlExpandUrlTest 2>&1
 	diff expected/htmlExpandUrlTest output/htmlExpandUrlTest
 
 htmlPageTest: htmlPageTest.o ${MYLIBS} mkdirs
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/htmlPageTest htmlPageTest.o ${MYLIBS} ${L}
 	${STRIP} ${BIN_DIR}/htmlPageTest${EXE}
 	${BIN_DIR}/htmlPageTest input/google.html > output/google.out
 	diff expected/google.out output/google.out
 
 pipelineTests: 	pipelineWrite pipelineWriteMult pipelineWriteFd \
 		pipelineRead pipelineReadMult pipelineReadFd pipelineReadMem \
 		pipelineExitCode pipelineExitCode2X pipelineWriteErr pipelineExecError pipelineSigpipe \
 		pipelineTimeout
 
 pipelineWrite: ${pipelineTester} mkdirs
 	${pipelineTester} -write -pipeData=input/simple1.txt -otherEnd=output/$@.out.gz "gzip -1"
 	gunzip -c output/$@.out.gz > output/$@.out
 	diff -b input/simple1.txt output/$@.out
 
 # add come junk to make sure output gets truncated
 pipelineWriteMult: ${pipelineTester} mkdirs
 	cat input/google.html > output/$@.wc
 	${pipelineTester} -write -pipeData=input/simple1.txt -otherEnd=output/$@.wc "gzip -1" "gzip -dc" "wc"
 	diff -b expected/simple1.wc output/$@.wc
 
 pipelineWriteFd: ${pipelineTester} mkdirs
 	${pipelineTester} -fdApi -write -pipeData=input/simple1.txt -otherEnd=output/$@.out.gz "gzip -1"
 	gunzip -c output/$@.out.gz > output/$@.out
 	diff -b input/simple1.txt output/$@.out
 
 pipelineRead: ${pipelineTester} mkdirs
 	gzip -1c input/simple1.txt >output/$@.in.gz
 	${pipelineTester} -otherEnd=output/$@.in.gz -pipeData=output/$@.out "gzip -dc"
 	diff -b input/simple1.txt output/$@.out
 
 pipelineReadMult: ${pipelineTester} mkdirs
 	${pipelineTester} -pipeData=output/$@.wc -otherEnd=input/simple1.txt "gzip -1" "gzip -dc" "wc"
 	diff -b expected/simple1.wc output/$@.wc
 
 pipelineReadFd: ${pipelineTester} mkdirs
 	gzip -1c input/simple1.txt >output/$@.in.gz
 	${pipelineTester} -fdApi -otherEnd=output/$@.in.gz -pipeData=output/$@.out "gzip -dc"
 	diff -b input/simple1.txt output/$@.out
 
 pipelineReadMem: ${pipelineTester} mkdirs
 	gzip -1c input/simple1.txt >output/$@.in.gz
 	${pipelineTester} -memApi -otherEnd=output/$@.in.gz -pipeData=output/$@.out "gzip -dc"
 	diff -b input/simple1.txt output/$@.out
 
 # make sure pipe exit code makes it back
 pipelineExitCode: ${pipelineTester}
 	${pipelineTester} -exitCode=13 "sh -c 'exit 13'"
 
 # this failed when test was run twise in same process
 pipelineExitCode2X: ${pipelineTester}
 	${pipelineTester} -executeTwice -exitCode=13 "sh -c 'exit 13'"
 
 # test redirecting stderr, see that two process can write stderr, but only
 # the second's stdout should make it to the end of the pipe.  Since order
 # of writes to stderr is determined by process scheduling and when a process
 # terminates due to SIGPIPE, just check that stderr was not empty, don't
 # check contents.
 pipelineWriteErr: ${pipelineTester} mkdirs
 	${pipelineTester} -write -otherEnd=output/$@.out -stderr=output/$@.err "sh -c 'echo OUT; echo ERR >&2'"  "sh -c 'echo OUT2; echo ERR2 >&2'"
 	diff -b expected/$@.out output/$@.out
 	test -s output/$@.err
 
 # exec a non-existent program
 pipelineExecError: ${pipelineTester} mkdirs
 	if ${pipelineTester} -write -stderr=output/$@.err "./thatDoesNotCompute" 2> output/$@.parent.err ; then false else true ; fi
 	diff -b expected/$@.err output/$@.err
 	diff -b expected/$@.parent.err output/$@.parent.err
 
 # test setting SIGPIPE by generating lots of output and then prematurely closing the
 # pipe
 pipelineSigpipe: ${pipelineTester} mkdirs
 	${pipelineTester} -sigpipe -maxNumLines=3 -pipeData=/dev/null "awk 'BEGIN {while (1) {print "foo"}}'"
 
 pipelineTimeout: ${pipelineTester} mkdirs
 	if ${pipelineTester} -timeout=2 "bash -c 'sleep 20'" 2> output/$@.parent.err ; then false else true ; fi
 	diff -b expected/$@.parent.err output/$@.parent.err
 
 ${pipelineTester}: pipelineTester.o ${MYLIBS}
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${pipelineTester} pipelineTester.o ${MYLIBS} ${L}
 
 
 dyStringTest: ${BIN_DIR}/dyStringTester mkdirs
 	${BIN_DIR}/dyStringTester
 
 ${BIN_DIR}/dyStringTester:  mkdirs dyStringTester.o ${MYLIBS}
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/dyStringTester dyStringTester.o ${MYLIBS} ${L}
 
 
 mimeTests: mime1 mime2 mime3 mime4 mimeBin mime5 mimeAltHead mimeAutoBoundary mimeBlat
 
 ${BIN_DIR}/mimeTester:  mkdirs mimeTester.o ${MYLIBS}
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/mimeTester mimeTester.o ${MYLIBS} ${L}
 
 
 mime1: ${BIN_DIR}/mimeTester mkdirs
 	${BIN_DIR}/mimeTester < input/$@.txt > output/$@.out
 	diff expected/$@.out output/$@.out
 
 mime2: ${BIN_DIR}/mimeTester mkdirs
 	${BIN_DIR}/mimeTester < input/$@.txt > output/$@.out
 	diff expected/$@.out output/$@.out
 
 mime3: ${BIN_DIR}/mimeTester mkdirs
 	${BIN_DIR}/mimeTester < input/$@.txt > output/$@.out
 	diff expected/$@.out output/$@.out
 
 mime4: ${BIN_DIR}/mimeTester mkdirs
 	${BIN_DIR}/mimeTester < input/$@.txt > output/$@.out
 	diff expected/$@.out output/$@.out
 
 mimeBin: ${BIN_DIR}/mimeTester mkdirs
 	${BIN_DIR}/mimeTester < input/$@.txt > output/$@.out
 	diff expected/$@.out output/$@.out
 
 mime5: ${BIN_DIR}/mimeTester mkdirs
 	${BIN_DIR}/mimeTester < input/$@.txt > output/$@.out
 	diff expected/$@.out output/$@.out
 
 mimeAltHead: ${BIN_DIR}/mimeTester mkdirs
 	${BIN_DIR}/mimeTester -altHeader='CONTENT_TYPE=multipart/form-data; boundary=----------0xKhTmLbOuNdArY' < input/$@.txt > output/$@.out
 	diff expected/$@.out output/$@.out
 
 mimeAutoBoundary: ${BIN_DIR}/mimeTester mkdirs
 	${BIN_DIR}/mimeTester -autoBoundary < input/$@.txt > output/$@.out
 	diff expected/$@.out output/$@.out
 
 mimeBlat: ${BIN_DIR}/mimeTester mkdirs
 	${BIN_DIR}/mimeTester -altHeader='CONTENT_TYPE=multipart/form-data; boundary=----------0xKhTmLbOuNdArY' < input/$@.txt > output/$@.out
 	diff expected/$@.out output/$@.out
 
 mimeSeries: ${BIN_DIR}/mimeTester mkdirs
 	${BIN_DIR}/mimeTester -sizeSeries=3000
 
 ${BIN_DIR}/htmlMimeTest:  mkdirs htmlMimeTest.o ${MYLIBS}
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/htmlMimeTest htmlMimeTest.o ${MYLIBS} ${L}
 
 htmlMime1: ${BIN_DIR}/htmlMimeTest mkdirs
 	${BIN_DIR}/htmlMimeTest https://hgwdev.gi.ucsc.edu/cgi-bin/hgBlat input/htmlMime.txt 3490 3502 > output/$@.out
 	diff expected/$@.out output/$@.out
 
 
 base64Tests: base64Encode base64Decode
 
 ${BIN_DIR}/testBase64:  mkdirs testBase64.o ${MYLIBS}
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/testBase64 testBase64.o ${MYLIBS} ${L}
 
 base64Encode: ${BIN_DIR}/testBase64 mkdirs
 	${BIN_DIR}/testBase64 'My Test String' > output/$@.out
 	diff expected/$@.out output/$@.out
 
 base64Decode: ${BIN_DIR}/testBase64 mkdirs
 	${BIN_DIR}/testBase64 'TXkgVGVzdCBTdHJpbmc=' > output/$@.out
 	diff expected/$@.out output/$@.out
 
 
 
 quotedPTests: quotedPEncode quotedPDecode
 
 ${BIN_DIR}/testQuotedP:  mkdirs testQuotedP.o ${MYLIBS}
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/testQuotedP testQuotedP.o ${MYLIBS} ${L}
 
 quotedPEncode: ${BIN_DIR}/testQuotedP mkdirs
 	${BIN_DIR}/testQuotedP 'taxes are quite high ' > output/$@.out
 	diff expected/$@.out output/$@.out
 
 quotedPDecode: ${BIN_DIR}/testQuotedP mkdirs
 	${BIN_DIR}/testQuotedP 'taxes=20are=20quite=20high=20=' > output/$@.out
 	diff expected/$@.out output/$@.out
 
 ${BIN_DIR}/mimeDecodeTest:  mkdirs mimeDecodeTest.o ${MYLIBS}
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/mimeDecodeTest mimeDecodeTest.o ${MYLIBS} ${L}
 
 mimeDecodeTest: ${BIN_DIR}/mimeDecodeTest mkdirs
 	${BIN_DIR}/mimeDecodeTest -cid -autoBoundary output < input/$@.txt
 	diff expected/noName1.html output/noName1.html
 
 ${BIN_DIR}/safeTester:  mkdirs safeTester.o ${MYLIBS}
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/safeTester safeTester.o ${MYLIBS} ${L}
 
 safeTest: ${BIN_DIR}/safeTester mkdirs
 	${BIN_DIR}/safeTester
 
 hashTest: hashTest1
 
 ${BIN_DIR}/testHash:  mkdirs testHash.o ${MYLIBS}
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/testHash testHash.o ${MYLIBS} ${L}
 
 hashTest1: ${BIN_DIR}/testHash mkdirs
 	${BIN_DIR}/testHash input/$@.txt > output/$@.out
 	diff expected/$@.out output/$@.out
 
 ${BIN_DIR}/testQuotedString: mkdirs testQuotedString.o ${MYLIBS}
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/testQuotedString testQuotedString.o ${MYLIBS} ${L}
 
 testQuotedString:	${BIN_DIR}/testQuotedString mkdirs
 	${BIN_DIR}/testQuotedString -verbose=2 quote this\\ following
 
 miniBlat:  mkdirs miniBlat.o ${MYLIBS}
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o miniBlat miniBlat.o ${MYLIBS} ${L}
 
 fetchUrlTest:  mkdirs fetchUrlTest.o ${MYLIBS}
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o fetchUrlTest fetchUrlTest.o ${MYLIBS} ${L}
 
 fetchUrlViaUdcTest:  mkdirs fetchUrlViaUdcTest.o ${MYLIBS}
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o fetchUrlViaUdcTest fetchUrlViaUdcTest.o ${MYLIBS} ${L}
 
 ##
 # gff3 tests
 ##
 gff3Tester=${BIN_DIR}/gff3Tester
 gff3Test: gff3SacCerTest gff3ErrorCasesTest gff3DiscontiousTest
 
 # FIXME: doesn't work yet
 # gff3SpecialCasesTest
 
 gff3SacCerTest: ${gff3Tester} mkdirs
 	${gff3Tester} input/sacCerTest.gff3 output/$@.out
 	diff expected/$@.out output/$@.out
 gff3SpecialCasesTest: ${gff3Tester} mkdirs
 	${gff3Tester} input/specialCasesTest.gff3 output/$@.out
 	diff expected/$@.out output/$@.out
 gff3ErrorCasesTest: ${gff3Tester} mkdirs
 	if ${gff3Tester} input/errorCasesTest.gff3 /dev/null >output/$@.err 2>&1 ; then exit 0 else exit 1; fi
 	diff expected/$@.err output/$@.err
 gff3DiscontiousTest: ${gff3Tester} mkdirs
 	${gff3Tester} input/discontinuous.gff3 output/$@.out
 	diff expected/$@.out output/$@.out
 
 ${BIN_DIR}/gff3Tester: gff3Tester.o ${MYLIBS}
 	${MKDIR} ${BIN_DIR}
 	${CC} ${COPT} -o ${BIN_DIR}/gff3Tester gff3Tester.o ${MYLIBS} ${L}
 
 
 # lineFile's tabix support:
 tabixTester=${BIN_DIR}/tabixFetch
 tabixTest: tabixFetch1kGNoGenotypes tabixFetch1kGWithGenotypes
 
 tabixFetch1kGNoGenotypes: ${tabixTester} mkdirs
 	${tabixTester} input/YRI.trio.2010_06.novelsequences.sites.vcf.gz 2:26790860-194631353 > output/$@.out
 	diff expected/$@.out output/$@.out
 
 tabixFetch1kGWithGenotypes: ${tabixTester} mkdirs
 	${tabixTester} input/YRI.low_coverage.2010_07_excerpt.genotypes.vcf.gz 2:26793738-26794385 > output/$@.out
 	diff expected/$@.out output/$@.out
 
 ${BIN_DIR}/tabixFetch: tabixFetch.o ${MYLIBS}
 	${MKDIR} ${BIN_DIR}
 	${CC} ${COPT} -o ${BIN_DIR}/tabixFetch tabixFetch.o ${MYLIBS} ${L}
 
 
 # vcf:
 vcfTester=${BIN_DIR}/vcfParseTest
 vcfTest: vcfParse1kGNoGenotypes vcfParse1kGWithGenotypes vcfParseOldV3 \
 	vcfHeader1kGNoGenotypes vcfHeader1kGWithGenotypes vcfHeaderOldV3 \
 	vcfParseBadGenotypeIx vcfParseManyAlleles
 
 vcfParse1kGNoGenotypes: ${vcfTester} mkdirs
 	${vcfTester} input/YRI.trio.2010_06.novelsequences.sites.vcf.gz 2 26790859 194631353 > output/$@.out
 	diff expected/$@.out output/$@.out
 
 vcfParse1kGWithGenotypes: ${vcfTester} mkdirs
 	${vcfTester} input/YRI.low_coverage.2010_07_excerpt.genotypes.vcf.gz 2 26793737 26794385 > output/$@.out
 	diff expected/$@.out output/$@.out
 
 vcfParseOldV3: ${vcfTester} mkdirs
 	${vcfTester} input/20091110_pilot1_vcf_merged_call_sets_YRI.2and3_way.vcf.gz 1 3000 50000 >& output/$@.out
 	diff expected/$@.out output/$@.out
 
 # Regression tests for the tabix header-read path (htslib >= 1.21 tbx_readrec
 # strips meta_char lines, so the VCF header must be read off the htsFile
 # directly, not via the tabix iterator).  These check version, def counts, and
 # sample IDs -- all of which silently degrade if the header parser sees nothing.
 vcfHeader1kGNoGenotypes: ${vcfTester} mkdirs
 	${vcfTester} -headerOnly input/YRI.trio.2010_06.novelsequences.sites.vcf.gz > output/$@.out
 	diff expected/$@.out output/$@.out
 
 vcfHeader1kGWithGenotypes: ${vcfTester} mkdirs
 	${vcfTester} -headerOnly input/YRI.low_coverage.2010_07_excerpt.genotypes.vcf.gz > output/$@.out
 	diff expected/$@.out output/$@.out
 
 vcfHeaderOldV3: ${vcfTester} mkdirs
 	${vcfTester} -headerOnly input/20091110_pilot1_vcf_merged_call_sets_YRI.2and3_way.vcf.gz > output/$@.out
 	diff expected/$@.out output/$@.out
 
 # A GT allele index that this record has no allele for must parse as missing data, so that
 # every caller sees either a real allele or missing data.
 vcfParseBadGenotypeIx: ${vcfTester} mkdirs
 	${vcfTester} -genotypes input/badGenotypeIx.vcf.gz chr1 0 10000 > output/$@.out
 	diff expected/$@.out output/$@.out
 
 # A record can have more alleles than fit in the signed char that holds a genotype's allele
 # index.  An index too large for the field must also parse as missing data, instead of being
 # silently narrowed to some other value.  input/manyAlleles.vcf.gz has 260 ALT alleles, so
 # index 128 would narrow to a negative value and index 260 to 4, a real allele of that record.
 vcfParseManyAlleles: ${vcfTester} mkdirs
 	${vcfTester} -genotypes input/manyAlleles.vcf.gz chr1 0 10000 > output/$@.out
 	diff expected/$@.out output/$@.out
 
 ${BIN_DIR}/vcfParseTest: vcfParseTest.o ${MYLIBS}
 	${MKDIR} ${BIN_DIR}
 	${CC} ${COPT} -o ${BIN_DIR}/vcfParseTest vcfParseTest.o ${MYLIBS} ${L}
 
 
 # hacTree:
 hacTreeTester=${BIN_DIR}/hacTreeTest
 hacTreeTest: ${hacTreeTester} mkdirs
 	${hacTreeTester} input/$@.txt output/$@.out
 	diff expected/$@.out output/$@.out
 
 ${BIN_DIR}/hacTreeTest: hacTreeTest.o ${MYLIBS}
 	${MKDIR} ${BIN_DIR}
 	${CC} ${COPT} -o ${BIN_DIR}/hacTreeTest hacTreeTest.o ${MYLIBS} ${L}
 
 # mmHash:
 mmHashTester=${BIN_DIR}/mmHashTest
 mmHashTest: ${mmHashTester} mkdirs
 	${mmHashTester} input/$@.txt output/$@.mmh output/$@.out
 	diff expected/$@.out output/$@.out
 	cmp expected/$@.mmh output/$@.mmh
 
 ${BIN_DIR}/mmHashTest: mmHashTest.o ${MYLIBS}
 	${MKDIR} ${BIN_DIR}
 	${CC} ${COPT} -o ${BIN_DIR}/mmHashTest mmHashTest.o ${MYLIBS} ${L}
 
 # udc (not part of the top-level test target at this point):
 udcTest: udcTest.o ${MYLIBS} mkdirs
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/udcTest udcTest.o ${MYLIBS} ${L}
 	${BIN_DIR}/udcTest
 
 # udc (not part of the top-level test target at this point):
 udcCacheSizesCheck: udcCacheSizesCheck.o ${MYLIBS} mkdirs
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/udcCacheSizesCheck udcCacheSizesCheck.o ${MYLIBS} ${L}
 	${BIN_DIR}/udcCacheSizesCheck
 
 testOutOfMem: testOutOfMem.o ${MYLIBS} mkdirs
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/testOutOfMem testOutOfMem.o ${MYLIBS} ${L}
 	# we expect this to errAbort because we allocate one byte too much beyond limit
 	-${BIN_DIR}/testOutOfMem 100000 1
 
 clean:
 	rm -rf *.o bin output *.tmp mimeTester.tmp mimeTester.out fetchUrlTest fetchUrlViaUdcTest
 
 ${BIN_DIR}/testDecodedString: mkdirs testDecodedString.o ${MYLIBS}
 	@${MKDIR} $(dir $@)
 	${CC} ${COPT} -o ${BIN_DIR}/testDecodedString testDecodedString.o ${MYLIBS} ${L}
 
 testDecodedString:	${BIN_DIR}/testDecodedString mkdirs
 	${BIN_DIR}/testDecodedString -verbose=2 quote this\\ following
 
 # jsonQuery:
 jsonQueryTester=${BIN_DIR}/jsonQueryTest
 jsonQueryTest: ${jsonQueryTester} mkdirs
 	${jsonQueryTester} input/$@Json.txt input/$@Path.txt output/$@.out
 	diff expected/$@.out output/$@.out
 
 ${BIN_DIR}/jsonQueryTest: jsonQueryTest.o ${MYLIBS}
 	${MKDIR} ${BIN_DIR}
 	${CC} ${COPT} -o ${BIN_DIR}/jsonQueryTest jsonQueryTest.o ${MYLIBS} ${L}