summaryrefslogtreecommitdiff
path: root/devel/bmake/files/unit-tests
diff options
context:
space:
mode:
Diffstat (limited to 'devel/bmake/files/unit-tests')
-rw-r--r--devel/bmake/files/unit-tests/Makefile155
-rw-r--r--devel/bmake/files/unit-tests/Makefile.config.in4
-rw-r--r--devel/bmake/files/unit-tests/cond-late.exp3
-rw-r--r--devel/bmake/files/unit-tests/cond-late.mk23
-rw-r--r--devel/bmake/files/unit-tests/cond2.mk6
-rw-r--r--devel/bmake/files/unit-tests/dollar.exp51
-rw-r--r--devel/bmake/files/unit-tests/dollar.mk81
-rw-r--r--devel/bmake/files/unit-tests/doterror.exp2
-rw-r--r--devel/bmake/files/unit-tests/dotwait.exp4
-rw-r--r--devel/bmake/files/unit-tests/dotwait.mk4
-rw-r--r--devel/bmake/files/unit-tests/escape.mk6
-rw-r--r--devel/bmake/files/unit-tests/export-env.exp2
-rw-r--r--devel/bmake/files/unit-tests/export-env.mk9
-rw-r--r--devel/bmake/files/unit-tests/forloop.exp3
-rw-r--r--devel/bmake/files/unit-tests/forloop.mk12
-rw-r--r--devel/bmake/files/unit-tests/include-main.exp6
-rw-r--r--devel/bmake/files/unit-tests/include-main.mk30
-rw-r--r--devel/bmake/files/unit-tests/include-sub.mk17
-rw-r--r--devel/bmake/files/unit-tests/include-subsub.mk7
-rw-r--r--devel/bmake/files/unit-tests/modmatch.exp3
-rw-r--r--devel/bmake/files/unit-tests/modmatch.mk11
-rw-r--r--devel/bmake/files/unit-tests/modorder.mk7
-rw-r--r--devel/bmake/files/unit-tests/modts.exp10
-rw-r--r--devel/bmake/files/unit-tests/modts.mk7
-rw-r--r--devel/bmake/files/unit-tests/sysv.exp8
-rw-r--r--devel/bmake/files/unit-tests/sysv.mk21
-rw-r--r--devel/bmake/files/unit-tests/varcmd.exp2
-rw-r--r--devel/bmake/files/unit-tests/varcmd.mk15
-rw-r--r--devel/bmake/files/unit-tests/varmisc.exp23
-rw-r--r--devel/bmake/files/unit-tests/varmisc.mk58
-rw-r--r--devel/bmake/files/unit-tests/varmod-edge.exp17
-rw-r--r--devel/bmake/files/unit-tests/varmod-edge.mk162
-rw-r--r--devel/bmake/files/unit-tests/varquote.exp3
-rw-r--r--devel/bmake/files/unit-tests/varquote.mk14
34 files changed, 756 insertions, 30 deletions
diff --git a/devel/bmake/files/unit-tests/Makefile b/devel/bmake/files/unit-tests/Makefile
new file mode 100644
index 00000000000..4c91e6697c8
--- /dev/null
+++ b/devel/bmake/files/unit-tests/Makefile
@@ -0,0 +1,155 @@
+# $Id: Makefile,v 1.1.1.1 2020/05/24 05:35:53 nia Exp $
+#
+# $NetBSD: Makefile,v 1.1.1.1 2020/05/24 05:35:53 nia Exp $
+#
+# Unit tests for make(1)
+# The main targets are:
+#
+# all: run all the tests
+# test: run 'all', and compare to expected results
+# accept: move generated output to expected results
+#
+# Adding a test case.
+# Each feature should get its own set of tests in its own suitably
+# named makefile (*.mk), with its own set of expected results (*.exp),
+# and it should be added to the TESTNAMES list.
+#
+
+.MAIN: all
+
+.-include "Makefile.config"
+
+UNIT_TESTS:= ${.PARSEDIR}
+.PATH: ${UNIT_TESTS}
+
+# Each test is in a sub-makefile.
+# Keep the list sorted.
+TESTNAMES= \
+ comment \
+ cond-late \
+ cond1 \
+ cond2 \
+ dollar \
+ doterror \
+ dotwait \
+ error \
+ export \
+ export-all \
+ export-env \
+ forloop \
+ forsubst \
+ hash \
+ include-main \
+ misc \
+ moderrs \
+ modmatch \
+ modmisc \
+ modorder \
+ modts \
+ modword \
+ order \
+ posix \
+ qequals \
+ sunshcmd \
+ sysv \
+ ternary \
+ unexport \
+ unexport-env \
+ varcmd \
+ varmisc \
+ varmod-edge \
+ varquote \
+ varshell
+
+# these tests were broken by referting POSIX chanegs
+STRICT_POSIX_TESTS = \
+ escape \
+ impsrc \
+ phony-end \
+ posix1 \
+ suffixes
+
+# Override make flags for certain tests
+flags.doterror=
+flags.order=-j1
+
+OUTFILES= ${TESTNAMES:S/$/.out/}
+
+all: ${OUTFILES}
+
+CLEANFILES += *.rawout *.out *.status *.tmp *.core *.tmp
+CLEANFILES += obj*.[och] lib*.a # posix1.mk
+CLEANFILES += issue* .[ab]* # suffixes.mk
+CLEANRECURSIVE += dir dummy # posix1.mk
+
+clean:
+ rm -f ${CLEANFILES}
+.if !empty(CLEANRECURSIVE)
+ rm -rf ${CLEANRECURSIVE}
+.endif
+
+TEST_MAKE?= ${.MAKE}
+TOOL_SED?= sed
+TOOL_TR?= tr
+TOOL_DIFF?= diff
+
+.if defined(.PARSEDIR)
+# ensure consistent results from sort(1)
+LC_ALL= C
+LANG= C
+.export LANG LC_ALL
+.endif
+
+# some tests need extra post-processing
+SED_CMDS.varshell = -e 's,^[a-z]*sh: ,,' \
+ -e '/command/s,No such.*,not found,'
+
+# the tests are actually done with sub-makes.
+.SUFFIXES: .mk .rawout .out
+.mk.rawout:
+ @echo ${TEST_MAKE} ${flags.${.TARGET:R}:U-k} -f ${.IMPSRC}
+ -@cd ${.OBJDIR} && \
+ { ${TEST_MAKE} ${flags.${.TARGET:R}:U-k} -f ${.IMPSRC} \
+ 2>&1 ; echo $$? >${.TARGET:R}.status ; } > ${.TARGET}.tmp
+ @mv ${.TARGET}.tmp ${.TARGET}
+
+# We always pretend .MAKE was called 'make'
+# and strip ${.CURDIR}/ from the output
+# and replace anything after 'stopped in' with unit-tests
+# so the results can be compared.
+.rawout.out:
+ @echo postprocess ${.TARGET}
+ @${TOOL_SED} -e 's,^${TEST_MAKE:T:C/\./\\\./g}[][0-9]*:,make:,' \
+ -e 's,${TEST_MAKE:C/\./\\\./g},make,' \
+ -e '/stopped/s, /.*, unit-tests,' \
+ -e 's,${.CURDIR:C/\./\\\./g}/,,g' \
+ -e 's,${UNIT_TESTS:C/\./\\\./g}/,,g' ${SED_CMDS.${.TARGET:T:R}} \
+ < ${.IMPSRC} > ${.TARGET}.tmp
+ @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp
+ @mv ${.TARGET}.tmp ${.TARGET}
+
+# Compare all output files
+test: ${OUTFILES} .PHONY
+ @failed= ; \
+ for test in ${TESTNAMES}; do \
+ ${TOOL_DIFF} ${DIFF_FLAGS} ${UNIT_TESTS}/$${test}.exp $${test}.out \
+ || failed="$${failed}$${failed:+ }$${test}" ; \
+ done ; \
+ if [ -n "$${failed}" ]; then \
+ echo "Failed tests: $${failed}" ; false ; \
+ else \
+ echo "All tests passed" ; \
+ fi
+
+accept:
+ @for test in ${TESTNAMES}; do \
+ cmp -s ${UNIT_TESTS}/$${test}.exp $${test}.out \
+ || { echo "Replacing $${test}.exp" ; \
+ cp $${test}.out ${UNIT_TESTS}/$${test}.exp ; } \
+ done
+
+.if exists(${TEST_MAKE})
+${TESTNAMES:S/$/.rawout/}: ${TEST_MAKE}
+.endif
+
+.-include <obj.mk>
diff --git a/devel/bmake/files/unit-tests/Makefile.config.in b/devel/bmake/files/unit-tests/Makefile.config.in
new file mode 100644
index 00000000000..0993ab0a224
--- /dev/null
+++ b/devel/bmake/files/unit-tests/Makefile.config.in
@@ -0,0 +1,4 @@
+# $Id: Makefile.config.in,v 1.1.1.1 2020/05/24 05:35:53 nia Exp $
+
+srcdir= @srcdir@
+DIFF_FLAGS?= @diff_u@
diff --git a/devel/bmake/files/unit-tests/cond-late.exp b/devel/bmake/files/unit-tests/cond-late.exp
new file mode 100644
index 00000000000..3717b088c64
--- /dev/null
+++ b/devel/bmake/files/unit-tests/cond-late.exp
@@ -0,0 +1,3 @@
+yes
+no
+exit status 0
diff --git a/devel/bmake/files/unit-tests/cond-late.mk b/devel/bmake/files/unit-tests/cond-late.mk
new file mode 100644
index 00000000000..efc6f829133
--- /dev/null
+++ b/devel/bmake/files/unit-tests/cond-late.mk
@@ -0,0 +1,23 @@
+# $NetBSD: cond-late.mk,v 1.1.1.1 2020/05/24 05:35:53 nia Exp $
+#
+# Using the :? modifier, variable expressions can contain conditional
+# expressions that are evaluated late. Any variables appearing in these
+# conditions are expanded before parsing the condition. This is
+# different from many other places.
+#
+# Because of this, variables that are used in these lazy conditions
+# should not contain double-quotes, or the parser will probably fail.
+#
+# They should also not contain operators like == or <, since these are
+# actually interpreted as these operators. This is demonstrated below.
+#
+# If the order of evaluation were to change to first parse the condition
+# and then expand the variables, the output would change from the
+# current "yes no" to "yes yes", since both variables are non-empty.
+
+COND.true= "yes" == "yes"
+COND.false= "yes" != "yes"
+
+all:
+ @echo ${ ${COND.true} :?yes:no}
+ @echo ${ ${COND.false} :?yes:no}
diff --git a/devel/bmake/files/unit-tests/cond2.mk b/devel/bmake/files/unit-tests/cond2.mk
index e396fc308e7..1d6125af4a0 100644
--- a/devel/bmake/files/unit-tests/cond2.mk
+++ b/devel/bmake/files/unit-tests/cond2.mk
@@ -1,4 +1,4 @@
-# $Id: cond2.mk,v 1.1.1.1 2015/05/19 21:36:45 joerg Exp $
+# $Id: cond2.mk,v 1.1.1.2 2020/05/24 05:35:53 nia Exp $
TEST_UNAME_S= NetBSD
@@ -21,5 +21,9 @@ Y!= echo TEST_NOT_SET is empty or not defined >&2; echo
Y= oops
.endif
+.if defined(.NDEF) && ${.NDEF} > 0
+Z= yes
+.endif
+
all:
@echo $@
diff --git a/devel/bmake/files/unit-tests/dollar.exp b/devel/bmake/files/unit-tests/dollar.exp
new file mode 100644
index 00000000000..496adc02f15
--- /dev/null
+++ b/devel/bmake/files/unit-tests/dollar.exp
@@ -0,0 +1,51 @@
+
+Printing dollar from literals and variables
+
+To survive the parser, a dollar character must be doubled.
+ 1 dollar literal => <single-quote-var-value>
+ 1 dollar literal eol => <>
+ 2 dollar literal => <$>
+ 4 dollar literal => <$$>
+Some hungry part of make eats all the dollars after a :U modifier.
+ 1 dollar default => <>
+ 2 dollar default => <>
+ 4 dollar default => <>
+This works as expected.
+ 1 dollar variable => <>
+ 2 dollar variable => <$>
+ 4 dollar variable => <$$>
+Some hungry part of make eats all the dollars after a :U modifier.
+ 1 dollar var-default => <>
+ 2 dollar var-default => <$>
+ 4 dollar var-default => <$$>
+
+Dollar in :S pattern
+
+ S,$,word, => <$XYword>
+ S,$X,word, => <$XY>
+ S,$$X,word, => <$XY>
+ S,$$$X,word, => <$XY>
+ S,$X,replaced, => <replaced>
+ S,$$X,replaced, => <replaced>
+ S,$$$X,replaced, => <replaced>
+
+Dollar in :C character class
+
+The A is replaced because the $$ is reduced to a single $,
+which is then resolved to the variable X with the value VAR_X.
+The effective character class becomes [VAR_XY].
+ C,[$$XY],<&>,g => <$<A><X><Y>>
+
+Dollar in :C pattern
+
+For some reason, multiple dollars are folded into one.
+ C,$,dollar,g => <>
+ C,$$,dollar,g => <>
+
+Dollar in :S replacement
+
+For some reason, multiple dollars are folded into one.
+ S,word,a$Xo, => <aVAR_Xo>
+ S,word,a$$Xo, => <aVAR_Xo>
+ S,word,a$$$Xo, => <aVAR_Xo>
+exit status 0
diff --git a/devel/bmake/files/unit-tests/dollar.mk b/devel/bmake/files/unit-tests/dollar.mk
new file mode 100644
index 00000000000..faa8e622802
--- /dev/null
+++ b/devel/bmake/files/unit-tests/dollar.mk
@@ -0,0 +1,81 @@
+# $NetBSD: dollar.mk,v 1.1.1.1 2020/05/24 05:35:53 nia Exp $
+#
+# Test the various places where a dollar character can appear and
+# see what happens. There are lots of surprises here.
+#
+
+LIST= plain 'single' "double" 'mix'"ed" back\ slashed
+WORD= word
+
+DOLLAR1= $
+DOLLAR2= $$
+DOLLAR4= $$$$
+
+X= VAR_X
+DOLLAR_XY= $$XY
+DOLLAR_AXY= $$AXY
+
+H= @header() { printf '\n%s\n\n' "$$*"; }; header
+T= @testcase() { printf '%23s => <%s>\n' "$$@"; }; testcase
+C= @comment() { printf '%s\n' "$$*"; }; comment
+
+# These variable values are not accessed.
+# The trailing dollar in the '1 dollar literal eol' test case accesses
+# the empty variable instead, which is always guaranteed to be empty.
+${:U }= space-var-value
+${:U${.newline}}= newline-var-value
+# But this one is accessed.
+${:U'}= single-quote-var-value'
+
+all:
+ $H 'Printing dollar from literals and variables'
+
+ $C 'To survive the parser, a dollar character must be doubled.'
+ $T '1 dollar literal' '$'
+ $T '1 dollar literal eol' ''$
+ $T '2 dollar literal' '$$'
+ $T '4 dollar literal' '$$$$'
+
+ $C 'Some hungry part of make eats all the dollars after a :U modifier.'
+ $T '1 dollar default' ''${:U$:Q}
+ $T '2 dollar default' ''${:U$$:Q}
+ $T '4 dollar default' ''${:U$$$$:Q}
+
+ $C 'This works as expected.'
+ $T '1 dollar variable' ''${DOLLAR1:Q}
+ $T '2 dollar variable' ''${DOLLAR2:Q}
+ $T '4 dollar variable' ''${DOLLAR4:Q}
+
+ $C 'Some hungry part of make eats all the dollars after a :U modifier.'
+ $T '1 dollar var-default' ''${:U${DOLLAR1}:Q}
+ $T '2 dollar var-default' ''${:U${DOLLAR2}:Q}
+ $T '4 dollar var-default' ''${:U${DOLLAR4}:Q}
+
+ $H 'Dollar in :S pattern'
+
+ $T 'S,$$,word,' ''${DOLLAR_XY:S,$,word,:Q}
+ $T 'S,$$X,word,' ''${DOLLAR_XY:S,$X,word,:Q}
+ $T 'S,$$$$X,word,' ''${DOLLAR_XY:S,$$X,word,:Q}
+ $T 'S,$$$$$$X,word,' ''${DOLLAR_XY:S,$$$X,word,:Q}
+
+ $T 'S,$$X,replaced,' ''${X:S,$X,replaced,:Q}
+ $T 'S,$$$$X,replaced,' ''${X:S,$$X,replaced,:Q}
+ $T 'S,$$$$$$X,replaced,' ''${X:S,$$$X,replaced,:Q}
+
+ $H 'Dollar in :C character class'
+
+ $C 'The A is replaced because the $$$$ is reduced to a single $$,'
+ $C 'which is then resolved to the variable X with the value VAR_X.'
+ $C 'The effective character class becomes [VAR_XY].'
+ $T 'C,[$$$$XY],<&>,g' ''${DOLLAR_AXY:C,[$$XY],<&>,g:Q}
+
+ $H 'Dollar in :C pattern'
+ $C 'For some reason, multiple dollars are folded into one.'
+ $T 'C,$$,dollar,g' ''${DOLLAR:C,$,dollar,g:Q}
+ $T 'C,$$$$,dollar,g' ''${DOLLAR:C,$$,dollar,g:Q}
+
+ $H 'Dollar in :S replacement'
+ $C 'For some reason, multiple dollars are folded into one.'
+ $T 'S,word,a$$Xo,' ''${WORD:S,word,a$Xo,:Q}
+ $T 'S,word,a$$$$Xo,' ''${WORD:S,word,a$$Xo,:Q}
+ $T 'S,word,a$$$$$$Xo,' ''${WORD:S,word,a$$$Xo,:Q}
diff --git a/devel/bmake/files/unit-tests/doterror.exp b/devel/bmake/files/unit-tests/doterror.exp
index 0447a519344..5655644c32e 100644
--- a/devel/bmake/files/unit-tests/doterror.exp
+++ b/devel/bmake/files/unit-tests/doterror.exp
@@ -1,9 +1,9 @@
At first, I am
happy
and now: sad
-.ERROR: Looks like 'sad' is upset.
*** Error code 1
Stop.
make: stopped in unit-tests
+.ERROR: Looks like 'sad' is upset.
exit status 1
diff --git a/devel/bmake/files/unit-tests/dotwait.exp b/devel/bmake/files/unit-tests/dotwait.exp
index 6bf96e301c1..bdc0a0eb150 100644
--- a/devel/bmake/files/unit-tests/dotwait.exp
+++ b/devel/bmake/files/unit-tests/dotwait.exp
@@ -22,9 +22,9 @@ shared.2.1
shared.2.1
shared.2.99
shared.2.99
+cycle.1.99
+cycle.1.99
make: Graph cycles through `cycle.2.99'
make: Graph cycles through `cycle.2.98'
make: Graph cycles through `cycle.2.97'
-cycle.1.99
-cycle.1.99
exit status 0
diff --git a/devel/bmake/files/unit-tests/dotwait.mk b/devel/bmake/files/unit-tests/dotwait.mk
index b6cde7d31de..463d30625f8 100644
--- a/devel/bmake/files/unit-tests/dotwait.mk
+++ b/devel/bmake/files/unit-tests/dotwait.mk
@@ -1,4 +1,4 @@
-# $NetBSD: dotwait.mk,v 1.1.1.1 2015/05/19 21:36:45 joerg Exp $
+# $NetBSD: dotwait.mk,v 1.1.1.2 2020/05/24 05:35:53 nia Exp $
THISMAKEFILE:= ${.PARSEDIR}/${.PARSEFILE}
@@ -11,7 +11,7 @@ PAUSE= sleep 1
# Ignore "--- target ---" lines printed by parallel make.
all:
.for t in ${TESTS}
- @${.MAKE} -f ${THISMAKEFILE} -j4 $t | grep -v "^--- "
+ @${.MAKE} -f ${THISMAKEFILE} -j4 $t 2>&1 | grep -v "^--- "
.endfor
#
diff --git a/devel/bmake/files/unit-tests/escape.mk b/devel/bmake/files/unit-tests/escape.mk
index ede9d9e7254..a48ea29f8be 100644
--- a/devel/bmake/files/unit-tests/escape.mk
+++ b/devel/bmake/files/unit-tests/escape.mk
@@ -1,4 +1,4 @@
-# $Id: escape.mk,v 1.1.1.1 2015/05/19 21:36:45 joerg Exp $
+# $Id: escape.mk,v 1.1.1.2 2020/05/24 05:35:53 nia Exp $
#
# Test backslash escaping.
@@ -35,8 +35,8 @@
# Also, our practice is that an even number of backslashes before a
# newline in a variable assignment simply stores the backslashes as part
# of the value, and treats the newline as though it was not escaped.
-# Similarly, ann even number of backslashes before a newline in a
-# command simply uses the backslashes as part of the command test, but
+# Similarly, an even number of backslashes before a newline in a
+# command simply uses the backslashes as part of the command, but
# does not escape the newline. This is compatible with GNU make.
all: .PHONY
diff --git a/devel/bmake/files/unit-tests/export-env.exp b/devel/bmake/files/unit-tests/export-env.exp
index 6221232a2a1..8a779e6aff0 100644
--- a/devel/bmake/files/unit-tests/export-env.exp
+++ b/devel/bmake/files/unit-tests/export-env.exp
@@ -2,8 +2,10 @@ make:
UT_TEST=export-env.mk
UT_ENV=not-exported
UT_EXP=not-exported
+UT_LIT=literal export-env.mk
env:
UT_TEST=export-env.mk
UT_ENV=exported
UT_EXP=exported
+UT_LIT=literal ${UT_TEST}
exit status 0
diff --git a/devel/bmake/files/unit-tests/export-env.mk b/devel/bmake/files/unit-tests/export-env.mk
index 3832e2a2e27..aedcc7b38a3 100644
--- a/devel/bmake/files/unit-tests/export-env.mk
+++ b/devel/bmake/files/unit-tests/export-env.mk
@@ -1,4 +1,4 @@
-# $Id: export-env.mk,v 1.1.1.1 2015/05/19 21:36:45 joerg Exp $
+# $Id: export-env.mk,v 1.1.1.2 2020/05/24 05:35:53 nia Exp $
# our normal .export, subsequent changes affect the environment
UT_TEST=this
@@ -15,9 +15,12 @@ UT_EXP=before-export
export UT_EXP=exported
UT_EXP=not-exported
+UT_LIT= literal ${UT_TEST}
+.export-literal UT_LIT
+
all:
- @echo make:; ${UT_TEST UT_ENV UT_EXP:L:@v@echo $v=${$v};@}
- @echo env:; ${UT_TEST UT_ENV UT_EXP:L:@v@echo $v=$${$v};@}
+ @echo make:; ${UT_TEST UT_ENV UT_EXP UT_LIT:L:@v@echo $v=${$v};@}
+ @echo env:; ${UT_TEST UT_ENV UT_EXP UT_LIT:L:@v@echo $v=$${$v};@}
diff --git a/devel/bmake/files/unit-tests/forloop.exp b/devel/bmake/files/unit-tests/forloop.exp
index df14b751224..99845619073 100644
--- a/devel/bmake/files/unit-tests/forloop.exp
+++ b/devel/bmake/files/unit-tests/forloop.exp
@@ -7,12 +7,13 @@ x=-I"This or that"
x=-Ithat
x="-DTHIS=\"this and that\""
cfl=-I/this -I"This or that" -Ithat "-DTHIS=\"this and that\""
+newline-item=(a)
a=one b="two and three"
a=four b="five"
a=ONE b="TWO AND THREE"
a=FOUR b="FIVE"
We expect an error next:
-make: "forloop.mk" line 38: Wrong number of words (9) in .for substitution list with 2 vars
+make: "forloop.mk" line 46: Wrong number of words (9) in .for substitution list with 2 vars
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
OK
diff --git a/devel/bmake/files/unit-tests/forloop.mk b/devel/bmake/files/unit-tests/forloop.mk
index ae693438b94..985ccaf964b 100644
--- a/devel/bmake/files/unit-tests/forloop.mk
+++ b/devel/bmake/files/unit-tests/forloop.mk
@@ -1,4 +1,4 @@
-# $Id: forloop.mk,v 1.1.1.1 2015/05/19 21:36:45 joerg Exp $
+# $Id: forloop.mk,v 1.1.1.2 2020/05/24 05:35:53 nia Exp $
all: for-loop
@@ -33,7 +33,15 @@ X!= echo 'cfl=${cfl}' >&2; echo
.for a b in ${EMPTY}
X!= echo 'a=$a b=$b' >&2; echo
.endfor
-.endif
+
+# Since at least 1993, iteration stops at the first newline.
+# Back then, the .newline variable didn't exist, therefore it was unlikely
+# that a newline ever occured.
+.for var in a${.newline}b${.newline}c
+X!= echo 'newline-item=('${var:Q}')' 1>&2; echo
+.endfor
+
+.endif # for-fail
.for a b in ${LIST} ${LIST:tu} ${XTRA_LIST}
X!= echo 'a=$a b=$b' >&2; echo
diff --git a/devel/bmake/files/unit-tests/include-main.exp b/devel/bmake/files/unit-tests/include-main.exp
new file mode 100644
index 00000000000..7a55c6e97dc
--- /dev/null
+++ b/devel/bmake/files/unit-tests/include-main.exp
@@ -0,0 +1,6 @@
+main-before-ok
+sub-before-ok
+subsub-ok
+sub-after-fail(include-sub.mk)
+main-after-fail(include-sub.mk)
+exit status 0
diff --git a/devel/bmake/files/unit-tests/include-main.mk b/devel/bmake/files/unit-tests/include-main.mk
new file mode 100644
index 00000000000..cb333ef5211
--- /dev/null
+++ b/devel/bmake/files/unit-tests/include-main.mk
@@ -0,0 +1,30 @@
+# $NetBSD: include-main.mk,v 1.1.1.1 2020/05/24 05:35:53 nia Exp $
+#
+# Demonstrates that the .INCLUDEDFROMFILE magic variable does not behave
+# as described in the manual page.
+#
+# The manual page says that it is the "filename of the file this Makefile
+# was included from", while in reality it is the "filename in which the
+# latest .include happened".
+#
+
+.if !defined(.INCLUDEDFROMFILE)
+LOG+= main-before-ok
+.else
+. for f in ${.INCLUDEDFROMFILE}
+LOG+= main-before-fail\(${f:Q}\)
+. endfor
+.endif
+
+.include "include-sub.mk"
+
+.if !defined(.INCLUDEDFROMFILE)
+LOG+= main-after-ok
+.else
+. for f in ${.INCLUDEDFROMFILE}
+LOG+= main-after-fail\(${f:Q}\)
+. endfor
+.endif
+
+all:
+ @printf '%s\n' ${LOG}
diff --git a/devel/bmake/files/unit-tests/include-sub.mk b/devel/bmake/files/unit-tests/include-sub.mk
new file mode 100644
index 00000000000..2bed3465966
--- /dev/null
+++ b/devel/bmake/files/unit-tests/include-sub.mk
@@ -0,0 +1,17 @@
+# $NetBSD: include-sub.mk,v 1.1.1.1 2020/05/24 05:35:53 nia Exp $
+
+.if ${.INCLUDEDFROMFILE} == "include-main.mk"
+LOG+= sub-before-ok
+.else
+LOG+= sub-before-fail
+.endif
+
+.include "include-subsub.mk"
+
+.if ${.INCLUDEDFROMFILE} == "include-main.mk"
+LOG+= sub-after-ok
+.else
+. for f in ${.INCLUDEDFROMFILE}
+LOG+= sub-after-fail\(${f:Q}\)
+. endfor
+.endif
diff --git a/devel/bmake/files/unit-tests/include-subsub.mk b/devel/bmake/files/unit-tests/include-subsub.mk
new file mode 100644
index 00000000000..87d3e371366
--- /dev/null
+++ b/devel/bmake/files/unit-tests/include-subsub.mk
@@ -0,0 +1,7 @@
+# $NetBSD: include-subsub.mk,v 1.1.1.1 2020/05/24 05:35:53 nia Exp $
+
+.if ${.INCLUDEDFROMFILE:T} == "include-sub.mk"
+LOG+= subsub-ok
+.else
+LOG+= subsub-fail
+.endif
diff --git a/devel/bmake/files/unit-tests/modmatch.exp b/devel/bmake/files/unit-tests/modmatch.exp
index fcaf6c02ed6..a7bf8b748f5 100644
--- a/devel/bmake/files/unit-tests/modmatch.exp
+++ b/devel/bmake/files/unit-tests/modmatch.exp
@@ -14,4 +14,7 @@ LIB=e X_LIBS:M${LIB${LIB:tu}} is "/tmp/libe.a"
LIB=e X_LIBS:M*/lib${LIB}.a is "/tmp/libe.a"
LIB=e X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBE.A"
Mscanner=OK
+Upper=One Two Three Four
+Lower=five six seven
+nose=One Three five
exit status 0
diff --git a/devel/bmake/files/unit-tests/modmatch.mk b/devel/bmake/files/unit-tests/modmatch.mk
index 48a1befb58b..45199287acd 100644
--- a/devel/bmake/files/unit-tests/modmatch.mk
+++ b/devel/bmake/files/unit-tests/modmatch.mk
@@ -15,7 +15,9 @@ res = no
res = OK
.endif
-all:
+all: show-libs check-cclass
+
+show-libs:
@for x in $X; do ${.MAKE} -f ${MAKEFILE} show LIB=$$x; done
@echo "Mscanner=${res}"
@@ -23,3 +25,10 @@ show:
@echo 'LIB=${LIB} X_LIBS:M$${LIB$${LIB:tu}} is "${X_LIBS:M${LIB${LIB:tu}}}"'
@echo 'LIB=${LIB} X_LIBS:M*/lib$${LIB}.a is "${X_LIBS:M*/lib${LIB}.a}"'
@echo 'LIB=${LIB} X_LIBS:M*/lib$${LIB}.a:tu is "${X_LIBS:M*/lib${LIB}.a:tu}"'
+
+LIST= One Two Three Four five six seven
+
+check-cclass:
+ @echo Upper=${LIST:M[A-Z]*}
+ @echo Lower=${LIST:M[^A-Z]*}
+ @echo nose=${LIST:M[^s]*[ex]}
diff --git a/devel/bmake/files/unit-tests/modorder.mk b/devel/bmake/files/unit-tests/modorder.mk
index 441a3da5ad2..3070d340e1a 100644
--- a/devel/bmake/files/unit-tests/modorder.mk
+++ b/devel/bmake/files/unit-tests/modorder.mk
@@ -1,4 +1,4 @@
-# $NetBSD: modorder.mk,v 1.1.1.1 2015/05/19 21:36:45 joerg Exp $
+# $NetBSD: modorder.mk,v 1.1.1.2 2020/05/24 05:35:53 nia Exp $
LIST= one two three four five six seven eight nine ten
LISTX= ${LIST:Ox}
@@ -12,8 +12,9 @@ all:
@echo "LIST:O = ${LIST:O}"
# Note that 1 in every 10! trials two independently generated
# randomized orderings will be the same. The test framework doesn't
- # support checking probabilistic output, so we accept that the test
- # will incorrectly fail with probability 2.8E-7.
+ # support checking probabilistic output, so we accept that each of the
+ # 3 :Ox tests will incorrectly fail with probability 2.756E-7, which
+ # lets the whole test fail once in 1.209.600 runs, on average.
@echo "LIST:Ox = `test '${LIST:Ox}' != '${LIST:Ox}' ${TEST_RESULT}`"
@echo "LIST:O:Ox = `test '${LIST:O:Ox}' != '${LIST:O:Ox}' ${TEST_RESULT}`"
@echo "LISTX = `test '${LISTX}' != '${LISTX}' ${TEST_RESULT}`"
diff --git a/devel/bmake/files/unit-tests/modts.exp b/devel/bmake/files/unit-tests/modts.exp
index cf3c91d43c0..338964963a8 100644
--- a/devel/bmake/files/unit-tests/modts.exp
+++ b/devel/bmake/files/unit-tests/modts.exp
@@ -23,10 +23,16 @@ THREE
FOUR
FIVE
SIX"
+LIST:ts/xa:tu="ONE
+TWO
+THREE
+FOUR
+FIVE
+SIX"
make: Bad modifier `:tx' for LIST
LIST:tx="}"
-make: Bad modifier `:ts\x' for LIST
-LIST:ts/x:tu="\x:tu}"
+make: Bad modifier `:ts\X' for LIST
+LIST:ts/x:tu="\X:tu}"
FU_mod-ts="a/b/cool"
FU_mod-ts:ts:T="cool" == cool?
B.${AAA:ts}="Baaa" == Baaa?
diff --git a/devel/bmake/files/unit-tests/modts.mk b/devel/bmake/files/unit-tests/modts.mk
index 616bd8944f2..e66dc25a2a0 100644
--- a/devel/bmake/files/unit-tests/modts.mk
+++ b/devel/bmake/files/unit-tests/modts.mk
@@ -12,9 +12,9 @@ all: mod-ts
# Use print or printf iff they are builtin.
# XXX note that this causes problems, when make decides
# there is no need to use a shell, so avoid where possible.
-.if ${type print 2> /dev/null || echo:L:sh:Mbuiltin} != ""
+.if ${(type print) 2> /dev/null || echo:L:sh:Mbuiltin} != ""
PRINT= print -r --
-.elif ${type printf 2> /dev/null || echo:L:sh:Mbuiltin} != ""
+.elif ${(type printf) 2> /dev/null || echo:L:sh:Mbuiltin} != ""
PRINT= printf '%s\n'
.else
PRINT= echo
@@ -36,8 +36,9 @@ mod-ts:
@${PRINT} 'LIST:ts/n="${LIST:ts\n}"'
@${PRINT} 'LIST:ts/t="${LIST:ts\t}"'
@${PRINT} 'LIST:ts/012:tu="${LIST:ts\012:tu}"'
+ @${PRINT} 'LIST:ts/xa:tu="${LIST:ts\xa:tu}"'
@${PRINT} 'LIST:tx="${LIST:tx}"'
- @${PRINT} 'LIST:ts/x:tu="${LIST:ts\x:tu}"'
+ @${PRINT} 'LIST:ts/x:tu="${LIST:ts\X:tu}"'
@${PRINT} 'FU_$@="${FU_${@:ts}:ts}"'
@${PRINT} 'FU_$@:ts:T="${FU_${@:ts}:ts:T}" == cool?'
@${PRINT} 'B.$${AAA:ts}="${B.${AAA:ts}}" == Baaa?'
diff --git a/devel/bmake/files/unit-tests/sysv.exp b/devel/bmake/files/unit-tests/sysv.exp
index 4cce2de3191..610f97c39e8 100644
--- a/devel/bmake/files/unit-tests/sysv.exp
+++ b/devel/bmake/files/unit-tests/sysv.exp
@@ -4,4 +4,12 @@ fun
fun
fun
In the Sun
+acme
+aam.d
+sam.c
+a%.c
+asam.c.c
+asam.c
+a.c.c
+
exit status 0
diff --git a/devel/bmake/files/unit-tests/sysv.mk b/devel/bmake/files/unit-tests/sysv.mk
index 0eb780f91e9..99a9f4a3bc9 100644
--- a/devel/bmake/files/unit-tests/sysv.mk
+++ b/devel/bmake/files/unit-tests/sysv.mk
@@ -1,4 +1,4 @@
-# $Id: sysv.mk,v 1.1.1.1 2015/05/19 21:36:45 joerg Exp $
+# $Id: sysv.mk,v 1.1.1.2 2020/05/24 05:35:53 nia Exp $
FOO ?=
FOOBAR = ${FOO:=bar}
@@ -11,7 +11,7 @@ FUN = ${B}${S}fun
SUN = the Sun
# we expect nothing when FOO is empty
-all: foo fun
+all: foo fun sam bla
foo:
@echo FOOBAR = ${FOOBAR}
@@ -24,3 +24,20 @@ fun:
@echo ${FUN:${B}${S}fun=fun}
@echo ${FUN:${B}${S}%=%}
@echo ${In:L:%=% ${SUN}}
+
+
+SAM=sam.c
+
+sam:
+ @echo ${SAM:s%.c=acme}
+ @echo ${SAM:s%.c=a%.d}
+ @echo ${SAM:s.c=a%.d}
+ @echo ${SAM:sam.c=a%.c}
+ @echo ${SAM:%=a%.c}
+ @echo ${SAM:%.c=a%.c}
+ @echo ${SAM:sam%=a%.c}
+
+BLA=
+
+bla:
+ @echo $(BLA:%=foo/%x)
diff --git a/devel/bmake/files/unit-tests/varcmd.exp b/devel/bmake/files/unit-tests/varcmd.exp
index 34dd637f93e..7803c2b4299 100644
--- a/devel/bmake/files/unit-tests/varcmd.exp
+++ b/devel/bmake/files/unit-tests/varcmd.exp
@@ -1,5 +1,7 @@
default FU=<v>fu</v> FOO=<v>foo</v> VAR=<v></v>
two FU=<v>bar</v> FOO=<v>goo</v> VAR=<v></v>
+immutable FU='bar'
+immutable FOO='goo'
three FU=<v>bar</v> FOO=<v>goo</v> VAR=<v></v>
four FU=<v>bar</v> FOO=<v>goo</v> VAR=<v>Internal</v>
five FU=<v>bar</v> FOO=<v>goo</v> VAR=<v>Internal</v>
diff --git a/devel/bmake/files/unit-tests/varcmd.mk b/devel/bmake/files/unit-tests/varcmd.mk
index b107a7ccec8..1d6e75c7fb2 100644
--- a/devel/bmake/files/unit-tests/varcmd.mk
+++ b/devel/bmake/files/unit-tests/varcmd.mk
@@ -1,4 +1,4 @@
-# $Id: varcmd.mk,v 1.1.1.1 2015/05/19 21:36:45 joerg Exp $
+# $Id: varcmd.mk,v 1.1.1.2 2020/05/24 05:35:53 nia Exp $
#
# Test behaviour of recursive make and vars set on command line.
@@ -15,7 +15,7 @@ show:
@echo "${TAG} FU=<v>${FU}</v> FOO=<v>${FOO}</v> VAR=<v>${VAR}</v>"
one: show
- @${.MAKE} -f ${MAKEFILE} FU=bar FOO=goo two
+ @${.MAKE} -f ${MAKEFILE} FU=bar FOO+=goo two
two: show
@${.MAKE} -f ${MAKEFILE} three
@@ -24,6 +24,17 @@ three: show
@${.MAKE} -f ${MAKEFILE} four
+.ifmake two
+# this should not work
+FU+= oops
+FOO+= oops
+_FU:= ${FU}
+_FOO:= ${FOO}
+two: immutable
+immutable:
+ @echo "$@ FU='${_FU}'"
+ @echo "$@ FOO='${_FOO}'"
+.endif
.ifmake four
VAR=Internal
.MAKEOVERRIDES+= VAR
diff --git a/devel/bmake/files/unit-tests/varmisc.exp b/devel/bmake/files/unit-tests/varmisc.exp
index 1636aafc11e..ffe8f8b867c 100644
--- a/devel/bmake/files/unit-tests/varmisc.exp
+++ b/devel/bmake/files/unit-tests/varmisc.exp
@@ -1,2 +1,25 @@
+:D expanded when var set
+true
+TRUE
+:U expanded when var undef
+true
+TRUE
+:D skipped if var undef
+
+:U skipped when var set
+is set
+:? only lhs when value true
+true
+TRUE
+:? only rhs when value false
+false
+FALSE
+do not evaluate or expand :? if discarding
+is set
+year=2016 month=04 day=01
+date=20160401
+Version=123.456.789 == 123456789
+Literal=3.4.5 == 3004005
+We have target specific vars
exit status 0
diff --git a/devel/bmake/files/unit-tests/varmisc.mk b/devel/bmake/files/unit-tests/varmisc.mk
index 47da97329ad..8f23dfec588 100644
--- a/devel/bmake/files/unit-tests/varmisc.mk
+++ b/devel/bmake/files/unit-tests/varmisc.mk
@@ -1,8 +1,62 @@
-# $Id: varmisc.mk,v 1.1.1.1 2015/05/19 21:36:45 joerg Exp $
+# $Id: varmisc.mk,v 1.1.1.2 2020/05/24 05:35:53 nia Exp $
#
# Miscellaneous variable tests.
-all: unmatched_var_paren
+all: unmatched_var_paren D_true U_true D_false U_false Q_lhs Q_rhs NQ_none \
+ strftime cmpv
unmatched_var_paren:
@echo ${foo::=foo-text}
+
+True = ${echo true >&2:L:sh}TRUE
+False= ${echo false >&2:L:sh}FALSE
+
+VSET= is set
+.undef UNDEF
+
+U_false:
+ @echo :U skipped when var set
+ @echo ${VSET:U${False}}
+
+D_false:
+ @echo :D skipped if var undef
+ @echo ${UNDEF:D${False}}
+
+U_true:
+ @echo :U expanded when var undef
+ @echo ${UNDEF:U${True}}
+
+D_true:
+ @echo :D expanded when var set
+ @echo ${VSET:D${True}}
+
+Q_lhs:
+ @echo :? only lhs when value true
+ @echo ${1:L:?${True}:${False}}
+
+Q_rhs:
+ @echo :? only rhs when value false
+ @echo ${0:L:?${True}:${False}}
+
+NQ_none:
+ @echo do not evaluate or expand :? if discarding
+ @echo ${VSET:U${1:L:?${True}:${False}}}
+
+April1= 1459494000
+
+# slightly contorted syntax to use utc via variable
+strftime:
+ @echo ${year=%Y month=%m day=%d:L:gmtime=1459494000}
+ @echo date=${%Y%m%d:L:${gmtime=${April1}:L}}
+
+# big jumps to handle 3 digits per step
+M_cmpv.units = 1 1000 1000000
+M_cmpv = S,., ,g:_:range:@i@+ $${_:[-$$i]} \* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh
+
+Version = 123.456.789
+cmpv.only = target specific vars
+
+cmpv:
+ @echo Version=${Version} == ${Version:${M_cmpv}}
+ @echo Literal=3.4.5 == ${3.4.5:L:${M_cmpv}}
+ @echo We have ${${.TARGET:T}.only}
diff --git a/devel/bmake/files/unit-tests/varmod-edge.exp b/devel/bmake/files/unit-tests/varmod-edge.exp
new file mode 100644
index 00000000000..b3b2e3a92f9
--- /dev/null
+++ b/devel/bmake/files/unit-tests/varmod-edge.exp
@@ -0,0 +1,17 @@
+make: Unclosed variable specification (expecting '}') for "" (value "*)") modifier U
+make: Unclosed substitution for INP.eq-esc (= missing)
+ok M-paren
+ok M-mixed
+ok M-unescape
+ok M-nest-mix
+ok M-nest-brk
+ok M-pat-err
+ok M-bsbs
+ok M-bs1-par
+ok M-bs2-par
+ok M-128
+ok eq-ext
+ok eq-q
+ok eq-bs
+ok eq-esc
+exit status 0
diff --git a/devel/bmake/files/unit-tests/varmod-edge.mk b/devel/bmake/files/unit-tests/varmod-edge.mk
new file mode 100644
index 00000000000..587f5bd6d94
--- /dev/null
+++ b/devel/bmake/files/unit-tests/varmod-edge.mk
@@ -0,0 +1,162 @@
+# $NetBSD: varmod-edge.mk,v 1.1.1.1 2020/05/24 05:35:53 nia Exp $
+#
+# Tests for edge cases in variable modifiers.
+#
+# These tests demonstrate the current implementation in small examples.
+# They may contain surprising behavior.
+#
+# Each test consists of:
+# - INP, the input to the test
+# - MOD, the expression for testing the modifier
+# - EXP, the expected output
+
+TESTS+= M-paren
+INP.M-paren= (parentheses) {braces} (opening closing) ()
+MOD.M-paren= ${INP.M-paren:M(*)}
+EXP.M-paren= (parentheses) ()
+
+# The first closing brace matches the opening parenthesis.
+# The second closing brace actually ends the variable expression.
+#
+# XXX: This is unexpected but rarely occurs in practice.
+TESTS+= M-mixed
+INP.M-mixed= (paren-brace} (
+MOD.M-mixed= ${INP.M-mixed:M(*}}
+EXP.M-mixed= (paren-brace}
+
+# After the :M modifier has parsed the pattern, only the closing brace
+# and the colon are unescaped. The other characters are left as-is.
+# To actually see this effect, the backslashes in the :M modifier need
+# to be doubled since single backslashes would simply be unescaped by
+# Str_Match.
+#
+# XXX: This is unexpected. The opening brace should also be unescaped.
+TESTS+= M-unescape
+INP.M-unescape= ({}): \(\{\}\)\: \(\{}\):
+MOD.M-unescape= ${INP.M-unescape:M\\(\\{\\}\\)\\:}
+EXP.M-unescape= \(\{}\):
+
+# When the :M and :N modifiers are parsed, the pattern finishes as soon
+# as open_parens + open_braces == closing_parens + closing_braces. This
+# means that ( and } form a matching pair.
+#
+# Nested variable expressions are not parsed as such. Instead, only the
+# parentheses and braces are counted. This leads to a parse error since
+# the nested expression is not "${:U*)}" but only "${:U*)", which is
+# missing the closing brace. The expression is evaluated anyway.
+# The final brace in the output comes from the end of M.nest-mix.
+#
+# XXX: This is unexpected but rarely occurs in practice.
+TESTS+= M-nest-mix
+INP.M-nest-mix= (parentheses)
+MOD.M-nest-mix= ${INP.M-nest-mix:M${:U*)}}
+EXP.M-nest-mix= (parentheses)}
+# make: Unclosed variable specification (expecting '}') for "" (value "*)") modifier U
+
+# In contrast to parentheses and braces, the brackets are not counted
+# when the :M modifier is parsed since Makefile variables only take the
+# ${VAR} or $(VAR) forms, but not $[VAR].
+#
+# The final ] in the pattern is needed to close the character class.
+TESTS+= M-nest-brk
+INP.M-nest-brk= [ [[ [[[
+MOD.M-nest-brk= ${INP.M-nest-brk:M${:U[[[[[]}}
+EXP.M-nest-brk= [
+
+# The pattern in the nested variable has an unclosed character class.
+# No error is reported though, and the pattern is closed implicitly.
+#
+# XXX: It is unexpected that no error is reported.
+# See str.c, function Str_Match.
+#
+# Before 2019-12-02, this test case triggered an out-of-bounds read
+# in Str_Match.
+TESTS+= M-pat-err
+INP.M-pat-err= [ [[ [[[
+MOD.M-pat-err= ${INP.M-pat-err:M${:U[[}}
+EXP.M-pat-err= [
+
+# The first backslash does not escape the second backslash.
+# Therefore, the second backslash escapes the parenthesis.
+# This means that the pattern ends there.
+# The final } in the output comes from the end of MOD.M-bsbs.
+#
+# If the first backslash were to escape the second backslash, the first
+# closing brace would match the opening parenthesis (see M-mixed), and
+# the second closing brace would be needed to close the variable.
+# After that, the remaining backslash would escape the parenthesis in
+# the pattern, therefore (} would match.
+TESTS+= M-bsbs
+INP.M-bsbs= (} \( \(}
+MOD.M-bsbs= ${INP.M-bsbs:M\\(}}
+EXP.M-bsbs= \(}
+#EXP.M-bsbs= (} # If the first backslash were to escape ...
+
+# The backslash in \( does not escape the parenthesis, therefore it
+# counts for the nesting level and matches with the first closing brace.
+# The second closing brace closes the variable, and the third is copied
+# literally.
+#
+# The second :M in the pattern is nested between ( and }, therefore it
+# does not start a new modifier.
+TESTS+= M-bs1-par
+INP.M-bs1-par= ( (:M (:M} \( \(:M \(:M}
+MOD.M-bs1-par= ${INP.M-bs1-par:M\(:M*}}}
+EXP.M-bs1-par= (:M}}
+
+# The double backslash is passed verbatim to the pattern matcher.
+# The Str_Match pattern is \\(:M*}, and there the backslash is unescaped.
+# Again, the ( takes place in the nesting level, and there is no way to
+# prevent this, no matter how many backslashes are used.
+TESTS+= M-bs2-par
+INP.M-bs2-par= ( (:M (:M} \( \(:M \(:M}
+MOD.M-bs2-par= ${INP.M-bs2-par:M\\(:M*}}}
+EXP.M-bs2-par= \(:M}}
+
+# Str_Match uses a recursive algorithm for matching the * patterns.
+# Make sure that it survives patterns with 128 asterisks.
+# That should be enough for all practical purposes.
+# To produce a stack overflow, just add more :Qs below.
+TESTS+= M-128
+INP.M-128= ${:U\\:Q:Q:Q:Q:Q:Q:Q:S,\\,x,g}
+PAT.M-128= ${:U\\:Q:Q:Q:Q:Q:Q:Q:S,\\,*,g}
+MOD.M-128= ${INP.M-128:M${PAT.M-128}}
+EXP.M-128= ${INP.M-128}
+
+# This is the normal SysV substitution. Nothing surprising here.
+TESTS+= eq-ext
+INP.eq-ext= file.c file.cc
+MOD.eq-ext= ${INP.eq-ext:%.c=%.o}
+EXP.eq-ext= file.o file.cc
+
+# The SysV := modifier is greedy and consumes all the modifier text
+# up until the closing brace or parenthesis. The :Q may look like a
+# modifier, but it really isn't, that's why it appears in the output.
+TESTS+= eq-q
+INP.eq-q= file.c file.cc
+MOD.eq-q= ${INP.eq-q:%.c=%.o:Q}
+EXP.eq-q= file.o:Q file.cc
+
+# The = in the := modifier can be escaped.
+TESTS+= eq-bs
+INP.eq-bs= file.c file.c=%.o
+MOD.eq-bs= ${INP.eq-bs:%.c\=%.o=%.ext}
+EXP.eq-bs= file.c file.ext
+
+# Having only an escaped = results in a parse error.
+# The call to "pattern.lhs = VarGetPattern" fails.
+TESTS+= eq-esc
+INP.eq-esc= file.c file...
+MOD.eq-esc= ${INP.eq-esc:a\=b}
+EXP.eq-esc= # empty
+# make: Unclosed substitution for INP.eq-esc (= missing)
+
+all:
+.for test in ${TESTS}
+. if ${MOD.${test}} == ${EXP.${test}}
+ @printf 'ok %s\n' ${test:Q}''
+. else
+ @printf 'error in %s: expected %s, got %s\n' \
+ ${test:Q}'' ${EXP.${test}:Q}'' ${MOD.${test}:Q}''
+. endif
+.endfor
diff --git a/devel/bmake/files/unit-tests/varquote.exp b/devel/bmake/files/unit-tests/varquote.exp
new file mode 100644
index 00000000000..63107bfd34f
--- /dev/null
+++ b/devel/bmake/files/unit-tests/varquote.exp
@@ -0,0 +1,3 @@
+-fdebug-prefix-map=$NETBSDSRCDIR=/usr/src -fdebug-regex-map=/usr/src/(.*)/obj$=/usr/obj/\1
+-fdebug-prefix-map=$NETBSDSRCDIR=/usr/src -fdebug-regex-map=/usr/src/(.*)/obj$=/usr/obj/\1
+exit status 0
diff --git a/devel/bmake/files/unit-tests/varquote.mk b/devel/bmake/files/unit-tests/varquote.mk
new file mode 100644
index 00000000000..45fc119c831
--- /dev/null
+++ b/devel/bmake/files/unit-tests/varquote.mk
@@ -0,0 +1,14 @@
+# $NetBSD: varquote.mk,v 1.1.1.1 2020/05/24 05:35:53 nia Exp $
+#
+# Test VAR:q modifier
+
+.if !defined(REPROFLAGS)
+REPROFLAGS+= -fdebug-prefix-map=\$$NETBSDSRCDIR=/usr/src
+REPROFLAGS+= -fdebug-regex-map='/usr/src/(.*)/obj$$=/usr/obj/\1'
+all:
+ @${MAKE} -f ${MAKEFILE} REPROFLAGS=${REPROFLAGS:S/\$/&&/g:Q}
+ @${MAKE} -f ${MAKEFILE} REPROFLAGS=${REPROFLAGS:q}
+.else
+all:
+ @printf "%s %s\n" ${REPROFLAGS}
+.endif