aboutsummaryrefslogtreecommitdiff
path: root/contrib/bmake/unit-tests
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/bmake/unit-tests')
-rw-r--r--contrib/bmake/unit-tests/Makefile358
-rwxr-xr-xcontrib/bmake/unit-tests/archive-suffix.exp2
-rwxr-xr-xcontrib/bmake/unit-tests/archive-suffix.mk23
-rw-r--r--contrib/bmake/unit-tests/archive.exp13
-rw-r--r--contrib/bmake/unit-tests/archive.mk45
-rwxr-xr-xcontrib/bmake/unit-tests/cmd-interrupt.exp9
-rwxr-xr-xcontrib/bmake/unit-tests/cmd-interrupt.mk50
-rw-r--r--contrib/bmake/unit-tests/cmdline.exp5
-rw-r--r--contrib/bmake/unit-tests/cmdline.mk37
-rw-r--r--contrib/bmake/unit-tests/cond-cmp-numeric-eq.exp1
-rwxr-xr-xcontrib/bmake/unit-tests/cond-cmp-numeric-eq.mk53
-rw-r--r--contrib/bmake/unit-tests/cond-cmp-numeric-ge.exp1
-rwxr-xr-xcontrib/bmake/unit-tests/cond-cmp-numeric-ge.mk75
-rw-r--r--contrib/bmake/unit-tests/cond-cmp-numeric-gt.exp1
-rwxr-xr-xcontrib/bmake/unit-tests/cond-cmp-numeric-gt.mk73
-rw-r--r--contrib/bmake/unit-tests/cond-cmp-numeric-le.exp1
-rwxr-xr-xcontrib/bmake/unit-tests/cond-cmp-numeric-le.mk75
-rw-r--r--contrib/bmake/unit-tests/cond-cmp-numeric-lt.exp1
-rwxr-xr-xcontrib/bmake/unit-tests/cond-cmp-numeric-lt.mk73
-rw-r--r--contrib/bmake/unit-tests/cond-cmp-numeric-ne.exp1
-rwxr-xr-xcontrib/bmake/unit-tests/cond-cmp-numeric-ne.mk49
-rw-r--r--contrib/bmake/unit-tests/cond-cmp-numeric.exp1
-rw-r--r--contrib/bmake/unit-tests/cond-cmp-numeric.mk8
-rw-r--r--contrib/bmake/unit-tests/cond-cmp-string.exp5
-rw-r--r--contrib/bmake/unit-tests/cond-cmp-string.mk39
-rw-r--r--contrib/bmake/unit-tests/cond-func-commands.exp1
-rw-r--r--contrib/bmake/unit-tests/cond-func-commands.mk36
-rw-r--r--contrib/bmake/unit-tests/cond-func-defined.exp5
-rw-r--r--contrib/bmake/unit-tests/cond-func-defined.mk33
-rw-r--r--contrib/bmake/unit-tests/cond-func-empty.exp1
-rw-r--r--contrib/bmake/unit-tests/cond-func-empty.mk8
-rw-r--r--contrib/bmake/unit-tests/cond-func-exists.exp1
-rw-r--r--contrib/bmake/unit-tests/cond-func-exists.mk42
-rw-r--r--contrib/bmake/unit-tests/cond-func-make.exp1
-rw-r--r--contrib/bmake/unit-tests/cond-func-make.mk8
-rw-r--r--contrib/bmake/unit-tests/cond-func-target.exp1
-rw-r--r--contrib/bmake/unit-tests/cond-func-target.mk38
-rw-r--r--contrib/bmake/unit-tests/cond-func.exp9
-rw-r--r--contrib/bmake/unit-tests/cond-func.mk63
-rw-r--r--contrib/bmake/unit-tests/cond-late.exp1
-rw-r--r--contrib/bmake/unit-tests/cond-late.mk10
-rw-r--r--contrib/bmake/unit-tests/cond-op-and.exp1
-rw-r--r--contrib/bmake/unit-tests/cond-op-and.mk27
-rw-r--r--contrib/bmake/unit-tests/cond-op-not.exp1
-rw-r--r--contrib/bmake/unit-tests/cond-op-not.mk22
-rw-r--r--contrib/bmake/unit-tests/cond-op-or.exp1
-rw-r--r--contrib/bmake/unit-tests/cond-op-or.mk27
-rw-r--r--contrib/bmake/unit-tests/cond-op-parentheses.exp1
-rw-r--r--contrib/bmake/unit-tests/cond-op-parentheses.mk8
-rw-r--r--contrib/bmake/unit-tests/cond-op.exp5
-rw-r--r--contrib/bmake/unit-tests/cond-op.mk60
-rw-r--r--contrib/bmake/unit-tests/cond-short.mk20
-rw-r--r--contrib/bmake/unit-tests/cond-token-number.exp1
-rw-r--r--contrib/bmake/unit-tests/cond-token-number.mk8
-rw-r--r--contrib/bmake/unit-tests/cond-token-plain.exp1
-rw-r--r--contrib/bmake/unit-tests/cond-token-plain.mk9
-rw-r--r--contrib/bmake/unit-tests/cond-token-string.exp1
-rw-r--r--contrib/bmake/unit-tests/cond-token-string.mk8
-rw-r--r--contrib/bmake/unit-tests/cond-token-var.exp7
-rw-r--r--contrib/bmake/unit-tests/cond-token-var.mk34
-rw-r--r--contrib/bmake/unit-tests/counter.exp88
-rw-r--r--contrib/bmake/unit-tests/counter.mk31
-rw-r--r--contrib/bmake/unit-tests/dep-colon.exp1
-rw-r--r--contrib/bmake/unit-tests/dep-colon.mk8
-rw-r--r--contrib/bmake/unit-tests/dep-double-colon.exp5
-rw-r--r--contrib/bmake/unit-tests/dep-double-colon.mk11
-rw-r--r--contrib/bmake/unit-tests/dep-exclam.exp1
-rw-r--r--contrib/bmake/unit-tests/dep-exclam.mk8
-rwxr-xr-xcontrib/bmake/unit-tests/dep-none.exp4
-rwxr-xr-xcontrib/bmake/unit-tests/dep-none.mk3
-rwxr-xr-xcontrib/bmake/unit-tests/dep-var.exp2
-rwxr-xr-xcontrib/bmake/unit-tests/dep-var.mk33
-rw-r--r--contrib/bmake/unit-tests/dep-wildcards.exp1
-rw-r--r--contrib/bmake/unit-tests/dep-wildcards.mk8
-rw-r--r--contrib/bmake/unit-tests/dep.exp1
-rw-r--r--contrib/bmake/unit-tests/dep.mk8
-rw-r--r--contrib/bmake/unit-tests/depsrc-exec.exp1
-rw-r--r--contrib/bmake/unit-tests/depsrc-exec.mk8
-rw-r--r--contrib/bmake/unit-tests/depsrc-ignore.exp11
-rw-r--r--contrib/bmake/unit-tests/depsrc-ignore.mk67
-rw-r--r--contrib/bmake/unit-tests/depsrc-made.exp1
-rw-r--r--contrib/bmake/unit-tests/depsrc-made.mk8
-rw-r--r--contrib/bmake/unit-tests/depsrc-make.exp1
-rw-r--r--contrib/bmake/unit-tests/depsrc-make.mk8
-rw-r--r--contrib/bmake/unit-tests/depsrc-meta.exp1
-rw-r--r--contrib/bmake/unit-tests/depsrc-meta.mk8
-rw-r--r--contrib/bmake/unit-tests/depsrc-nometa.exp1
-rw-r--r--contrib/bmake/unit-tests/depsrc-nometa.mk8
-rw-r--r--contrib/bmake/unit-tests/depsrc-nometa_cmp.exp1
-rw-r--r--contrib/bmake/unit-tests/depsrc-nometa_cmp.mk8
-rw-r--r--contrib/bmake/unit-tests/depsrc-nopath.exp1
-rw-r--r--contrib/bmake/unit-tests/depsrc-nopath.mk8
-rw-r--r--contrib/bmake/unit-tests/depsrc-notmain.exp1
-rw-r--r--contrib/bmake/unit-tests/depsrc-notmain.mk8
-rw-r--r--contrib/bmake/unit-tests/depsrc-optional.exp1
-rw-r--r--contrib/bmake/unit-tests/depsrc-optional.mk8
-rw-r--r--contrib/bmake/unit-tests/depsrc-phony.exp1
-rw-r--r--contrib/bmake/unit-tests/depsrc-phony.mk8
-rw-r--r--contrib/bmake/unit-tests/depsrc-precious.exp1
-rw-r--r--contrib/bmake/unit-tests/depsrc-precious.mk8
-rw-r--r--contrib/bmake/unit-tests/depsrc-recursive.exp1
-rw-r--r--contrib/bmake/unit-tests/depsrc-recursive.mk8
-rw-r--r--contrib/bmake/unit-tests/depsrc-silent.exp4
-rw-r--r--contrib/bmake/unit-tests/depsrc-silent.mk12
-rw-r--r--contrib/bmake/unit-tests/depsrc-use.exp6
-rw-r--r--contrib/bmake/unit-tests/depsrc-use.mk24
-rwxr-xr-xcontrib/bmake/unit-tests/depsrc-usebefore-double-colon.exp2
-rwxr-xr-xcontrib/bmake/unit-tests/depsrc-usebefore-double-colon.mk30
-rw-r--r--contrib/bmake/unit-tests/depsrc-usebefore.exp6
-rw-r--r--contrib/bmake/unit-tests/depsrc-usebefore.mk24
-rw-r--r--contrib/bmake/unit-tests/depsrc-wait.exp1
-rw-r--r--contrib/bmake/unit-tests/depsrc-wait.mk8
-rw-r--r--contrib/bmake/unit-tests/depsrc.exp1
-rw-r--r--contrib/bmake/unit-tests/depsrc.mk9
-rw-r--r--contrib/bmake/unit-tests/deptgt-begin.exp4
-rw-r--r--contrib/bmake/unit-tests/deptgt-begin.mk13
-rw-r--r--contrib/bmake/unit-tests/deptgt-default.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-default.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-delete_on_error.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-delete_on_error.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-end.exp4
-rw-r--r--contrib/bmake/unit-tests/deptgt-end.mk13
-rw-r--r--contrib/bmake/unit-tests/deptgt-error.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-error.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-ignore.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-ignore.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-interrupt.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-interrupt.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-main.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-main.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-makeflags.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-makeflags.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-no_parallel.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-no_parallel.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-nopath.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-nopath.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-notparallel.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-notparallel.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-objdir.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-objdir.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-order.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-order.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-path-suffix.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-path-suffix.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-path.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-path.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-phony.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-phony.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-precious.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-precious.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-shell.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-shell.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-silent.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-silent.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-stale.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-stale.mk8
-rw-r--r--contrib/bmake/unit-tests/deptgt-suffixes.exp7
-rw-r--r--contrib/bmake/unit-tests/deptgt-suffixes.mk18
-rw-r--r--contrib/bmake/unit-tests/deptgt.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt.mk9
-rw-r--r--contrib/bmake/unit-tests/dir-expand-path.exp4
-rwxr-xr-xcontrib/bmake/unit-tests/dir-expand-path.mk19
-rw-r--r--contrib/bmake/unit-tests/dir.exp19
-rw-r--r--contrib/bmake/unit-tests/dir.mk58
-rw-r--r--contrib/bmake/unit-tests/directive-elif.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-elif.mk8
-rw-r--r--contrib/bmake/unit-tests/directive-elifdef.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-elifdef.mk8
-rw-r--r--contrib/bmake/unit-tests/directive-elifmake.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-elifmake.mk8
-rw-r--r--contrib/bmake/unit-tests/directive-elifndef.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-elifndef.mk8
-rw-r--r--contrib/bmake/unit-tests/directive-elifnmake.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-elifnmake.mk8
-rw-r--r--contrib/bmake/unit-tests/directive-else.exp8
-rw-r--r--contrib/bmake/unit-tests/directive-else.mk32
-rw-r--r--contrib/bmake/unit-tests/directive-endif.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-endif.mk8
-rw-r--r--contrib/bmake/unit-tests/directive-error.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-error.mk8
-rw-r--r--contrib/bmake/unit-tests/directive-export-env.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-export-env.mk8
-rw-r--r--contrib/bmake/unit-tests/directive-export-literal.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-export-literal.mk8
-rw-r--r--contrib/bmake/unit-tests/directive-export.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-export.mk8
-rwxr-xr-xcontrib/bmake/unit-tests/directive-for-generating-endif.exp7
-rwxr-xr-xcontrib/bmake/unit-tests/directive-for-generating-endif.mk25
-rwxr-xr-xcontrib/bmake/unit-tests/directive-for.exp1
-rwxr-xr-xcontrib/bmake/unit-tests/directive-for.mk97
-rw-r--r--contrib/bmake/unit-tests/directive-if.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-if.mk8
-rw-r--r--contrib/bmake/unit-tests/directive-ifdef.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-ifdef.mk8
-rw-r--r--contrib/bmake/unit-tests/directive-ifmake.exp10
-rw-r--r--contrib/bmake/unit-tests/directive-ifmake.mk55
-rw-r--r--contrib/bmake/unit-tests/directive-ifndef.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-ifndef.mk8
-rw-r--r--contrib/bmake/unit-tests/directive-ifnmake.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-ifnmake.mk8
-rw-r--r--contrib/bmake/unit-tests/directive-info.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-info.mk8
-rw-r--r--contrib/bmake/unit-tests/directive-undef.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-undef.mk17
-rw-r--r--contrib/bmake/unit-tests/directive-unexport-env.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-unexport-env.mk8
-rw-r--r--contrib/bmake/unit-tests/directive-unexport.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-unexport.mk8
-rw-r--r--contrib/bmake/unit-tests/directive-warning.exp1
-rw-r--r--contrib/bmake/unit-tests/directive-warning.mk8
-rw-r--r--contrib/bmake/unit-tests/directive.exp1
-rw-r--r--contrib/bmake/unit-tests/directive.mk8
-rw-r--r--contrib/bmake/unit-tests/directives.exp42
-rw-r--r--contrib/bmake/unit-tests/directives.mk163
-rw-r--r--contrib/bmake/unit-tests/envfirst.exp1
-rw-r--r--contrib/bmake/unit-tests/envfirst.mk42
-rw-r--r--contrib/bmake/unit-tests/export-all.mk3
-rwxr-xr-xcontrib/bmake/unit-tests/export-variants.exp1
-rwxr-xr-xcontrib/bmake/unit-tests/export-variants.mk40
-rw-r--r--contrib/bmake/unit-tests/export.exp2
-rw-r--r--contrib/bmake/unit-tests/export.mk31
-rw-r--r--contrib/bmake/unit-tests/forloop.mk4
-rw-r--r--contrib/bmake/unit-tests/impsrc.exp16
-rw-r--r--contrib/bmake/unit-tests/impsrc.mk29
-rw-r--r--contrib/bmake/unit-tests/include-main.mk4
-rwxr-xr-xcontrib/bmake/unit-tests/lint.exp4
-rwxr-xr-xcontrib/bmake/unit-tests/lint.mk17
-rwxr-xr-xcontrib/bmake/unit-tests/make-exported.exp3
-rwxr-xr-xcontrib/bmake/unit-tests/make-exported.mk16
-rw-r--r--contrib/bmake/unit-tests/moderrs.exp129
-rw-r--r--contrib/bmake/unit-tests/moderrs.mk151
-rw-r--r--contrib/bmake/unit-tests/modmatch.exp3
-rw-r--r--contrib/bmake/unit-tests/modmatch.mk17
-rw-r--r--contrib/bmake/unit-tests/modmisc.exp40
-rw-r--r--contrib/bmake/unit-tests/modmisc.mk113
-rw-r--r--contrib/bmake/unit-tests/modorder.exp12
-rw-r--r--contrib/bmake/unit-tests/modorder.mk24
-rw-r--r--contrib/bmake/unit-tests/modts.exp37
-rw-r--r--contrib/bmake/unit-tests/modts.mk37
-rw-r--r--contrib/bmake/unit-tests/opt-backwards.exp1
-rw-r--r--contrib/bmake/unit-tests/opt-backwards.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-chdir.exp1
-rw-r--r--contrib/bmake/unit-tests/opt-chdir.mk8
-rwxr-xr-xcontrib/bmake/unit-tests/opt-debug-g1.exp15
-rwxr-xr-xcontrib/bmake/unit-tests/opt-debug-g1.mk19
-rw-r--r--contrib/bmake/unit-tests/opt-debug.exp1
-rw-r--r--contrib/bmake/unit-tests/opt-debug.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-define.exp1
-rw-r--r--contrib/bmake/unit-tests/opt-define.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-env.exp1
-rw-r--r--contrib/bmake/unit-tests/opt-env.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-file.exp1
-rw-r--r--contrib/bmake/unit-tests/opt-file.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-ignore.exp12
-rw-r--r--contrib/bmake/unit-tests/opt-ignore.mk30
-rw-r--r--contrib/bmake/unit-tests/opt-include-dir.exp1
-rw-r--r--contrib/bmake/unit-tests/opt-include-dir.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-jobs-internal.exp1
-rw-r--r--contrib/bmake/unit-tests/opt-jobs-internal.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-jobs.exp1
-rw-r--r--contrib/bmake/unit-tests/opt-jobs.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-keep-going.exp6
-rw-r--r--contrib/bmake/unit-tests/opt-keep-going.mk24
-rw-r--r--contrib/bmake/unit-tests/opt-m-include-dir.exp2
-rw-r--r--contrib/bmake/unit-tests/opt-m-include-dir.mk61
-rw-r--r--contrib/bmake/unit-tests/opt-no-action-at-all.exp1
-rw-r--r--contrib/bmake/unit-tests/opt-no-action-at-all.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-no-action.exp13
-rw-r--r--contrib/bmake/unit-tests/opt-no-action.mk33
-rw-r--r--contrib/bmake/unit-tests/opt-query.exp2
-rw-r--r--contrib/bmake/unit-tests/opt-query.mk24
-rw-r--r--contrib/bmake/unit-tests/opt-raw.exp1
-rw-r--r--contrib/bmake/unit-tests/opt-raw.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-silent.exp1
-rw-r--r--contrib/bmake/unit-tests/opt-silent.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-touch.exp1
-rw-r--r--contrib/bmake/unit-tests/opt-touch.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-tracefile.exp1
-rw-r--r--contrib/bmake/unit-tests/opt-tracefile.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-var-expanded.exp3
-rw-r--r--contrib/bmake/unit-tests/opt-var-expanded.mk6
-rw-r--r--contrib/bmake/unit-tests/opt-var-literal.exp3
-rw-r--r--contrib/bmake/unit-tests/opt-var-literal.mk6
-rw-r--r--contrib/bmake/unit-tests/opt-warnings-as-errors.exp7
-rw-r--r--contrib/bmake/unit-tests/opt-warnings-as-errors.mk11
-rw-r--r--contrib/bmake/unit-tests/opt-where-am-i.exp1
-rw-r--r--contrib/bmake/unit-tests/opt-where-am-i.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-x-reduce-exported.exp1
-rw-r--r--contrib/bmake/unit-tests/opt-x-reduce-exported.mk8
-rw-r--r--contrib/bmake/unit-tests/opt.exp1
-rw-r--r--contrib/bmake/unit-tests/opt.mk8
-rw-r--r--contrib/bmake/unit-tests/phony-end.exp2
-rw-r--r--contrib/bmake/unit-tests/posix1.mk4
-rw-r--r--contrib/bmake/unit-tests/recursive.exp5
-rw-r--r--contrib/bmake/unit-tests/recursive.mk37
-rwxr-xr-xcontrib/bmake/unit-tests/sh-dots.exp15
-rwxr-xr-xcontrib/bmake/unit-tests/sh-dots.mk37
-rw-r--r--contrib/bmake/unit-tests/sh-jobs-error.exp1
-rw-r--r--contrib/bmake/unit-tests/sh-jobs-error.mk9
-rw-r--r--contrib/bmake/unit-tests/sh-jobs.exp1
-rw-r--r--contrib/bmake/unit-tests/sh-jobs.mk9
-rw-r--r--contrib/bmake/unit-tests/sh-leading-at.exp5
-rw-r--r--contrib/bmake/unit-tests/sh-leading-at.mk10
-rw-r--r--contrib/bmake/unit-tests/sh-leading-hyphen.exp1
-rw-r--r--contrib/bmake/unit-tests/sh-leading-hyphen.mk9
-rw-r--r--contrib/bmake/unit-tests/sh-leading-plus.exp4
-rw-r--r--contrib/bmake/unit-tests/sh-leading-plus.mk8
-rw-r--r--contrib/bmake/unit-tests/sh-meta-chars.exp1
-rw-r--r--contrib/bmake/unit-tests/sh-meta-chars.mk11
-rw-r--r--contrib/bmake/unit-tests/sh-multi-line.exp1
-rw-r--r--contrib/bmake/unit-tests/sh-multi-line.mk9
-rw-r--r--contrib/bmake/unit-tests/sh-single-line.exp1
-rw-r--r--contrib/bmake/unit-tests/sh-single-line.mk12
-rw-r--r--contrib/bmake/unit-tests/sh.exp1
-rw-r--r--contrib/bmake/unit-tests/sh.mk9
-rw-r--r--contrib/bmake/unit-tests/sysv.exp1
-rw-r--r--contrib/bmake/unit-tests/sysv.mk11
-rw-r--r--contrib/bmake/unit-tests/unexport-env.mk3
-rw-r--r--contrib/bmake/unit-tests/unexport.mk13
-rw-r--r--contrib/bmake/unit-tests/use-inference.exp4
-rw-r--r--contrib/bmake/unit-tests/use-inference.mk35
-rw-r--r--contrib/bmake/unit-tests/var-class-cmdline.exp1
-rw-r--r--contrib/bmake/unit-tests/var-class-cmdline.mk8
-rw-r--r--contrib/bmake/unit-tests/var-class-env.exp1
-rw-r--r--contrib/bmake/unit-tests/var-class-env.mk8
-rw-r--r--contrib/bmake/unit-tests/var-class-global.exp1
-rw-r--r--contrib/bmake/unit-tests/var-class-global.mk8
-rw-r--r--contrib/bmake/unit-tests/var-class-local-legacy.exp1
-rw-r--r--contrib/bmake/unit-tests/var-class-local-legacy.mk8
-rw-r--r--contrib/bmake/unit-tests/var-class-local.exp1
-rw-r--r--contrib/bmake/unit-tests/var-class-local.mk8
-rw-r--r--contrib/bmake/unit-tests/var-class.exp1
-rw-r--r--contrib/bmake/unit-tests/var-class.mk9
-rw-r--r--contrib/bmake/unit-tests/var-op-append.exp1
-rw-r--r--contrib/bmake/unit-tests/var-op-append.mk9
-rw-r--r--contrib/bmake/unit-tests/var-op-assign.exp6
-rw-r--r--contrib/bmake/unit-tests/var-op-assign.mk89
-rw-r--r--contrib/bmake/unit-tests/var-op-default.exp1
-rw-r--r--contrib/bmake/unit-tests/var-op-default.mk9
-rw-r--r--contrib/bmake/unit-tests/var-op-expand.exp1
-rw-r--r--contrib/bmake/unit-tests/var-op-expand.mk9
-rw-r--r--contrib/bmake/unit-tests/var-op-shell.exp1
-rw-r--r--contrib/bmake/unit-tests/var-op-shell.mk9
-rw-r--r--contrib/bmake/unit-tests/var-op.exp1
-rw-r--r--contrib/bmake/unit-tests/var-op.mk8
-rw-r--r--contrib/bmake/unit-tests/vardebug.exp80
-rw-r--r--contrib/bmake/unit-tests/vardebug.mk59
-rw-r--r--contrib/bmake/unit-tests/varfind.exp15
-rw-r--r--contrib/bmake/unit-tests/varfind.mk31
-rw-r--r--contrib/bmake/unit-tests/varmisc.exp48
-rw-r--r--contrib/bmake/unit-tests/varmisc.mk147
-rw-r--r--contrib/bmake/unit-tests/varmod-assign.exp26
-rw-r--r--contrib/bmake/unit-tests/varmod-assign.mk81
-rw-r--r--contrib/bmake/unit-tests/varmod-defined.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-defined.mk28
-rw-r--r--contrib/bmake/unit-tests/varmod-edge.exp35
-rw-r--r--contrib/bmake/unit-tests/varmod-edge.mk27
-rw-r--r--contrib/bmake/unit-tests/varmod-exclam-shell.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-exclam-shell.mk28
-rw-r--r--contrib/bmake/unit-tests/varmod-extension.exp10
-rw-r--r--contrib/bmake/unit-tests/varmod-extension.mk9
-rw-r--r--contrib/bmake/unit-tests/varmod-gmtime.exp9
-rw-r--r--contrib/bmake/unit-tests/varmod-gmtime.mk35
-rw-r--r--contrib/bmake/unit-tests/varmod-hash.exp9
-rw-r--r--contrib/bmake/unit-tests/varmod-hash.mk10
-rw-r--r--contrib/bmake/unit-tests/varmod-head.exp10
-rw-r--r--contrib/bmake/unit-tests/varmod-head.mk9
-rw-r--r--contrib/bmake/unit-tests/varmod-ifelse.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-ifelse.mk9
-rw-r--r--contrib/bmake/unit-tests/varmod-l-name-to-value.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-l-name-to-value.mk31
-rw-r--r--contrib/bmake/unit-tests/varmod-localtime.exp4
-rw-r--r--contrib/bmake/unit-tests/varmod-localtime.mk9
-rw-r--r--contrib/bmake/unit-tests/varmod-loop.exp16
-rw-r--r--contrib/bmake/unit-tests/varmod-loop.mk63
-rwxr-xr-xcontrib/bmake/unit-tests/varmod-match-escape.exp3
-rwxr-xr-xcontrib/bmake/unit-tests/varmod-match-escape.mk20
-rw-r--r--contrib/bmake/unit-tests/varmod-match.exp5
-rw-r--r--contrib/bmake/unit-tests/varmod-match.mk22
-rw-r--r--contrib/bmake/unit-tests/varmod-no-match.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-no-match.mk9
-rw-r--r--contrib/bmake/unit-tests/varmod-order-reverse.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-order-reverse.mk13
-rw-r--r--contrib/bmake/unit-tests/varmod-order-shuffle.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-order-shuffle.mk39
-rw-r--r--contrib/bmake/unit-tests/varmod-order.exp7
-rw-r--r--contrib/bmake/unit-tests/varmod-order.mk19
-rw-r--r--contrib/bmake/unit-tests/varmod-path.exp4
-rw-r--r--contrib/bmake/unit-tests/varmod-path.mk35
-rw-r--r--contrib/bmake/unit-tests/varmod-quote-dollar.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-quote-dollar.mk10
-rw-r--r--contrib/bmake/unit-tests/varmod-quote.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-quote.mk9
-rw-r--r--contrib/bmake/unit-tests/varmod-range.exp8
-rw-r--r--contrib/bmake/unit-tests/varmod-range.mk10
-rw-r--r--contrib/bmake/unit-tests/varmod-remember.exp3
-rw-r--r--contrib/bmake/unit-tests/varmod-remember.mk12
-rw-r--r--contrib/bmake/unit-tests/varmod-root.exp10
-rw-r--r--contrib/bmake/unit-tests/varmod-root.mk9
-rw-r--r--contrib/bmake/unit-tests/varmod-select-words.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-select-words.mk9
-rw-r--r--contrib/bmake/unit-tests/varmod-shell.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-shell.mk9
-rw-r--r--contrib/bmake/unit-tests/varmod-subst-regex.exp23
-rw-r--r--contrib/bmake/unit-tests/varmod-subst-regex.mk87
-rw-r--r--contrib/bmake/unit-tests/varmod-subst.exp51
-rw-r--r--contrib/bmake/unit-tests/varmod-subst.mk153
-rw-r--r--contrib/bmake/unit-tests/varmod-sysv.exp8
-rw-r--r--contrib/bmake/unit-tests/varmod-sysv.mk61
-rw-r--r--contrib/bmake/unit-tests/varmod-tail.exp10
-rw-r--r--contrib/bmake/unit-tests/varmod-tail.mk9
-rw-r--r--contrib/bmake/unit-tests/varmod-to-abs.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-to-abs.mk9
-rw-r--r--contrib/bmake/unit-tests/varmod-to-lower.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-to-lower.mk19
-rw-r--r--contrib/bmake/unit-tests/varmod-to-many-words.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-to-many-words.mk9
-rw-r--r--contrib/bmake/unit-tests/varmod-to-one-word.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-to-one-word.mk9
-rw-r--r--contrib/bmake/unit-tests/varmod-to-separator.exp9
-rw-r--r--contrib/bmake/unit-tests/varmod-to-separator.mk118
-rw-r--r--contrib/bmake/unit-tests/varmod-to-upper.exp2
-rw-r--r--contrib/bmake/unit-tests/varmod-to-upper.mk21
-rw-r--r--contrib/bmake/unit-tests/varmod-undefined.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-undefined.mk55
-rw-r--r--contrib/bmake/unit-tests/varmod-unique.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-unique.mk39
-rw-r--r--contrib/bmake/unit-tests/varmod.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dollar.exp5
-rw-r--r--contrib/bmake/unit-tests/varname-dollar.mk29
-rw-r--r--contrib/bmake/unit-tests/varname-dot-alltargets.exp4
-rw-r--r--contrib/bmake/unit-tests/varname-dot-alltargets.mk25
-rw-r--r--contrib/bmake/unit-tests/varname-dot-curdir.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-curdir.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-includedfromdir.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-includedfromdir.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-includedfromfile.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-includedfromfile.mk8
-rwxr-xr-xcontrib/bmake/unit-tests/varname-dot-includes.exp2
-rwxr-xr-xcontrib/bmake/unit-tests/varname-dot-includes.mk20
-rwxr-xr-xcontrib/bmake/unit-tests/varname-dot-libs.exp2
-rwxr-xr-xcontrib/bmake/unit-tests/varname-dot-libs.mk20
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-dependfile.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-dependfile.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-expand_variables.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-expand_variables.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-exported.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-exported.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-jobs-prefix.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-jobs-prefix.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-jobs.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-jobs.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-level.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-level.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-makefile_preference.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-makefile_preference.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-makefiles.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-makefiles.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-bailiwick.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-bailiwick.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-created.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-created.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-files.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-files.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-prefix.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-prefix.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-mode.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-mode.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-path_filemon.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-path_filemon.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-pid.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-pid.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-ppid.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-ppid.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-save_dollars.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-save_dollars.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-makeoverrides.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-makeoverrides.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-newline.exp4
-rw-r--r--contrib/bmake/unit-tests/varname-dot-newline.mk23
-rw-r--r--contrib/bmake/unit-tests/varname-dot-objdir.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-objdir.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-parsedir.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-parsedir.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-parsefile.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-parsefile.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-dot-path.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-path.mk8
-rwxr-xr-xcontrib/bmake/unit-tests/varname-dot-shell.exp19
-rwxr-xr-xcontrib/bmake/unit-tests/varname-dot-shell.mk25
-rw-r--r--contrib/bmake/unit-tests/varname-dot-targets.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-targets.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-empty.exp11
-rwxr-xr-xcontrib/bmake/unit-tests/varname-empty.mk26
-rw-r--r--contrib/bmake/unit-tests/varname-make.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-make.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-make_print_var_on_error.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-make_print_var_on_error.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-makeflags.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-makeflags.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-pwd.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-pwd.mk8
-rw-r--r--contrib/bmake/unit-tests/varname-vpath.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-vpath.mk8
-rw-r--r--contrib/bmake/unit-tests/varname.exp1
-rw-r--r--contrib/bmake/unit-tests/varname.mk8
-rw-r--r--contrib/bmake/unit-tests/varparse-dynamic.exp5
-rw-r--r--contrib/bmake/unit-tests/varparse-dynamic.mk14
515 files changed, 6733 insertions, 305 deletions
diff --git a/contrib/bmake/unit-tests/Makefile b/contrib/bmake/unit-tests/Makefile
index c9cc13d769db..1566b177087a 100644
--- a/contrib/bmake/unit-tests/Makefile
+++ b/contrib/bmake/unit-tests/Makefile
@@ -1,6 +1,6 @@
-# $Id: Makefile,v 1.60 2020/07/10 00:48:32 sjg Exp $
+# $Id: Makefile,v 1.92 2020/09/02 18:39:29 sjg Exp $
#
-# $NetBSD: Makefile,v 1.63 2020/07/09 22:40:14 sjg Exp $
+# $NetBSD: Makefile,v 1.130 2020/09/02 05:33:57 rillig Exp $
#
# Unit tests for make(1)
#
@@ -25,68 +25,364 @@
# named makefile (*.mk), with its own set of expected results (*.exp),
# and it should be added to the TESTS list.
#
-# Any added files must also be added to src/distrib/sets/lists/tests/mi.
-# Makefiles that are not added to TESTS must be ignored in
-# src/tests/usr.bin/make/t_make.sh (example: include-sub).
+# A few *.mk files are helper files for other tests (such as include-sub.mk)
+# and are thus not added to TESTS. Such files must be ignored in
+# src/tests/usr.bin/make/t_make.sh.
#
# Each test is in a sub-makefile.
# Keep the list sorted.
+# Any test that is commented out must be ignored in
+# src/tests/usr.bin/make/t_make.sh as well.
+TESTS+= # archive # broken on FreeBSD, enabled in t_make.sh
+TESTS+= archive-suffix
+TESTS+= cmd-interrupt
+TESTS+= cmdline
TESTS+= comment
+TESTS+= cond-cmp-numeric
+TESTS+= cond-cmp-numeric-eq
+TESTS+= cond-cmp-numeric-ge
+TESTS+= cond-cmp-numeric-gt
+TESTS+= cond-cmp-numeric-le
+TESTS+= cond-cmp-numeric-lt
+TESTS+= cond-cmp-numeric-ne
+TESTS+= cond-cmp-string
+TESTS+= cond-func
+TESTS+= cond-func-commands
+TESTS+= cond-func-defined
+TESTS+= cond-func-empty
+TESTS+= cond-func-exists
+TESTS+= cond-func-make
+TESTS+= cond-func-target
TESTS+= cond-late
+TESTS+= cond-op
+TESTS+= cond-op-and
+TESTS+= cond-op-not
+TESTS+= cond-op-or
+TESTS+= cond-op-parentheses
TESTS+= cond-short
+TESTS+= cond-token-number
+TESTS+= cond-token-plain
+TESTS+= cond-token-string
+TESTS+= cond-token-var
TESTS+= cond1
TESTS+= cond2
+TESTS+= counter
+TESTS+= dep
+TESTS+= dep-colon
+TESTS+= dep-double-colon
+TESTS+= dep-exclam
+TESTS+= dep-none
+TESTS+= dep-var
+TESTS+= dep-wildcards
+TESTS+= depsrc
+TESTS+= depsrc-exec
+TESTS+= depsrc-ignore
+TESTS+= depsrc-made
+TESTS+= depsrc-make
+TESTS+= depsrc-meta
+TESTS+= depsrc-nometa
+TESTS+= depsrc-nometa_cmp
+TESTS+= depsrc-nopath
+TESTS+= depsrc-notmain
+TESTS+= depsrc-optional
+TESTS+= depsrc-phony
+TESTS+= depsrc-precious
+TESTS+= depsrc-recursive
+TESTS+= depsrc-silent
+TESTS+= depsrc-use
+TESTS+= depsrc-usebefore
+TESTS+= depsrc-usebefore-double-colon
+TESTS+= depsrc-wait
+TESTS+= deptgt
+TESTS+= deptgt-begin
+TESTS+= deptgt-default
+TESTS+= deptgt-delete_on_error
+TESTS+= deptgt-end
+TESTS+= deptgt-error
+TESTS+= deptgt-ignore
+TESTS+= deptgt-interrupt
+TESTS+= deptgt-main
+TESTS+= deptgt-makeflags
+TESTS+= deptgt-no_parallel
+TESTS+= deptgt-nopath
+TESTS+= deptgt-notparallel
+TESTS+= deptgt-objdir
+TESTS+= deptgt-order
+TESTS+= deptgt-path
+TESTS+= deptgt-path-suffix
+TESTS+= deptgt-phony
+TESTS+= deptgt-precious
+TESTS+= deptgt-shell
+TESTS+= deptgt-silent
+TESTS+= deptgt-stale
+TESTS+= deptgt-suffixes
+TESTS+= dir
+TESTS+= dir-expand-path
+TESTS+= directive
+TESTS+= directive-elif
+TESTS+= directive-elifdef
+TESTS+= directive-elifmake
+TESTS+= directive-elifndef
+TESTS+= directive-elifnmake
+TESTS+= directive-else
+TESTS+= directive-endif
+TESTS+= directive-error
+TESTS+= directive-export
+TESTS+= directive-export-env
+TESTS+= directive-export-literal
+TESTS+= directive-for
+TESTS+= directive-for-generating-endif
+TESTS+= directive-if
+TESTS+= directive-ifdef
+TESTS+= directive-ifmake
+TESTS+= directive-ifndef
+TESTS+= directive-ifnmake
+TESTS+= directive-info
+TESTS+= directive-undef
+TESTS+= directive-unexport
+TESTS+= directive-unexport-env
+TESTS+= directive-warning
+TESTS+= directives
TESTS+= dollar
TESTS+= doterror
TESTS+= dotwait
+TESTS+= envfirst
TESTS+= error
TESTS+= # escape # broken by reverting POSIX changes
TESTS+= export
TESTS+= export-all
TESTS+= export-env
+TESTS+= export-variants
TESTS+= forloop
TESTS+= forsubst
TESTS+= hash
-TESTS+= # impsrc # broken by reverting POSIX changes
+TESTS+= impsrc
TESTS+= include-main
+TESTS+= lint
+TESTS+= make-exported
TESTS+= misc
TESTS+= moderrs
TESTS+= modmatch
TESTS+= modmisc
-TESTS+= modorder
TESTS+= modts
TESTS+= modword
+TESTS+= opt
+TESTS+= opt-backwards
+TESTS+= opt-chdir
+TESTS+= opt-debug
+TESTS+= opt-debug-g1
+TESTS+= opt-define
+TESTS+= opt-env
+TESTS+= opt-file
+TESTS+= opt-ignore
+TESTS+= opt-include-dir
+TESTS+= opt-jobs
+TESTS+= opt-jobs-internal
+TESTS+= opt-keep-going
+TESTS+= opt-m-include-dir
+TESTS+= opt-no-action
+TESTS+= opt-no-action-at-all
+TESTS+= opt-query
+TESTS+= opt-raw
+TESTS+= opt-silent
+TESTS+= opt-touch
+TESTS+= opt-tracefile
+TESTS+= opt-var-expanded
+TESTS+= opt-var-literal
+TESTS+= opt-warnings-as-errors
+TESTS+= opt-where-am-i
+TESTS+= opt-x-reduce-exported
TESTS+= order
-TESTS+= # phony-end # broken by reverting POSIX changes
+TESTS+= phony-end
TESTS+= posix
TESTS+= # posix1 # broken by reverting POSIX changes
TESTS+= qequals
-TESTS+= # suffixes # broken by reverting POSIX changes
+TESTS+= recursive
+TESTS+= sh
+TESTS+= sh-dots
+TESTS+= sh-jobs
+TESTS+= sh-jobs-error
+TESTS+= sh-leading-at
+TESTS+= sh-leading-hyphen
+TESTS+= sh-leading-plus
+TESTS+= sh-meta-chars
+TESTS+= sh-multi-line
+TESTS+= sh-single-line
+TESTS+= # suffixes # runs into an endless loop (try -dA)
TESTS+= sunshcmd
TESTS+= sysv
TESTS+= ternary
TESTS+= unexport
TESTS+= unexport-env
+TESTS+= use-inference
+TESTS+= var-class
+TESTS+= var-class-cmdline
+TESTS+= var-class-env
+TESTS+= var-class-global
+TESTS+= var-class-local
+TESTS+= var-class-local-legacy
+TESTS+= var-op
+TESTS+= var-op-append
+TESTS+= var-op-assign
+TESTS+= var-op-default
+TESTS+= var-op-expand
+TESTS+= var-op-shell
TESTS+= varcmd
+TESTS+= vardebug
+TESTS+= varfind
TESTS+= varmisc
+TESTS+= varmod
+TESTS+= varmod-assign
+TESTS+= varmod-defined
TESTS+= varmod-edge
+TESTS+= varmod-exclam-shell
+TESTS+= varmod-extension
+TESTS+= varmod-gmtime
+TESTS+= varmod-hash
+TESTS+= varmod-head
+TESTS+= varmod-ifelse
+TESTS+= varmod-l-name-to-value
+TESTS+= varmod-localtime
+TESTS+= varmod-loop
+TESTS+= varmod-match
+TESTS+= varmod-match-escape
+TESTS+= varmod-no-match
+TESTS+= varmod-order
+TESTS+= varmod-order-reverse
+TESTS+= varmod-order-shuffle
+TESTS+= varmod-path
+TESTS+= varmod-quote
+TESTS+= varmod-quote-dollar
+TESTS+= varmod-range
+TESTS+= varmod-remember
+TESTS+= varmod-root
+TESTS+= varmod-select-words
+TESTS+= varmod-shell
+TESTS+= varmod-subst
+TESTS+= varmod-subst-regex
+TESTS+= varmod-sysv
+TESTS+= varmod-tail
+TESTS+= varmod-to-abs
+TESTS+= varmod-to-lower
+TESTS+= varmod-to-many-words
+TESTS+= varmod-to-one-word
+TESTS+= varmod-to-separator
+TESTS+= varmod-to-upper
+TESTS+= varmod-undefined
+TESTS+= varmod-unique
+TESTS+= varname
+TESTS+= varname-dollar
+TESTS+= varname-dot-alltargets
+TESTS+= varname-dot-curdir
+TESTS+= varname-dot-includes
+TESTS+= varname-dot-includedfromdir
+TESTS+= varname-dot-includedfromfile
+TESTS+= varname-dot-libs
+TESTS+= varname-dot-make-dependfile
+TESTS+= varname-dot-make-expand_variables
+TESTS+= varname-dot-make-exported
+TESTS+= varname-dot-make-jobs
+TESTS+= varname-dot-make-jobs-prefix
+TESTS+= varname-dot-make-level
+TESTS+= varname-dot-make-makefile_preference
+TESTS+= varname-dot-make-makefiles
+TESTS+= varname-dot-make-meta-bailiwick
+TESTS+= varname-dot-make-meta-created
+TESTS+= varname-dot-make-meta-files
+TESTS+= varname-dot-make-meta-ignore_filter
+TESTS+= varname-dot-make-meta-ignore_paths
+TESTS+= varname-dot-make-meta-ignore_patterns
+TESTS+= varname-dot-make-meta-prefix
+TESTS+= varname-dot-make-mode
+TESTS+= varname-dot-make-path_filemon
+TESTS+= varname-dot-make-pid
+TESTS+= varname-dot-make-ppid
+TESTS+= varname-dot-make-save_dollars
+TESTS+= varname-dot-makeoverrides
+TESTS+= varname-dot-newline
+TESTS+= varname-dot-objdir
+TESTS+= varname-dot-parsedir
+TESTS+= varname-dot-parsefile
+TESTS+= varname-dot-path
+TESTS+= varname-dot-shell
+TESTS+= varname-dot-targets
+TESTS+= varname-empty
+TESTS+= varname-make
+TESTS+= varname-make_print_var_on_error
+TESTS+= varname-makeflags
+TESTS+= varname-pwd
+TESTS+= varname-vpath
+TESTS+= varparse-dynamic
TESTS+= varquote
TESTS+= varshell
-# Override make flags for certain tests; default is -k.
+# Additional environment variables for some of the tests.
+# The base environment is -i PATH="$PATH".
+ENV.envfirst= FROM_ENV=value-from-env
+ENV.varmisc= FROM_ENV=env
+ENV.varmisc+= FROM_ENV_BEFORE=env
+ENV.varmisc+= FROM_ENV_AFTER=env
+
+# Override make flags for some of the tests; default is -k.
+# If possible, write ".MAKEFLAGS: -dv" in the test .mk file instead of
+# settings FLAGS.test=-dv here, since that is closer to the test code.
+FLAGS.archive= -dA
+FLAGS.counter= -dv
+FLAGS.directive-ifmake= first second
FLAGS.doterror= # none
+FLAGS.envfirst= -e
+FLAGS.export= # none
+FLAGS.lint= -dL -k
+FLAGS.opt-debug-g1= -dg1
+FLAGS.opt-ignore= -i
+FLAGS.opt-keep-going= -k
+FLAGS.opt-no-action= -n
+FLAGS.opt-query= -q
+FLAGS.opt-var-expanded= -v VAR -v VALUE
+FLAGS.opt-var-literal= -V VAR -V VALUE
+FLAGS.opt-warnings-as-errors= -W
FLAGS.order= -j1
+FLAGS.recursive= -dL
+FLAGS.sh-leading-plus= -n
+FLAGS.vardebug= -k -dv FROM_CMDLINE=
+FLAGS.varmod-match-escape= -dv
+FLAGS.varname-dot-shell= -dpv
+FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmline-plain'
# Some tests need extra post-processing.
-SED_CMDS.modmisc+= -e 's,\(substitution error:\).*,\1 (details omitted),'
-SED_CMDS.varshell+= -e 's,^[a-z]*sh: ,,'
+SED_CMDS.opt-debug-g1= -e 's,${.CURDIR},CURDIR,'
+SED_CMDS.opt-debug-g1+= -e '/Global Variables:/,/Suffixes:/d'
+SED_CMDS.sh-dots= -e 's,^.*\.\.\.:.*,<normalized: ...: not found>,'
+SED_CMDS.varmod-subst-regex+= \
+ -e 's,\(Regex compilation error:\).*,\1 (details omitted),'
+SED_CMDS.varmod-edge+= -e 's, line [0-9]*:, line omitted:,'
+SED_CMDS.varshell+= -e 's,^${.SHELL:T}: ,,'
SED_CMDS.varshell+= -e '/command/s,No such.*,not found,'
+SED_CMDS.varname-dot-shell= -e 's, = /.*, = (details omitted),'
+SED_CMDS.varname-dot-shell+= -e 's,"/[^"]*","(details omitted)",'
+SED_CMDS.varname-dot-shell+= -e 's,\[/[^]]*\],[(details omitted)],'
+
+# Some tests need an additional round of postprocessing.
+POSTPROC.counter= ${TOOL_SED} -n -e '/:RELEVANT = yes/,/:RELEVANT = no/p'
+POSTPROC.deptgt-suffixes= \
+ ${TOOL_SED} -n -e '/^\#\*\*\* Suffixes/,/^\#\*/p'
+POSTPROC.vardebug= ${TOOL_SED} -n -e '/:RELEVANT = yes/,/:RELEVANT = no/p'
+POSTPROC.varmod-match-escape= ${TOOL_SED} -n -e '/^Pattern/p'
+POSTPROC.varname-dot-shell= \
+ awk '/\.SHELL/ || /^ParseReadLine/'
+POSTPROC.varname-empty= ${TOOL_SED} -n -e '/^Var_Set/p' -e '/^out:/p'
+
+# Some tests reuse other tests, which makes them unnecessarily fragile.
+export-all.rawout: export.mk
+unexport.rawout: export.mk
+unexport-env.rawout: export.mk
# End of the configuration section.
.MAIN: all
+.-include "Makefile.inc"
.-include "Makefile.config"
UNIT_TESTS:= ${.PARSEDIR}
@@ -96,16 +392,14 @@ OUTFILES= ${TESTS:=.out}
all: ${OUTFILES}
-CLEANFILES+= *.rawout *.out *.status *.tmp *.core *.tmp
+CLEANFILES= *.rawout *.out *.status *.tmp *.core *.tmp
CLEANFILES+= obj*.[och] lib*.a # posix1.mk
CLEANFILES+= issue* .[ab]* # suffixes.mk
-CLEANRECURSIVE+= dir dummy # posix1.mk
+CLEANDIRS= dir dummy # posix1.mk
clean:
rm -f ${CLEANFILES}
-.if !empty(CLEANRECURSIVE)
- rm -rf ${CLEANRECURSIVE}
-.endif
+ rm -rf ${CLEANDIRS}
TEST_MAKE?= ${.MAKE}
TOOL_SED?= sed
@@ -120,13 +414,24 @@ LANG= C
.export LANG LC_ALL
.endif
+.if ${.MAKE.MODE:Unormal:Mmeta} != ""
+# we don't need the noise
+_MKMSG_TEST= :
+.endif
+
# 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
+ @${_MKMSG_TEST:Uecho '# test '} ${.PREFIX}
+ @set -eu; \
+ cd ${.OBJDIR}; \
+ env -i PATH="$$PATH" ${ENV.${.TARGET:R}} \
+ ${TEST_MAKE} \
+ -r -C ${.CURDIR} -f ${.IMPSRC} \
+ ${FLAGS.${.TARGET:R}:U-k} \
+ > ${.TARGET}.tmp 2>&1 \
+ && status=$$? || status=$$?; \
+ echo $$status > ${.TARGET:R}.status
@mv ${.TARGET}.tmp ${.TARGET}
# Post-process the test output so that the results can be compared.
@@ -141,11 +446,12 @@ _SED_CMDS+= -e 's,${.CURDIR:S,.,\\.,g}/,,g'
_SED_CMDS+= -e 's,${UNIT_TESTS:S,.,\\.,g}/,,g'
.rawout.out:
- @echo postprocess ${.TARGET}
@${TOOL_SED} ${_SED_CMDS} ${SED_CMDS.${.TARGET:R}} \
- < ${.IMPSRC} > ${.TARGET}.tmp
- @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp
- @mv ${.TARGET}.tmp ${.TARGET}
+ < ${.IMPSRC} > ${.TARGET}.tmp1
+ @${POSTPROC.${.TARGET:R}:Ucat} < ${.TARGET}.tmp1 > ${.TARGET}.tmp2
+ @rm ${.TARGET}.tmp1
+ @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp2
+ @mv ${.TARGET}.tmp2 ${.TARGET}
# Compare all output files
test: ${OUTFILES} .PHONY
@@ -168,7 +474,7 @@ accept:
done
.if exists(${TEST_MAKE})
-${TESTS:=.rawout}: ${TEST_MAKE}
+${TESTS:=.rawout}: ${TEST_MAKE} ${.PARSEDIR}/Makefile
.endif
.-include <obj.mk>
diff --git a/contrib/bmake/unit-tests/archive-suffix.exp b/contrib/bmake/unit-tests/archive-suffix.exp
new file mode 100755
index 000000000000..05f2ac6624c4
--- /dev/null
+++ b/contrib/bmake/unit-tests/archive-suffix.exp
@@ -0,0 +1,2 @@
+`all' is up to date.
+exit status 0
diff --git a/contrib/bmake/unit-tests/archive-suffix.mk b/contrib/bmake/unit-tests/archive-suffix.mk
new file mode 100755
index 000000000000..9f7fa219c667
--- /dev/null
+++ b/contrib/bmake/unit-tests/archive-suffix.mk
@@ -0,0 +1,23 @@
+# $NetBSD: archive-suffix.mk,v 1.1 2020/08/29 14:47:26 rillig Exp $
+#
+# Between 2020-08-23 and 2020-08-30, the below code produced an assertion
+# failure in Var_Set_with_flags, triggered by Compat_Make, when setting the
+# .IMPSRC of an archive node to its .TARGET.
+#
+# The code assumed that the .TARGET variable of every node would be set, but
+# but that is not guaranteed.
+#
+# Between 2016-03-15 and 2016-03-16 the behavior of the below code changed.
+# Until 2016-03-15, it remade the target, starting with 2016-03-16 it says
+# "`all' is up to date".
+
+.SUFFIXES:
+.SUFFIXES: .c .o
+
+all: lib.a(obj1.o)
+
+.c.o:
+ : making $@
+
+obj1.c:
+ : $@
diff --git a/contrib/bmake/unit-tests/archive.exp b/contrib/bmake/unit-tests/archive.exp
new file mode 100644
index 000000000000..a42b4f39e173
--- /dev/null
+++ b/contrib/bmake/unit-tests/archive.exp
@@ -0,0 +1,13 @@
+rm -f libprog.a
+ar cru libprog.a archive.mk modmisc.mk varmisc.mk
+ranlib libprog.a
+ar t libprog.a
+archive.mk
+modmisc.mk
+varmisc.mk
+list-archive-wildcard: archive.mk
+list-archive-wildcard: ternary.mk
+depend-on-existing-member
+`depend-on-nonexistent-member' is up to date.
+rm -f libprog.a
+exit status 0
diff --git a/contrib/bmake/unit-tests/archive.mk b/contrib/bmake/unit-tests/archive.mk
new file mode 100644
index 000000000000..c3b7e919eab2
--- /dev/null
+++ b/contrib/bmake/unit-tests/archive.mk
@@ -0,0 +1,45 @@
+# $NetBSD: archive.mk,v 1.5 2020/08/23 17:51:24 rillig Exp $
+#
+# Very basic demonstration of handling archives, based on the description
+# in PSD.doc/tutorial.ms.
+
+ARCHIVE= libprog.${EXT.a}
+FILES= archive.${EXT.mk} modmisc.${EXT.mk} varmisc.mk
+
+EXT.a= a
+EXT.mk= mk
+
+MAKE_CMD= ${.MAKE} -f ${MAKEFILE}
+RUN?= @set -eu;
+
+all:
+ ${RUN} ${MAKE_CMD} remove-archive
+ ${RUN} ${MAKE_CMD} create-archive
+ ${RUN} ${MAKE_CMD} list-archive
+ ${RUN} ${MAKE_CMD} list-archive-wildcard
+ ${RUN} ${MAKE_CMD} depend-on-existing-member
+ ${RUN} ${MAKE_CMD} depend-on-nonexistent-member
+ ${RUN} ${MAKE_CMD} remove-archive
+
+create-archive: ${ARCHIVE}
+${ARCHIVE}: ${ARCHIVE}(${FILES})
+ ar cru ${.TARGET} ${.OODATE}
+ ranlib ${.TARGET}
+
+list-archive: ${ARCHIVE}
+ ar t ${.ALLSRC}
+
+# XXX: I had expected that this dependency would select all *.mk files from
+# the archive. Instead, the globbing is done in the current directory.
+# To prevent an overly long file list, the pattern is restricted to [at]*.mk.
+list-archive-wildcard: ${ARCHIVE}([at]*.mk)
+ ${RUN} printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@}
+
+depend-on-existing-member: ${ARCHIVE}(archive.mk)
+ ${RUN} echo $@
+
+depend-on-nonexistent-member: ${ARCHIVE}(nonexistent.mk)
+ ${RUN} echo $@
+
+remove-archive:
+ rm -f ${ARCHIVE}
diff --git a/contrib/bmake/unit-tests/cmd-interrupt.exp b/contrib/bmake/unit-tests/cmd-interrupt.exp
new file mode 100755
index 000000000000..91f4439e7bea
--- /dev/null
+++ b/contrib/bmake/unit-tests/cmd-interrupt.exp
@@ -0,0 +1,9 @@
+> cmd-interrupt-ordinary
+make: *** cmd-interrupt-ordinary removed
+interrupt-ordinary: ok
+> cmd-interrupt-phony
+make: *** cmd-interrupt-phony removed
+interrupt-phony: ok
+> cmd-interrupt-precious
+interrupt-precious: ok
+exit status 0
diff --git a/contrib/bmake/unit-tests/cmd-interrupt.mk b/contrib/bmake/unit-tests/cmd-interrupt.mk
new file mode 100755
index 000000000000..033f3307bd2e
--- /dev/null
+++ b/contrib/bmake/unit-tests/cmd-interrupt.mk
@@ -0,0 +1,50 @@
+# $NetBSD: cmd-interrupt.mk,v 1.2 2020/08/28 18:16:22 rillig Exp $
+#
+# Tests for interrupting a command.
+#
+# If a command is interrupted (usually by the user, here by itself), the
+# target is removed. This is to avoid having an unfinished target that
+# would be newer than all of its sources and would therefore not be
+# tried again in the next run.
+#
+# This happens for ordinary targets as well as for .PHONY targets, even
+# though the .PHONY targets usually do not correspond to a file.
+#
+# To protect the target from being removed, the target has to be marked with
+# the special source .PRECIOUS. These targets need to ensure for themselves
+# that interrupting them does not leave an inconsistent state behind.
+#
+# See also:
+# CompatDeleteTarget
+
+all: clean-before interrupt-ordinary interrupt-phony interrupt-precious clean-after
+
+clean-before clean-after: .PHONY
+ @rm -f cmd-interrupt-ordinary cmd-interrupt-phony cmd-interrupt-precious
+
+interrupt-ordinary: .PHONY
+ @${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-ordinary || true
+ # The ././ is necessary to work around the file cache.
+ @echo ${.TARGET}: ${exists(././cmd-interrupt-ordinary) :? error : ok }
+
+interrupt-phony: .PHONY
+ @${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-phony || true
+ # The ././ is necessary to work around the file cache.
+ @echo ${.TARGET}: ${exists(././cmd-interrupt-phony) :? error : ok }
+
+interrupt-precious: .PRECIOUS
+ @${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-precious || true
+ # The ././ is necessary to work around the file cache.
+ @echo ${.TARGET}: ${exists(././cmd-interrupt-precious) :? ok : error }
+
+cmd-interrupt-ordinary:
+ > ${.TARGET}
+ @kill -INT ${.MAKE.PID}
+
+cmd-interrupt-phony: .PHONY
+ > ${.TARGET}
+ @kill -INT ${.MAKE.PID}
+
+cmd-interrupt-precious: .PRECIOUS
+ > ${.TARGET}
+ @kill -INT ${.MAKE.PID}
diff --git a/contrib/bmake/unit-tests/cmdline.exp b/contrib/bmake/unit-tests/cmdline.exp
new file mode 100644
index 000000000000..8e981ba5248c
--- /dev/null
+++ b/contrib/bmake/unit-tests/cmdline.exp
@@ -0,0 +1,5 @@
+makeobjdir-direct:
+show-objdir: /tmp/6a8899d2-d227-4b55-9b6b-f3c8eeb83fd5
+makeobjdir-indirect:
+show-objdir: /tmp/a7b41170-53f8-4cc2-bc5c-e4c3dd93ec45/
+exit status 0
diff --git a/contrib/bmake/unit-tests/cmdline.mk b/contrib/bmake/unit-tests/cmdline.mk
new file mode 100644
index 000000000000..c12c31220cb5
--- /dev/null
+++ b/contrib/bmake/unit-tests/cmdline.mk
@@ -0,0 +1,37 @@
+# $NetBSD: cmdline.mk,v 1.1 2020/07/28 22:44:44 rillig Exp $
+#
+# Tests for command line parsing and related special variables.
+
+RUN?= @set -eu;
+TMPBASE?= /tmp
+SUB1= a7b41170-53f8-4cc2-bc5c-e4c3dd93ec45 # just a random UUID
+SUB2= 6a8899d2-d227-4b55-9b6b-f3c8eeb83fd5 # just a random UUID
+MAKE_CMD= env TMPBASE=${TMPBASE}/${SUB1} ${.MAKE} -f ${MAKEFILE} -r
+DIR2= ${TMPBASE}/${SUB2}
+DIR12= ${TMPBASE}/${SUB1}/${SUB2}
+
+all: prepare-dirs
+all: makeobjdir-direct makeobjdir-indirect
+
+prepare-dirs:
+ ${RUN} rm -rf ${DIR2} ${DIR12}
+ ${RUN} mkdir -p ${DIR2} ${DIR12}
+
+# The .OBJDIR can be set via the MAKEOBJDIR command line variable.
+# It must be a command line variable; an environment variable would not work.
+makeobjdir-direct:
+ @echo $@:
+ ${RUN} ${MAKE_CMD} MAKEOBJDIR=${DIR2} show-objdir
+
+# The .OBJDIR can be set via the MAKEOBJDIR command line variable,
+# and that variable could even contain the usual modifiers.
+# Since the .OBJDIR=MAKEOBJDIR assignment happens very early,
+# the SUB2 variable in the modifier is not defined yet and is therefore empty.
+# The SUB1 in the resulting path comes from the environment variable TMPBASE,
+# see MAKE_CMD.
+makeobjdir-indirect:
+ @echo $@:
+ ${RUN} ${MAKE_CMD} MAKEOBJDIR='$${TMPBASE}/$${SUB2}' show-objdir
+
+show-objdir:
+ @echo $@: ${.OBJDIR:Q}
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-eq.exp b/contrib/bmake/unit-tests/cond-cmp-numeric-eq.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-eq.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-eq.mk b/contrib/bmake/unit-tests/cond-cmp-numeric-eq.mk
new file mode 100755
index 000000000000..02b95ae593cb
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-eq.mk
@@ -0,0 +1,53 @@
+# $NetBSD: cond-cmp-numeric-eq.mk,v 1.1 2020/08/23 13:50:17 rillig Exp $
+#
+# Tests for numeric comparisons with the == operator in .if conditions.
+
+# This comparison yields the same result, whether numeric or character-based.
+.if 1 == 1
+.else
+.error
+.endif
+
+# This comparison yields the same result, whether numeric or character-based.
+.if 1 == 2
+.error
+.endif
+
+.if 2 == 1
+.error
+.endif
+
+# Scientific notation is supported, as per strtod.
+.if 2e7 == 2000e4
+.else
+.error
+.endif
+
+.if 2000e4 == 2e7
+.else
+.error
+.endif
+
+# Trailing zeroes after the decimal point are irrelevant for the numeric
+# value.
+.if 3.30000 == 3.3
+.else
+.error
+.endif
+
+.if 3.3 == 3.30000
+.else
+.error
+.endif
+
+# As of 2020-08-23, numeric comparison is implemented as parsing both sides
+# as double, and then performing a normal comparison. The range of double is
+# typically 16 or 17 significant digits, therefore these two numbers seem to
+# be equal.
+.if 1.000000000000000001 == 1.000000000000000002
+.else
+.error
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-ge.exp b/contrib/bmake/unit-tests/cond-cmp-numeric-ge.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-ge.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-ge.mk b/contrib/bmake/unit-tests/cond-cmp-numeric-ge.mk
new file mode 100755
index 000000000000..510b80e14942
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-ge.mk
@@ -0,0 +1,75 @@
+# $NetBSD: cond-cmp-numeric-ge.mk,v 1.1 2020/08/23 13:50:17 rillig Exp $
+#
+# Tests for numeric comparisons with the >= operator in .if conditions.
+
+# When both sides are equal, the >= operator always yields true.
+.if 1 >= 1
+.else
+.error
+.endif
+
+# This comparison yields the same result, whether numeric or character-based.
+.if 1 >= 2
+.error
+.endif
+
+.if 2 >= 1
+.else
+.error
+.endif
+
+# If this comparison were character-based instead of numerical, the
+# 5 would be >= 14 since its first digit is greater.
+.if 5 >= 14
+.error
+.endif
+
+.if 14 >= 5
+.else
+.error
+.endif
+
+# Scientific notation is supported, as per strtod.
+.if 2e7 >= 1e8
+.error
+.endif
+
+.if 1e8 >= 2e7
+.else
+.error
+.endif
+
+# Floating pointer numbers can be compared as well.
+# This might be tempting to use for version numbers, but there are a few pitfalls.
+.if 3.141 >= 111.222
+.error
+.endif
+
+.if 111.222 >= 3.141
+.else
+.error
+.endif
+
+# When parsed as a version number, 3.30 is greater than 3.7.
+# Since make parses numbers as plain numbers, that leads to wrong results.
+# Numeric comparisons are not suited for comparing version number.
+.if 3.30 >= 3.7
+.error
+.endif
+
+.if 3.7 >= 3.30
+.else
+.error
+.endif
+
+# As of 2020-08-23, numeric comparison is implemented as parsing both sides
+# as double, and then performing a normal comparison. The range of double is
+# typically 16 or 17 significant digits, therefore these two numbers seem to
+# be equal.
+.if 1.000000000000000001 >= 1.000000000000000002
+.else
+.error
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-gt.exp b/contrib/bmake/unit-tests/cond-cmp-numeric-gt.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-gt.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-gt.mk b/contrib/bmake/unit-tests/cond-cmp-numeric-gt.mk
new file mode 100755
index 000000000000..24ac1eb8531b
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-gt.mk
@@ -0,0 +1,73 @@
+# $NetBSD: cond-cmp-numeric-gt.mk,v 1.1 2020/08/23 13:50:17 rillig Exp $
+#
+# Tests for numeric comparisons with the > operator in .if conditions.
+
+# When both sides are equal, the > operator always yields false.
+.if 1 > 1
+.error
+.endif
+
+# This comparison yields the same result, whether numeric or character-based.
+.if 1 > 2
+.error
+.endif
+
+.if 2 > 1
+.else
+.error
+.endif
+
+# If this comparison were character-based instead of numerical, the
+# 5 would be > 14 since its first digit is greater.
+.if 5 > 14
+.error
+.endif
+
+.if 14 > 5
+.else
+.error
+.endif
+
+# Scientific notation is supported, as per strtod.
+.if 2e7 > 1e8
+.error
+.endif
+
+.if 1e8 > 2e7
+.else
+.error
+.endif
+
+# Floating pointer numbers can be compared as well.
+# This might be tempting to use for version numbers, but there are a few pitfalls.
+.if 3.141 > 111.222
+.error
+.endif
+
+.if 111.222 > 3.141
+.else
+.error
+.endif
+
+# When parsed as a version number, 3.30 is greater than 3.7.
+# Since make parses numbers as plain numbers, that leads to wrong results.
+# Numeric comparisons are not suited for comparing version number.
+.if 3.30 > 3.7
+.error
+.endif
+
+.if 3.7 > 3.30
+.else
+.error
+.endif
+
+# As of 2020-08-23, numeric comparison is implemented as parsing both sides
+# as double, and then performing a normal comparison. The range of double is
+# typically 16 or 17 significant digits, therefore these two numbers seem to
+# be equal.
+.if 1.000000000000000001 > 1.000000000000000002
+.error
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-le.exp b/contrib/bmake/unit-tests/cond-cmp-numeric-le.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-le.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-le.mk b/contrib/bmake/unit-tests/cond-cmp-numeric-le.mk
new file mode 100755
index 000000000000..2e4f5e9e694b
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-le.mk
@@ -0,0 +1,75 @@
+# $NetBSD: cond-cmp-numeric-le.mk,v 1.1 2020/08/23 13:50:17 rillig Exp $
+#
+# Tests for numeric comparisons with the <= operator in .if conditions.
+
+# When both sides are equal, the <= operator always yields true.
+.if 1 <= 1
+.else
+.error
+.endif
+
+# This comparison yields the same result, whether numeric or character-based.
+.if 1 <= 2
+.else
+.error
+.endif
+
+.if 2 <= 1
+.error
+.endif
+
+# If this comparison were character-based instead of numerical, the
+# 5 would be >= 14 since its first digit is greater.
+.if 5 <= 14
+.else
+.error
+.endif
+
+.if 14 <= 5
+.error
+.endif
+
+# Scientific notation is supported, as per strtod.
+.if 2e7 <= 1e8
+.else
+.error
+.endif
+
+.if 1e8 <= 2e7
+.error
+.endif
+
+# Floating pointer numbers can be compared as well.
+# This might be tempting to use for version numbers, but there are a few pitfalls.
+.if 3.141 <= 111.222
+.else
+.error
+.endif
+
+.if 111.222 <= 3.141
+.error
+.endif
+
+# When parsed as a version number, 3.30 is greater than 3.7.
+# Since make parses numbers as plain numbers, that leads to wrong results.
+# Numeric comparisons are not suited for comparing version number.
+.if 3.30 <= 3.7
+.else
+.error
+.endif
+
+.if 3.7 <= 3.30
+.error
+.endif
+
+# As of 2020-08-23, numeric comparison is implemented as parsing both sides
+# as double, and then performing a normal comparison. The range of double is
+# typically 16 or 17 significant digits, therefore these two numbers seem to
+# be equal.
+.if 1.000000000000000001 <= 1.000000000000000002
+.else
+.error
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-lt.exp b/contrib/bmake/unit-tests/cond-cmp-numeric-lt.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-lt.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-lt.mk b/contrib/bmake/unit-tests/cond-cmp-numeric-lt.mk
new file mode 100755
index 000000000000..a5fcceddff4b
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-lt.mk
@@ -0,0 +1,73 @@
+# $NetBSD: cond-cmp-numeric-lt.mk,v 1.1 2020/08/23 13:50:17 rillig Exp $
+#
+# Tests for numeric comparisons with the < operator in .if conditions.
+
+# When both sides are equal, the < operator always yields false.
+.if 1 < 1
+.error
+.endif
+
+# This comparison yields the same result, whether numeric or character-based.
+.if 1 < 2
+.else
+.error
+.endif
+
+.if 2 < 1
+.error
+.endif
+
+# If this comparison were character-based instead of numerical, the
+# 5 would be > 14 since its first digit is greater.
+.if 5 < 14
+.else
+.error
+.endif
+
+.if 14 < 5
+.error
+.endif
+
+# Scientific notation is supported, as per strtod.
+.if 2e7 < 1e8
+.else
+.error
+.endif
+
+.if 1e8 < 2e7
+.error
+.endif
+
+# Floating pointer numbers can be compared as well.
+# This might be tempting to use for version numbers, but there are a few pitfalls.
+.if 3.141 < 111.222
+.else
+.error
+.endif
+
+.if 111.222 < 3.141
+.error
+.endif
+
+# When parsed as a version number, 3.30 is greater than 3.7.
+# Since make parses numbers as plain numbers, that leads to wrong results.
+# Numeric comparisons are not suited for comparing version number.
+.if 3.30 < 3.7
+.else
+.error
+.endif
+
+.if 3.7 < 3.30
+.error
+.endif
+
+# As of 2020-08-23, numeric comparison is implemented as parsing both sides
+# as double, and then performing a normal comparison. The range of double is
+# typically 16 or 17 significant digits, therefore these two numbers seem to
+# be equal.
+.if 1.000000000000000001 < 1.000000000000000002
+.error
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-ne.exp b/contrib/bmake/unit-tests/cond-cmp-numeric-ne.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-ne.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-ne.mk b/contrib/bmake/unit-tests/cond-cmp-numeric-ne.mk
new file mode 100755
index 000000000000..6f858584d139
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-ne.mk
@@ -0,0 +1,49 @@
+# $NetBSD: cond-cmp-numeric-ne.mk,v 1.1 2020/08/23 13:50:17 rillig Exp $
+#
+# Tests for numeric comparisons with the != operator in .if conditions.
+
+# When both sides are equal, the != operator always yields false.
+.if 1 != 1
+.error
+.endif
+
+# This comparison yields the same result, whether numeric or character-based.
+.if 1 != 2
+.else
+.error
+.endif
+
+.if 2 != 1
+.else
+.error
+.endif
+
+# Scientific notation is supported, as per strtod.
+.if 2e7 != 2000e4
+.error
+.endif
+
+.if 2000e4 != 2e7
+.error
+.endif
+
+# Trailing zeroes after the decimal point are irrelevant for the numeric
+# value.
+.if 3.30000 != 3.3
+.error
+.endif
+
+.if 3.3 != 3.30000
+.error
+.endif
+
+# As of 2020-08-23, numeric comparison is implemented as parsing both sides
+# as double, and then performing a normal comparison. The range of double is
+# typically 16 or 17 significant digits, therefore these two numbers seem to
+# be equal.
+.if 1.000000000000000001 != 1.000000000000000002
+.error
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric.exp b/contrib/bmake/unit-tests/cond-cmp-numeric.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric.mk b/contrib/bmake/unit-tests/cond-cmp-numeric.mk
new file mode 100644
index 000000000000..409636c3c3ca
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric.mk
@@ -0,0 +1,8 @@
+# $NetBSD: cond-cmp-numeric.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for numeric comparisons in .if conditions.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-cmp-string.exp b/contrib/bmake/unit-tests/cond-cmp-string.exp
new file mode 100644
index 000000000000..03dcb0416898
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-cmp-string.exp
@@ -0,0 +1,5 @@
+make: "cond-cmp-string.mk" line 18: Malformed conditional (str != str)
+make: "cond-cmp-string.mk" line 37: Malformed conditional ("string" != "str""ing")
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/cond-cmp-string.mk b/contrib/bmake/unit-tests/cond-cmp-string.mk
new file mode 100644
index 000000000000..67d86b61e88d
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-cmp-string.mk
@@ -0,0 +1,39 @@
+# $NetBSD: cond-cmp-string.mk,v 1.3 2020/08/20 18:43:19 rillig Exp $
+#
+# Tests for string comparisons in .if conditions.
+
+# This is a simple comparison of string literals.
+# Nothing surprising here.
+.if "str" != "str"
+.error
+.endif
+
+# The right-hand side of the comparison may be written without quotes.
+.if "str" != str
+.error
+.endif
+
+# The left-hand side of the comparison must be enclosed in quotes.
+# This one is not enclosed in quotes and thus generates an error message.
+.if str != str
+.error
+.endif
+
+# The left-hand side of the comparison requires a defined variable.
+# The variable named "" is not defined, but applying the :U modifier to it
+# makes it "kind of defined" (see VAR_KEEP). Therefore it is ok here.
+.if ${:Ustr} != "str"
+.error
+.endif
+
+# Any character in a string literal may be escaped using a backslash.
+# This means that "\n" does not mean a newline but a simple "n".
+.if "string" != "\s\t\r\i\n\g"
+.error
+.endif
+
+# It is not possible to concatenate two string literals to form a single
+# string.
+.if "string" != "str""ing"
+.error
+.endif
diff --git a/contrib/bmake/unit-tests/cond-func-commands.exp b/contrib/bmake/unit-tests/cond-func-commands.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-func-commands.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-func-commands.mk b/contrib/bmake/unit-tests/cond-func-commands.mk
new file mode 100644
index 000000000000..4a098169048c
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-func-commands.mk
@@ -0,0 +1,36 @@
+# $NetBSD: cond-func-commands.mk,v 1.3 2020/08/23 14:07:20 rillig Exp $
+#
+# Tests for the commands() function in .if conditions.
+
+.MAIN: all
+
+# The target "target" does not exist yet, therefore it cannot have commands.
+.if commands(target)
+.error
+.endif
+
+target:
+
+# Now the target exists, but it still has no commands.
+.if commands(target)
+.error
+.endif
+
+target:
+ # not a command
+
+# Even after the comment, the target still has no commands.
+.if commands(target)
+.error
+.endif
+
+target:
+ @:;
+
+# Finally the target has commands.
+.if !commands(target)
+.error
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-func-defined.exp b/contrib/bmake/unit-tests/cond-func-defined.exp
new file mode 100644
index 000000000000..70c6342a02c3
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-func-defined.exp
@@ -0,0 +1,5 @@
+make: "cond-func-defined.mk" line 23: warning: Missing closing parenthesis for defined()
+make: "cond-func-defined.mk" line 23: Malformed conditional (!defined(A B))
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/cond-func-defined.mk b/contrib/bmake/unit-tests/cond-func-defined.mk
new file mode 100644
index 000000000000..dce1399183aa
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-func-defined.mk
@@ -0,0 +1,33 @@
+# $NetBSD: cond-func-defined.mk,v 1.3 2020/08/20 17:23:43 rillig Exp $
+#
+# Tests for the defined() function in .if conditions.
+
+DEF= defined
+${:UA B}= variable name with spaces
+
+.if !defined(DEF)
+.error
+.endif
+
+# Horizontal whitespace after the opening parenthesis is ignored.
+.if !defined( DEF)
+.error
+.endif
+
+# Horizontal whitespace before the closing parenthesis is ignored.
+.if !defined(DEF )
+.error
+.endif
+
+# The argument of a function must not directly contain whitespace.
+.if !defined(A B)
+.error
+.endif
+
+# If necessary, the whitespace can be generated by a variable expression.
+.if !defined(${:UA B})
+.error
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-func-empty.exp b/contrib/bmake/unit-tests/cond-func-empty.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-func-empty.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-func-empty.mk b/contrib/bmake/unit-tests/cond-func-empty.mk
new file mode 100644
index 000000000000..737403f94525
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-func-empty.mk
@@ -0,0 +1,8 @@
+# $NetBSD: cond-func-empty.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the empty() function in .if conditions.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-func-exists.exp b/contrib/bmake/unit-tests/cond-func-exists.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-func-exists.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-func-exists.mk b/contrib/bmake/unit-tests/cond-func-exists.mk
new file mode 100644
index 000000000000..6386f21fdc7b
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-func-exists.mk
@@ -0,0 +1,42 @@
+# $NetBSD: cond-func-exists.mk,v 1.4 2020/08/28 12:59:36 rillig Exp $
+#
+# Tests for the exists() function in .if conditions.
+
+.if !exists(.)
+.error
+.endif
+
+# The argument to the function must not be enclosed in quotes.
+# Neither double quotes nor single quotes are allowed.
+.if exists(".")
+.error
+.endif
+
+.if exists('.')
+.error
+.endif
+
+# The only way to escape characters that would otherwise influence the parser
+# is to enclose them in a variable expression. For function arguments,
+# neither the backslash nor the dollar sign act as escape character.
+.if exists(\.)
+.error
+.endif
+
+.if !exists(${:U.})
+.error
+.endif
+
+# The argument to the function can have several variable expressions.
+# See cond-func.mk for the characters that cannot be used directly.
+.if !exists(${.PARSEDIR}/${.PARSEFILE})
+.error
+.endif
+
+# Whitespace is trimmed on both sides of the function argument.
+.if !exists( . )
+.error
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-func-make.exp b/contrib/bmake/unit-tests/cond-func-make.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-func-make.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-func-make.mk b/contrib/bmake/unit-tests/cond-func-make.mk
new file mode 100644
index 000000000000..baa0d37da726
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-func-make.mk
@@ -0,0 +1,8 @@
+# $NetBSD: cond-func-make.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the make() function in .if conditions.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-func-target.exp b/contrib/bmake/unit-tests/cond-func-target.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-func-target.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-func-target.mk b/contrib/bmake/unit-tests/cond-func-target.mk
new file mode 100644
index 000000000000..c36bcfe0a5f8
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-func-target.mk
@@ -0,0 +1,38 @@
+# $NetBSD: cond-func-target.mk,v 1.3 2020/08/23 14:07:20 rillig Exp $
+#
+# Tests for the target() function in .if conditions.
+
+.MAIN: all
+
+# The target "target" does not exist yet.
+.if target(target)
+.error
+.endif
+
+target:
+
+# The target exists, even though it does not have any commands.
+.if !target(target)
+.error
+.endif
+
+target:
+ # not a command
+
+# Adding a comment to an existing target does not change whether the target
+# is defined or not.
+.if !target(target)
+.error
+.endif
+
+target:
+ @:;
+
+# Adding a command to an existing target does not change whether the target
+# is defined or not.
+.if !target(target)
+.error
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-func.exp b/contrib/bmake/unit-tests/cond-func.exp
new file mode 100644
index 000000000000..0069ed75726c
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-func.exp
@@ -0,0 +1,9 @@
+make: "cond-func.mk" line 29: warning: Missing closing parenthesis for defined()
+make: "cond-func.mk" line 29: Malformed conditional (!defined(A B))
+make: "cond-func.mk" line 44: warning: Missing closing parenthesis for defined()
+make: "cond-func.mk" line 44: Malformed conditional (!defined(A&B))
+make: "cond-func.mk" line 47: warning: Missing closing parenthesis for defined()
+make: "cond-func.mk" line 47: Malformed conditional (!defined(A|B))
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/cond-func.mk b/contrib/bmake/unit-tests/cond-func.mk
new file mode 100644
index 000000000000..304735241e7f
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-func.mk
@@ -0,0 +1,63 @@
+# $NetBSD: cond-func.mk,v 1.1 2020/08/20 17:45:47 rillig Exp $
+#
+# Tests for those parts of the functions in .if conditions that are common
+# among several functions.
+#
+# The below test uses the function defined(...) since it has no side-effects,
+# the other functions (except empty(...)) would work equally well.
+
+DEF= defined
+${:UA B}= variable name with spaces
+${:UVAR(value)}= variable name with parentheses
+${:UVAR{value}}= variable name with braces
+
+.if !defined(DEF)
+.error
+.endif
+
+# Horizontal whitespace after the opening parenthesis is ignored.
+.if !defined( DEF)
+.error
+.endif
+
+# Horizontal whitespace before the closing parenthesis is ignored.
+.if !defined(DEF )
+.error
+.endif
+
+# The argument of a function must not directly contain whitespace.
+.if !defined(A B)
+.error
+.endif
+
+# If necessary, the whitespace can be generated by a variable expression.
+.if !defined(${:UA B})
+.error
+.endif
+
+# Characters that could be mistaken for operators must not appear directly
+# in a function argument. As with whitespace, these can be generated
+# indirectly.
+#
+# It's not entirely clear why these characters are forbidden.
+# The most plausible reason seems to be typo detection.
+.if !defined(A&B)
+.error
+.endif
+.if !defined(A|B)
+.error
+.endif
+
+# Even parentheses may appear in variable names.
+# They must be balanced though.
+.if !defined(VAR(value))
+.error
+.endif
+
+# Braces do not have any special meaning when parsing arguments.
+.if !defined(VAR{value})
+.error
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-late.exp b/contrib/bmake/unit-tests/cond-late.exp
index 3717b088c646..46c4aa2f4230 100644
--- a/contrib/bmake/unit-tests/cond-late.exp
+++ b/contrib/bmake/unit-tests/cond-late.exp
@@ -1,3 +1,4 @@
+make: Bad conditional expression ` != "no"' in != "no"?:
yes
no
exit status 0
diff --git a/contrib/bmake/unit-tests/cond-late.mk b/contrib/bmake/unit-tests/cond-late.mk
index 2eb02b42b920..397f5febd480 100644
--- a/contrib/bmake/unit-tests/cond-late.mk
+++ b/contrib/bmake/unit-tests/cond-late.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-late.mk,v 1.1 2020/04/29 23:15:21 rillig Exp $
+# $NetBSD: cond-late.mk,v 1.2 2020/07/25 20:37:46 rillig Exp $
#
# Using the :? modifier, variable expressions can contain conditional
# expressions that are evaluated late. Any variables appearing in these
@@ -15,9 +15,15 @@
# and then expand the variables, the output would change from the
# current "yes no" to "yes yes", since both variables are non-empty.
+all: cond-literal
+
COND.true= "yes" == "yes"
COND.false= "yes" != "yes"
-all:
+cond-literal:
@echo ${ ${COND.true} :?yes:no}
@echo ${ ${COND.false} :?yes:no}
+
+VAR+= ${${UNDEF} != "no":?:}
+.if empty(VAR:Mpattern)
+.endif
diff --git a/contrib/bmake/unit-tests/cond-op-and.exp b/contrib/bmake/unit-tests/cond-op-and.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-op-and.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-op-and.mk b/contrib/bmake/unit-tests/cond-op-and.mk
new file mode 100644
index 000000000000..a204a227c4f0
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-op-and.mk
@@ -0,0 +1,27 @@
+# $NetBSD: cond-op-and.mk,v 1.3 2020/08/28 14:48:37 rillig Exp $
+#
+# Tests for the && operator in .if conditions.
+
+.if 0 && 0
+.error
+.endif
+
+.if 1 && 0
+.error
+.endif
+
+.if 0 && 1
+.error
+.endif
+
+.if !(1 && 1)
+.error
+.endif
+
+# The right-hand side is not evaluated since the left-hand side is already
+# false.
+.if 0 && ${UNDEF}
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-op-not.exp b/contrib/bmake/unit-tests/cond-op-not.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-op-not.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-op-not.mk b/contrib/bmake/unit-tests/cond-op-not.mk
new file mode 100644
index 000000000000..ad0a0939eecf
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-op-not.mk
@@ -0,0 +1,22 @@
+# $NetBSD: cond-op-not.mk,v 1.3 2020/08/28 14:48:37 rillig Exp $
+#
+# Tests for the ! operator in .if conditions.
+
+# The exclamation mark negates its operand.
+.if !1
+.error
+.endif
+
+# Exclamation marks can be chained.
+# This doesn't happen in practice though.
+.if !!!1
+.error
+.endif
+
+# The ! binds more tightly than the &&.
+.if !!0 && 1
+.error
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-op-or.exp b/contrib/bmake/unit-tests/cond-op-or.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-op-or.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-op-or.mk b/contrib/bmake/unit-tests/cond-op-or.mk
new file mode 100644
index 000000000000..696b9dd23062
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-op-or.mk
@@ -0,0 +1,27 @@
+# $NetBSD: cond-op-or.mk,v 1.3 2020/08/28 14:48:37 rillig Exp $
+#
+# Tests for the || operator in .if conditions.
+
+.if 0 || 0
+.error
+.endif
+
+.if !(1 || 0)
+.error
+.endif
+
+.if !(0 || 1)
+.error
+.endif
+
+.if !(1 || 1)
+.error
+.endif
+
+# The right-hand side is not evaluated since the left-hand side is already
+# true.
+.if 1 || ${UNDEF}
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-op-parentheses.exp b/contrib/bmake/unit-tests/cond-op-parentheses.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-op-parentheses.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-op-parentheses.mk b/contrib/bmake/unit-tests/cond-op-parentheses.mk
new file mode 100644
index 000000000000..6c48d83dd2be
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-op-parentheses.mk
@@ -0,0 +1,8 @@
+# $NetBSD: cond-op-parentheses.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for parentheses in .if conditions.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-op.exp b/contrib/bmake/unit-tests/cond-op.exp
new file mode 100644
index 000000000000..4caec4df659b
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-op.exp
@@ -0,0 +1,5 @@
+make: "cond-op.mk" line 45: Malformed conditional ("!word" == !word)
+make: "cond-op.mk" line 57: Parsing continues until here.
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/cond-op.mk b/contrib/bmake/unit-tests/cond-op.mk
new file mode 100644
index 000000000000..a22e4181e8fe
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-op.mk
@@ -0,0 +1,60 @@
+# $NetBSD: cond-op.mk,v 1.4 2020/08/28 14:07:51 rillig Exp $
+#
+# Tests for operators like &&, ||, ! in .if conditions.
+#
+# See also:
+# cond-op-and.mk
+# cond-op-not.mk
+# cond-op-or.mk
+# cond-op-parentheses.mk
+
+# In make, && binds more tightly than ||, like in C.
+# If make had the same precedence for both && and ||, the result would be
+# different.
+# If || were to bind more tightly than &&, the result would be different
+# as well.
+.if !(1 || 1 && 0)
+.error
+.endif
+
+# If make were to interpret the && and || operators like the shell, the
+# implicit binding would be this:
+.if (1 || 1) && 0
+.error
+.endif
+
+# The precedence of the ! operator is different from C though. It has a
+# lower precedence than the comparison operators.
+.if !"word" == "word"
+.error
+.endif
+
+# This is how the above condition is actually interpreted.
+.if !("word" == "word")
+.error
+.endif
+
+# TODO: Demonstrate that the precedence of the ! and == operators actually
+# makes a difference. There is a simple example for sure, I just cannot
+# wrap my head around it.
+
+# This condition is malformed because the '!' on the right-hand side must not
+# appear unquoted. If any, it must be enclosed in quotes.
+# In any case, it is not interpreted as a negation of an unquoted string.
+# See CondGetString.
+.if "!word" == !word
+.error
+.endif
+
+# Surprisingly, the ampersand and pipe are allowed in bare strings.
+# That's another opportunity for writing confusing code.
+# See CondGetString, which only has '!' in the list of stop characters.
+.if "a&&b||c" != a&&b||c
+.error
+.endif
+
+# Just in case that parsing should ever stop on the first error.
+.info Parsing continues until here.
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-short.mk b/contrib/bmake/unit-tests/cond-short.mk
index 41d76768a404..ae441d80f7d9 100644
--- a/contrib/bmake/unit-tests/cond-short.mk
+++ b/contrib/bmake/unit-tests/cond-short.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-short.mk,v 1.7 2020/07/09 22:34:09 sjg Exp $
+# $NetBSD: cond-short.mk,v 1.9 2020/08/19 22:47:09 rillig Exp $
#
# Demonstrates that in conditions, the right-hand side of an && or ||
# is only evaluated if it can actually influence the result.
@@ -66,6 +66,19 @@ VAR= # empty again, for the following tests
.if 0 && !empty(:U${:!echo "unexpected exclam modifier" 1>&2 !})
.endif
+# Irrelevant assignment modifiers are skipped as well.
+.if 0 && ${1 2 3:L:@i@${FIRST::?=$i}@}
+.endif
+.if 0 && ${1 2 3:L:@i@${LAST::=$i}@}
+.endif
+.if 0 && ${1 2 3:L:@i@${APPENDED::+=$i}@}
+.endif
+.if 0 && ${echo.1 echo.2 echo.3:L:@i@${RAN::!=${i:C,.*,&; & 1>\&2,:S,., ,g}}@}
+.endif
+.if defined(FIRST) || defined(LAST) || defined(APPENDED) || defined(RAN)
+.warning first=${FIRST} last=${LAST} appended=${APPENDED} ran=${RAN}
+.endif
+
# The || operator.
.if 1 || ${echo "unexpected or" 1>&2 :L:sh}
@@ -110,6 +123,7 @@ x=Ok
x=Fail
.endif
x!= echo 'defined(V42) && ${V42} > 0: $x' >&2; echo
+
# this one throws both String comparison operator and
# Malformed conditional with cond.c 1.78
# indirect iV2 would expand to "" and treated as 0
@@ -119,6 +133,7 @@ x=Fail
x=Ok
.endif
x!= echo 'defined(V66) && ( "${iV2}" < ${V42} ): $x' >&2; echo
+
# next two thow String comparison operator with cond.c 1.78
# indirect iV1 would expand to 42
.if 1 || ${iV1} < ${V42}
@@ -127,12 +142,14 @@ x=Ok
x=Fail
.endif
x!= echo '1 || ${iV1} < ${V42}: $x' >&2; echo
+
.if 1 || ${iV2:U2} < ${V42}
x=Ok
.else
x=Fail
.endif
x!= echo '1 || ${iV2:U2} < ${V42}: $x' >&2; echo
+
# the same expressions are fine when the lhs is expanded
# ${iV1} expands to 42
.if 0 || ${iV1} <= ${V42}
@@ -141,6 +158,7 @@ x=Ok
x=Fail
.endif
x!= echo '0 || ${iV1} <= ${V42}: $x' >&2; echo
+
# ${iV2:U2} expands to 2
.if 0 || ${iV2:U2} < ${V42}
x=Ok
diff --git a/contrib/bmake/unit-tests/cond-token-number.exp b/contrib/bmake/unit-tests/cond-token-number.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-token-number.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-token-number.mk b/contrib/bmake/unit-tests/cond-token-number.mk
new file mode 100644
index 000000000000..30e7e84e81bb
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-token-number.mk
@@ -0,0 +1,8 @@
+# $NetBSD: cond-token-number.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for number tokens in .if conditions.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-token-plain.exp b/contrib/bmake/unit-tests/cond-token-plain.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-token-plain.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-token-plain.mk b/contrib/bmake/unit-tests/cond-token-plain.mk
new file mode 100644
index 000000000000..fc13245382f1
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-token-plain.mk
@@ -0,0 +1,9 @@
+# $NetBSD: cond-token-plain.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for plain tokens (that is, string literals without quotes)
+# in .if conditions.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-token-string.exp b/contrib/bmake/unit-tests/cond-token-string.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-token-string.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/cond-token-string.mk b/contrib/bmake/unit-tests/cond-token-string.mk
new file mode 100644
index 000000000000..1a8019754824
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-token-string.mk
@@ -0,0 +1,8 @@
+# $NetBSD: cond-token-string.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for quoted and unquoted string literals in .if conditions.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/cond-token-var.exp b/contrib/bmake/unit-tests/cond-token-var.exp
new file mode 100644
index 000000000000..eb71a43c55f3
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-token-var.exp
@@ -0,0 +1,7 @@
+make: "cond-token-var.mk" line 9: ok
+make: "cond-token-var.mk" line 15: Malformed conditional (${UNDEF} == ${DEF})
+make: "cond-token-var.mk" line 20: Malformed conditional (${DEF} == ${UNDEF})
+make: "cond-token-var.mk" line 29: Malformed conditional (${UNDEF})
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/cond-token-var.mk b/contrib/bmake/unit-tests/cond-token-var.mk
new file mode 100644
index 000000000000..5d5bbf2c7fc4
--- /dev/null
+++ b/contrib/bmake/unit-tests/cond-token-var.mk
@@ -0,0 +1,34 @@
+# $NetBSD: cond-token-var.mk,v 1.3 2020/08/20 19:43:42 rillig Exp $
+#
+# Tests for variables in .if conditions.
+
+DEF= defined
+
+# A defined variable may appear on either side of the comparison.
+.if ${DEF} == ${DEF}
+.info ok
+.else
+.error
+.endif
+
+# A variable that appears on the left-hand side must be defined.
+.if ${UNDEF} == ${DEF}
+.error
+.endif
+
+# A variable that appears on the right-hand side must be defined.
+.if ${DEF} == ${UNDEF}
+.error
+.endif
+
+# A defined variable may appear as an expression of its own.
+.if ${DEF}
+.endif
+
+# An undefined variable generates a warning.
+.if ${UNDEF}
+.endif
+
+# The :U modifier turns an undefined variable into an ordinary expression.
+.if ${UNDEF:U}
+.endif
diff --git a/contrib/bmake/unit-tests/counter.exp b/contrib/bmake/unit-tests/counter.exp
new file mode 100644
index 000000000000..c00b3e57056d
--- /dev/null
+++ b/contrib/bmake/unit-tests/counter.exp
@@ -0,0 +1,88 @@
+Global:RELEVANT = yes (load-time part)
+Global:COUNTER =
+Global:NEXT = ${COUNTER::=${COUNTER} a}${COUNTER:[#]}
+Global:A =
+Var_Parse: ${NEXT} with VARE_WANTRES|VARE_ASSIGN
+Var_Parse: ${COUNTER::=${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN
+Applying ${COUNTER::...} to "" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none)
+Var_Parse: ${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES
+Modifier part: " a"
+Global:COUNTER = a
+Result of ${COUNTER::=${COUNTER} a} is "" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none)
+Var_Parse: ${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN
+Var_Parse: ${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN
+Applying ${COUNTER:[...} to " a" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none)
+Modifier part: "#"
+Result of ${COUNTER:[#]} is "1" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none)
+Global:A = ${COUNTER::= a a}1
+Global:B =
+Var_Parse: ${NEXT} with VARE_WANTRES|VARE_ASSIGN
+Var_Parse: ${COUNTER::=${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN
+Applying ${COUNTER::...} to " a" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none)
+Var_Parse: ${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES
+Modifier part: " a a"
+Global:COUNTER = a a
+Result of ${COUNTER::=${COUNTER} a} is "" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none)
+Var_Parse: ${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN
+Var_Parse: ${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN
+Applying ${COUNTER:[...} to " a a" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none)
+Modifier part: "#"
+Result of ${COUNTER:[#]} is "2" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none)
+Global:B = ${COUNTER::= a a a}2
+Global:C =
+Var_Parse: ${NEXT} with VARE_WANTRES|VARE_ASSIGN
+Var_Parse: ${COUNTER::=${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN
+Applying ${COUNTER::...} to " a a" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none)
+Var_Parse: ${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES
+Modifier part: " a a a"
+Global:COUNTER = a a a
+Result of ${COUNTER::=${COUNTER} a} is "" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none)
+Var_Parse: ${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN
+Var_Parse: ${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN
+Applying ${COUNTER:[...} to " a a a" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none)
+Modifier part: "#"
+Result of ${COUNTER:[#]} is "3" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none)
+Global:C = ${COUNTER::= a a a a}3
+Global:RELEVANT = no
+Global:RELEVANT = yes (run-time part)
+Result of ${RELEVANT::=yes (run-time part)} is "" (eflags = VARE_WANTRES, vflags = none)
+Var_Parse: ${A:Q} B=${B:Q} C=${C:Q} COUNTER=${COUNTER:[#]:Q} with VARE_WANTRES
+Var_Parse: ${COUNTER::= a a}1 with VARE_WANTRES
+Applying ${COUNTER::...} to " a a a" (eflags = VARE_WANTRES, vflags = none)
+Modifier part: " a a"
+Global:COUNTER = a a
+Result of ${COUNTER::= a a} is "" (eflags = VARE_WANTRES, vflags = none)
+Applying ${A:Q} to "1" (eflags = VARE_WANTRES, vflags = none)
+QuoteMeta: [1]
+Result of ${A:Q} is "1" (eflags = VARE_WANTRES, vflags = none)
+Var_Parse: ${B:Q} C=${C:Q} COUNTER=${COUNTER:[#]:Q} with VARE_WANTRES
+Var_Parse: ${COUNTER::= a a a}2 with VARE_WANTRES
+Applying ${COUNTER::...} to " a a" (eflags = VARE_WANTRES, vflags = none)
+Modifier part: " a a a"
+Global:COUNTER = a a a
+Result of ${COUNTER::= a a a} is "" (eflags = VARE_WANTRES, vflags = none)
+Applying ${B:Q} to "2" (eflags = VARE_WANTRES, vflags = none)
+QuoteMeta: [2]
+Result of ${B:Q} is "2" (eflags = VARE_WANTRES, vflags = none)
+Var_Parse: ${C:Q} COUNTER=${COUNTER:[#]:Q} with VARE_WANTRES
+Var_Parse: ${COUNTER::= a a a a}3 with VARE_WANTRES
+Applying ${COUNTER::...} to " a a a" (eflags = VARE_WANTRES, vflags = none)
+Modifier part: " a a a a"
+Global:COUNTER = a a a a
+Result of ${COUNTER::= a a a a} is "" (eflags = VARE_WANTRES, vflags = none)
+Applying ${C:Q} to "3" (eflags = VARE_WANTRES, vflags = none)
+QuoteMeta: [3]
+Result of ${C:Q} is "3" (eflags = VARE_WANTRES, vflags = none)
+Var_Parse: ${COUNTER:[#]:Q} with VARE_WANTRES
+Applying ${COUNTER:[...} to " a a a a" (eflags = VARE_WANTRES, vflags = none)
+Modifier part: "#"
+Result of ${COUNTER:[#]} is "4" (eflags = VARE_WANTRES, vflags = none)
+Applying ${COUNTER:Q} to "4" (eflags = VARE_WANTRES, vflags = none)
+QuoteMeta: [4]
+Result of ${COUNTER:Q} is "4" (eflags = VARE_WANTRES, vflags = none)
+A=1 B=2 C=3 COUNTER=4
+Var_Parse: ${RELEVANT::=no} with VARE_WANTRES
+Applying ${RELEVANT::...} to "yes (run-time part)" (eflags = VARE_WANTRES, vflags = none)
+Modifier part: "no"
+Global:RELEVANT = no
+exit status 0
diff --git a/contrib/bmake/unit-tests/counter.mk b/contrib/bmake/unit-tests/counter.mk
new file mode 100644
index 000000000000..23e7dcc0e8d2
--- /dev/null
+++ b/contrib/bmake/unit-tests/counter.mk
@@ -0,0 +1,31 @@
+# $NetBSD: counter.mk,v 1.1 2020/08/02 14:53:02 rillig Exp $
+#
+# Demonstrates that it is not easily possible to let make count
+# the number of times a variable is actually accessed.
+#
+# As of 2020-08-02, the counter ends up at having 4 words, even
+# though the NEXT variable is only accessed 3 times. This is
+# surprising.
+#
+# A hint to this surprising behavior is that the variables don't
+# get fully expanded. For example, A does not simply contain the
+# value "1" but an additional unexpanded ${COUNTER:...} before it.
+
+RELEVANT= yes (load-time part) # just to filter the output
+
+COUNTER= # zero
+
+NEXT= ${COUNTER::=${COUNTER} a}${COUNTER:[#]}
+
+# This variable is first set to empty and then expanded.
+# See parse.c, function Parse_DoVar, keyword "!Var_Exists".
+A:= ${NEXT}
+B:= ${NEXT}
+C:= ${NEXT}
+
+RELEVANT= no
+
+all:
+ @: ${RELEVANT::=yes (run-time part)}
+ @echo A=${A:Q} B=${B:Q} C=${C:Q} COUNTER=${COUNTER:[#]:Q}
+ @: ${RELEVANT::=no}
diff --git a/contrib/bmake/unit-tests/dep-colon.exp b/contrib/bmake/unit-tests/dep-colon.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep-colon.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/dep-colon.mk b/contrib/bmake/unit-tests/dep-colon.mk
new file mode 100644
index 000000000000..7355c59ae557
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep-colon.mk
@@ -0,0 +1,8 @@
+# $NetBSD: dep-colon.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the : operator in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/dep-double-colon.exp b/contrib/bmake/unit-tests/dep-double-colon.exp
new file mode 100644
index 000000000000..5528abbab32d
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep-double-colon.exp
@@ -0,0 +1,5 @@
+command 1a
+command 1b
+command 2a
+command 2b
+exit status 0
diff --git a/contrib/bmake/unit-tests/dep-double-colon.mk b/contrib/bmake/unit-tests/dep-double-colon.mk
new file mode 100644
index 000000000000..de4cd9bc9e33
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep-double-colon.mk
@@ -0,0 +1,11 @@
+# $NetBSD: dep-double-colon.mk,v 1.3 2020/08/22 12:42:32 rillig Exp $
+#
+# Tests for the :: operator in dependency declarations.
+
+all::
+ @echo 'command 1a'
+ @echo 'command 1b'
+
+all::
+ @echo 'command 2a'
+ @echo 'command 2b'
diff --git a/contrib/bmake/unit-tests/dep-exclam.exp b/contrib/bmake/unit-tests/dep-exclam.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep-exclam.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/dep-exclam.mk b/contrib/bmake/unit-tests/dep-exclam.mk
new file mode 100644
index 000000000000..2779a66ea6e3
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep-exclam.mk
@@ -0,0 +1,8 @@
+# $NetBSD: dep-exclam.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the ! operator in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/dep-none.exp b/contrib/bmake/unit-tests/dep-none.exp
new file mode 100755
index 000000000000..0d299fcca8d3
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep-none.exp
@@ -0,0 +1,4 @@
+make: no target to make.
+
+make: stopped in unit-tests
+exit status 2
diff --git a/contrib/bmake/unit-tests/dep-none.mk b/contrib/bmake/unit-tests/dep-none.mk
new file mode 100755
index 000000000000..74b50aa29051
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep-none.mk
@@ -0,0 +1,3 @@
+# $NetBSD: dep-none.mk,v 1.1 2020/08/22 12:51:11 rillig Exp $
+#
+# Test for a Makefile that declares no target at all.
diff --git a/contrib/bmake/unit-tests/dep-var.exp b/contrib/bmake/unit-tests/dep-var.exp
new file mode 100755
index 000000000000..819a939ed70a
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep-var.exp
@@ -0,0 +1,2 @@
+def2
+exit status 0
diff --git a/contrib/bmake/unit-tests/dep-var.mk b/contrib/bmake/unit-tests/dep-var.mk
new file mode 100755
index 000000000000..b0ce4b2c53ae
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep-var.mk
@@ -0,0 +1,33 @@
+# $NetBSD: dep-var.mk,v 1.1 2020/08/22 16:51:26 rillig Exp $
+#
+# Tests for variable references in dependency declarations.
+#
+# Uh oh, this feels so strange that probably nobody uses it. But it seems to
+# be the only way to reach the lower half of SuffExpandChildren.
+
+# XXX: The -dv log says:
+# Var_Parse: ${UNDEF1} with VARE_UNDEFERR|VARE_WANTRES
+# but no error message is generated for this line.
+# The variable expression ${UNDEF1} simply expands to an empty string.
+all: ${UNDEF1}
+
+# Using a double dollar in order to circumvent immediate variable expansion
+# feels like unintended behavior. At least the manual page says nothing at
+# all about defined or undefined variables in dependency lines.
+#
+# At the point where the expression ${DEF2} is expanded, the variable DEF2
+# is defined, so everything's fine.
+all: $${DEF2}
+
+# This variable is not defined at all.
+# XXX: The -dv log says:
+# Var_Parse: ${UNDEF3} with VARE_UNDEFERR|VARE_WANTRES
+# but no error message is generated for this line, just like for UNDEF1.
+# The variable expression ${UNDEF3} simply expands to an empty string.
+all: $${UNDEF3}
+
+UNDEF1= undef1
+DEF2= def2
+
+undef1 def2:
+ @echo ${.TARGET}
diff --git a/contrib/bmake/unit-tests/dep-wildcards.exp b/contrib/bmake/unit-tests/dep-wildcards.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep-wildcards.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/dep-wildcards.mk b/contrib/bmake/unit-tests/dep-wildcards.mk
new file mode 100644
index 000000000000..c557efab3ebc
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep-wildcards.mk
@@ -0,0 +1,8 @@
+# $NetBSD: dep-wildcards.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for wildcards such as *.c in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/dep.exp b/contrib/bmake/unit-tests/dep.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/dep.mk b/contrib/bmake/unit-tests/dep.mk
new file mode 100644
index 000000000000..b2463dfc6458
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep.mk
@@ -0,0 +1,8 @@
+# $NetBSD: dep.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for dependency declarations, such as "target: sources".
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/depsrc-exec.exp b/contrib/bmake/unit-tests/depsrc-exec.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-exec.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-exec.mk b/contrib/bmake/unit-tests/depsrc-exec.mk
new file mode 100644
index 000000000000..a71284d33b3c
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-exec.mk
@@ -0,0 +1,8 @@
+# $NetBSD: depsrc-exec.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special source .EXEC in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/depsrc-ignore.exp b/contrib/bmake/unit-tests/depsrc-ignore.exp
new file mode 100644
index 000000000000..162f10ddc17b
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-ignore.exp
@@ -0,0 +1,11 @@
+ignore-errors begin
+false ignore-errors
+ignore-errors end
+all begin
+*** Error code 1 (ignored)
+false all
+*** Error code 1 (continuing)
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/depsrc-ignore.mk b/contrib/bmake/unit-tests/depsrc-ignore.mk
new file mode 100644
index 000000000000..1be3eabe8806
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-ignore.mk
@@ -0,0 +1,67 @@
+# $NetBSD: depsrc-ignore.mk,v 1.4 2020/08/29 16:13:27 rillig Exp $
+#
+# Tests for the special source .IGNORE in dependency declarations,
+# which ignores any command failures for that target.
+#
+# Even though ignore-errors fails, the all target is still made.
+# Since the all target is not marked with .IGNORE, it stops at the
+# first failing command.
+#
+# XXX: The ordering of the messages in the output is confusing.
+# The "ignored" comes much too late to be related to the "false
+# ignore-errors". This is due to stdout being buffered.
+#
+# The "continuing" message comes from the -k option. If there had been
+# other targets independent of "all", these would be built as well.
+#
+# Enabling the debugging option -de changes the order in which the messages
+# appear. Now the "ignored" message is issued in the correct position.
+# The explanation for the output reordering is that the output is buffered.
+# As the manual page says, in debugging mode stdout is line buffered.
+# In these tests the output is redirected to a file, therefore stdout is
+# fully buffered.
+#
+# This is what actually happens, as of 2020-08-29. To verify it, set the
+# following breakpoints in CompatRunCommand:
+#
+# * the "!silent" line, to see all commands.
+# * the "fflush" line, to see stdout being flushed.
+# * the "status = WEXITSTATUS" line
+# * the "(continuing)" line
+# * the "(ignored)" line
+#
+# The breakpoints are visited in the following order:
+#
+# "ignore-errors begin"
+# Goes directly to STDOUT_FILENO since it is run in a child process.
+# "false ignore-errors"
+# Goes to the stdout buffer (CompatRunCommand, keyword "!silent") and
+# the immediate call to fflush(stdout) copies it to STDOUT_FILENO.
+# "*** Error code 1 (ignored)"
+# Goes to the stdout buffer but is not flushed (CompatRunCommand, near
+# the end).
+# "ignore-errors end"
+# Goes directly to STDOUT_FILENO.
+# "all begin"
+# Goes directly to STDOUT_FILENO.
+# "false all"
+# Goes to the stdout buffer, where the "*** Error code 1 (ignored)" is
+# still waiting to be flushed. These two lines are flushed now.
+# "*** Error code 1 (continuing)"
+# Goes to the stdout buffer.
+# "Stop."
+# Goes to the stdout buffer.
+# exit(1)
+# Flushes the stdout buffer to STDOUT_FILENO.
+
+all: ignore-errors
+
+ignore-errors: .IGNORE
+ @echo $@ begin
+ false $@
+ @echo $@ end
+
+all:
+ @echo $@ begin
+ false $@
+ @echo $@ end
diff --git a/contrib/bmake/unit-tests/depsrc-made.exp b/contrib/bmake/unit-tests/depsrc-made.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-made.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-made.mk b/contrib/bmake/unit-tests/depsrc-made.mk
new file mode 100644
index 000000000000..9adeb2487496
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-made.mk
@@ -0,0 +1,8 @@
+# $NetBSD: depsrc-made.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special source .MADE in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/depsrc-make.exp b/contrib/bmake/unit-tests/depsrc-make.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-make.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-make.mk b/contrib/bmake/unit-tests/depsrc-make.mk
new file mode 100644
index 000000000000..9631f3e913d0
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-make.mk
@@ -0,0 +1,8 @@
+# $NetBSD: depsrc-make.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special source .MAKE in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/depsrc-meta.exp b/contrib/bmake/unit-tests/depsrc-meta.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-meta.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-meta.mk b/contrib/bmake/unit-tests/depsrc-meta.mk
new file mode 100644
index 000000000000..6adef4baa5de
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-meta.mk
@@ -0,0 +1,8 @@
+# $NetBSD: depsrc-meta.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special source .META in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/depsrc-nometa.exp b/contrib/bmake/unit-tests/depsrc-nometa.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-nometa.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-nometa.mk b/contrib/bmake/unit-tests/depsrc-nometa.mk
new file mode 100644
index 000000000000..1942383856ab
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-nometa.mk
@@ -0,0 +1,8 @@
+# $NetBSD: depsrc-nometa.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special source .NOMETA in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/depsrc-nometa_cmp.exp b/contrib/bmake/unit-tests/depsrc-nometa_cmp.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-nometa_cmp.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-nometa_cmp.mk b/contrib/bmake/unit-tests/depsrc-nometa_cmp.mk
new file mode 100644
index 000000000000..a0032d2c812a
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-nometa_cmp.mk
@@ -0,0 +1,8 @@
+# $NetBSD: depsrc-nometa_cmp.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special source .NOMETA_CMP in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/depsrc-nopath.exp b/contrib/bmake/unit-tests/depsrc-nopath.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-nopath.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-nopath.mk b/contrib/bmake/unit-tests/depsrc-nopath.mk
new file mode 100644
index 000000000000..052c6f10db66
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-nopath.mk
@@ -0,0 +1,8 @@
+# $NetBSD: depsrc-nopath.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special source .NOPATH in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/depsrc-notmain.exp b/contrib/bmake/unit-tests/depsrc-notmain.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-notmain.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-notmain.mk b/contrib/bmake/unit-tests/depsrc-notmain.mk
new file mode 100644
index 000000000000..6e4a68c3b70d
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-notmain.mk
@@ -0,0 +1,8 @@
+# $NetBSD: depsrc-notmain.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special source .NOTMAIN in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/depsrc-optional.exp b/contrib/bmake/unit-tests/depsrc-optional.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-optional.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-optional.mk b/contrib/bmake/unit-tests/depsrc-optional.mk
new file mode 100644
index 000000000000..074dd0b4ef8f
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-optional.mk
@@ -0,0 +1,8 @@
+# $NetBSD: depsrc-optional.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special source .OPTIONAL in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/depsrc-phony.exp b/contrib/bmake/unit-tests/depsrc-phony.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-phony.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-phony.mk b/contrib/bmake/unit-tests/depsrc-phony.mk
new file mode 100644
index 000000000000..73186e81a5f9
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-phony.mk
@@ -0,0 +1,8 @@
+# $NetBSD: depsrc-phony.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special source .PHONY in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/depsrc-precious.exp b/contrib/bmake/unit-tests/depsrc-precious.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-precious.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-precious.mk b/contrib/bmake/unit-tests/depsrc-precious.mk
new file mode 100644
index 000000000000..699b83d767b1
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-precious.mk
@@ -0,0 +1,8 @@
+# $NetBSD: depsrc-precious.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special source .PRECIOUS in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/depsrc-recursive.exp b/contrib/bmake/unit-tests/depsrc-recursive.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-recursive.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-recursive.mk b/contrib/bmake/unit-tests/depsrc-recursive.mk
new file mode 100644
index 000000000000..5c629338bc8d
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-recursive.mk
@@ -0,0 +1,8 @@
+# $NetBSD: depsrc-recursive.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special source .RECURSIVE in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/depsrc-silent.exp b/contrib/bmake/unit-tests/depsrc-silent.exp
new file mode 100644
index 000000000000..1ea493702c3b
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-silent.exp
@@ -0,0 +1,4 @@
+one
+two
+three
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-silent.mk b/contrib/bmake/unit-tests/depsrc-silent.mk
new file mode 100644
index 000000000000..98da7b387906
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-silent.mk
@@ -0,0 +1,12 @@
+# $NetBSD: depsrc-silent.mk,v 1.3 2020/08/29 17:34:21 rillig Exp $
+#
+# Tests for the special source .SILENT in dependency declarations,
+# which hides the commands, no matter whether they are prefixed with
+# '@' or not.
+
+# Without the .SILENT, the commands 'echo one' and 'echo two' would be
+# written to stdout.
+all: .SILENT
+ echo one
+ echo two
+ @echo three
diff --git a/contrib/bmake/unit-tests/depsrc-use.exp b/contrib/bmake/unit-tests/depsrc-use.exp
new file mode 100644
index 000000000000..c9810bda462d
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-use.exp
@@ -0,0 +1,6 @@
+first 1
+first 2
+second 1
+second 2
+directly
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-use.mk b/contrib/bmake/unit-tests/depsrc-use.mk
new file mode 100644
index 000000000000..17836cd39e23
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-use.mk
@@ -0,0 +1,24 @@
+# $NetBSD: depsrc-use.mk,v 1.4 2020/08/22 12:30:57 rillig Exp $
+#
+# Tests for the special source .USE in dependency declarations,
+# which allows to append common commands to other targets.
+
+all: action directly
+
+first: .USE
+ @echo first 1 # Using ${.TARGET} here would expand to "action"
+ @echo first 2
+
+second: .USE
+ @echo second 1
+ @echo second 2
+
+# It's possible but uncommon to have a .USE target with no commands.
+# This may happen as the result of expanding a .for loop.
+empty: .USE
+
+# It's possible but uncommon to directly make a .USEBEFORE target.
+directly: .USE
+ @echo directly
+
+action: first second empty
diff --git a/contrib/bmake/unit-tests/depsrc-usebefore-double-colon.exp b/contrib/bmake/unit-tests/depsrc-usebefore-double-colon.exp
new file mode 100755
index 000000000000..8bbdef5ad326
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-usebefore-double-colon.exp
@@ -0,0 +1,2 @@
+double-colon early 1
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-usebefore-double-colon.mk b/contrib/bmake/unit-tests/depsrc-usebefore-double-colon.mk
new file mode 100755
index 000000000000..448214112d32
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-usebefore-double-colon.mk
@@ -0,0 +1,30 @@
+# $NetBSD: depsrc-usebefore-double-colon.mk,v 1.1 2020/08/22 08:29:13 rillig Exp $
+#
+# Tests for the special source .USEBEFORE in dependency declarations,
+# combined with the double-colon dependency operator.
+
+all: action
+
+# The dependency operator :: allows commands to be added later to the same
+# target.
+double-colon:: .USEBEFORE
+ @echo double-colon early 1
+
+# This command is ignored, which kind of makes sense since this dependency
+# declaration has no .USEBEFORE source.
+double-colon::
+ @echo double-colon early 2
+
+# XXX: This command is ignored even though it has a .USEBEFORE source.
+# This is unexpected.
+double-colon:: .USEBEFORE
+ @echo double-colon early 3
+
+# At this point, the commands from the .USEBEFORE targets are copied to
+# the "action" target.
+action: double-colon
+
+# This command is not added to the "action" target since it comes too late.
+# The commands had been copied in the previous line already.
+double-colon::
+ @echo double-colon late
diff --git a/contrib/bmake/unit-tests/depsrc-usebefore.exp b/contrib/bmake/unit-tests/depsrc-usebefore.exp
new file mode 100644
index 000000000000..c9810bda462d
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-usebefore.exp
@@ -0,0 +1,6 @@
+first 1
+first 2
+second 1
+second 2
+directly
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-usebefore.mk b/contrib/bmake/unit-tests/depsrc-usebefore.mk
new file mode 100644
index 000000000000..c6be2bae0a9c
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-usebefore.mk
@@ -0,0 +1,24 @@
+# $NetBSD: depsrc-usebefore.mk,v 1.5 2020/08/22 11:53:18 rillig Exp $
+#
+# Tests for the special source .USEBEFORE in dependency declarations,
+# which allows to prepend common commands to other targets.
+
+all: action directly
+
+first: .USEBEFORE
+ @echo first 1 # Using ${.TARGET} here would expand to "action"
+ @echo first 2 # Using ${.TARGET} here would expand to "action"
+
+second: .USEBEFORE
+ @echo second 1
+ @echo second 2
+
+# It is possible but uncommon to have a .USEBEFORE target with no commands.
+# This may happen as the result of expanding a .for loop.
+empty: .USEBEFORE
+
+# It is possible but uncommon to directly make a .USEBEFORE target.
+directly: .USEBEFORE
+ @echo directly
+
+action: second first empty
diff --git a/contrib/bmake/unit-tests/depsrc-wait.exp b/contrib/bmake/unit-tests/depsrc-wait.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-wait.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-wait.mk b/contrib/bmake/unit-tests/depsrc-wait.mk
new file mode 100644
index 000000000000..bdf3be4608bb
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc-wait.mk
@@ -0,0 +1,8 @@
+# $NetBSD: depsrc-wait.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special source .WAIT in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/depsrc.exp b/contrib/bmake/unit-tests/depsrc.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc.mk b/contrib/bmake/unit-tests/depsrc.mk
new file mode 100644
index 000000000000..d461e1111d0f
--- /dev/null
+++ b/contrib/bmake/unit-tests/depsrc.mk
@@ -0,0 +1,9 @@
+# $NetBSD: depsrc.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for special sources (those starting with a dot, followed by
+# uppercase letters) in dependency declarations, such as .PHONY.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-begin.exp b/contrib/bmake/unit-tests/deptgt-begin.exp
new file mode 100644
index 000000000000..527020f6818d
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-begin.exp
@@ -0,0 +1,4 @@
+: parse time
+: .BEGIN
+: all
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-begin.mk b/contrib/bmake/unit-tests/deptgt-begin.mk
new file mode 100644
index 000000000000..c6ca2f4aa3c7
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-begin.mk
@@ -0,0 +1,13 @@
+# $NetBSD: deptgt-begin.mk,v 1.3 2020/08/29 17:34:21 rillig Exp $
+#
+# Tests for the special target .BEGIN in dependency declarations,
+# which is a container for commands that are run before any other
+# commands from the shell lines.
+
+.BEGIN:
+ : $@
+
+all:
+ : $@
+
+_!= echo : parse time 1>&2
diff --git a/contrib/bmake/unit-tests/deptgt-default.exp b/contrib/bmake/unit-tests/deptgt-default.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-default.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-default.mk b/contrib/bmake/unit-tests/deptgt-default.mk
new file mode 100644
index 000000000000..814eaf72aed3
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-default.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-default.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .DEFAULT in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-delete_on_error.exp b/contrib/bmake/unit-tests/deptgt-delete_on_error.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-delete_on_error.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-delete_on_error.mk b/contrib/bmake/unit-tests/deptgt-delete_on_error.mk
new file mode 100644
index 000000000000..476bb8a54c52
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-delete_on_error.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-delete_on_error.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .DELETE_ON_ERROR in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-end.exp b/contrib/bmake/unit-tests/deptgt-end.exp
new file mode 100644
index 000000000000..3effe2e3182f
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-end.exp
@@ -0,0 +1,4 @@
+: .BEGIN
+: all
+: .END
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-end.mk b/contrib/bmake/unit-tests/deptgt-end.mk
new file mode 100644
index 000000000000..b3d59a610a2f
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-end.mk
@@ -0,0 +1,13 @@
+# $NetBSD: deptgt-end.mk,v 1.3 2020/08/29 17:34:21 rillig Exp $
+#
+# Tests for the special target .END in dependency declarations,
+# which is run after making the desired targets.
+
+.BEGIN:
+ : $@
+
+.END:
+ : $@
+
+all:
+ : $@
diff --git a/contrib/bmake/unit-tests/deptgt-error.exp b/contrib/bmake/unit-tests/deptgt-error.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-error.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-error.mk b/contrib/bmake/unit-tests/deptgt-error.mk
new file mode 100644
index 000000000000..07bc1b5d3408
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-error.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-error.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .ERROR in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-ignore.exp b/contrib/bmake/unit-tests/deptgt-ignore.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-ignore.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-ignore.mk b/contrib/bmake/unit-tests/deptgt-ignore.mk
new file mode 100644
index 000000000000..6ace0841f28b
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-ignore.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-ignore.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .IGNORE in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-interrupt.exp b/contrib/bmake/unit-tests/deptgt-interrupt.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-interrupt.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-interrupt.mk b/contrib/bmake/unit-tests/deptgt-interrupt.mk
new file mode 100644
index 000000000000..d555f864563e
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-interrupt.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-interrupt.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .INTERRUPT in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-main.exp b/contrib/bmake/unit-tests/deptgt-main.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-main.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-main.mk b/contrib/bmake/unit-tests/deptgt-main.mk
new file mode 100644
index 000000000000..cf1b1b4fd340
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-main.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-main.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .MAIN in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-makeflags.exp b/contrib/bmake/unit-tests/deptgt-makeflags.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-makeflags.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-makeflags.mk b/contrib/bmake/unit-tests/deptgt-makeflags.mk
new file mode 100644
index 000000000000..958751b64cfa
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-makeflags.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-makeflags.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .MAKEFLAGS in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-no_parallel.exp b/contrib/bmake/unit-tests/deptgt-no_parallel.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-no_parallel.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-no_parallel.mk b/contrib/bmake/unit-tests/deptgt-no_parallel.mk
new file mode 100644
index 000000000000..247f6a9ac8a5
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-no_parallel.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-no_parallel.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .NO_PARALLEL in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-nopath.exp b/contrib/bmake/unit-tests/deptgt-nopath.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-nopath.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-nopath.mk b/contrib/bmake/unit-tests/deptgt-nopath.mk
new file mode 100644
index 000000000000..a8cde2657dde
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-nopath.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-nopath.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .NOPATH in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-notparallel.exp b/contrib/bmake/unit-tests/deptgt-notparallel.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-notparallel.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-notparallel.mk b/contrib/bmake/unit-tests/deptgt-notparallel.mk
new file mode 100644
index 000000000000..db08aa9d3558
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-notparallel.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-notparallel.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .NOTPARALLEL in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-objdir.exp b/contrib/bmake/unit-tests/deptgt-objdir.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-objdir.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-objdir.mk b/contrib/bmake/unit-tests/deptgt-objdir.mk
new file mode 100644
index 000000000000..f5e9a15d8cb3
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-objdir.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-objdir.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .OBJDIR in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-order.exp b/contrib/bmake/unit-tests/deptgt-order.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-order.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-order.mk b/contrib/bmake/unit-tests/deptgt-order.mk
new file mode 100644
index 000000000000..003552f57a49
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-order.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-order.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .ORDER in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-path-suffix.exp b/contrib/bmake/unit-tests/deptgt-path-suffix.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-path-suffix.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-path-suffix.mk b/contrib/bmake/unit-tests/deptgt-path-suffix.mk
new file mode 100644
index 000000000000..3a7e697bc748
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-path-suffix.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-path-suffix.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .PATH.suffix in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-path.exp b/contrib/bmake/unit-tests/deptgt-path.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-path.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-path.mk b/contrib/bmake/unit-tests/deptgt-path.mk
new file mode 100644
index 000000000000..f74d1a46d1b1
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-path.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-path.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .PATH in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-phony.exp b/contrib/bmake/unit-tests/deptgt-phony.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-phony.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-phony.mk b/contrib/bmake/unit-tests/deptgt-phony.mk
new file mode 100644
index 000000000000..7f9909fa89a2
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-phony.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-phony.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .PHONY in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-precious.exp b/contrib/bmake/unit-tests/deptgt-precious.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-precious.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-precious.mk b/contrib/bmake/unit-tests/deptgt-precious.mk
new file mode 100644
index 000000000000..07b2bf719a4f
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-precious.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-precious.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .PRECIOUS in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-shell.exp b/contrib/bmake/unit-tests/deptgt-shell.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-shell.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-shell.mk b/contrib/bmake/unit-tests/deptgt-shell.mk
new file mode 100644
index 000000000000..71b535d89115
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-shell.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-shell.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .SHELL in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-silent.exp b/contrib/bmake/unit-tests/deptgt-silent.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-silent.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-silent.mk b/contrib/bmake/unit-tests/deptgt-silent.mk
new file mode 100644
index 000000000000..9ae64567fb97
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-silent.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-silent.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .SILENT in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-stale.exp b/contrib/bmake/unit-tests/deptgt-stale.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-stale.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-stale.mk b/contrib/bmake/unit-tests/deptgt-stale.mk
new file mode 100644
index 000000000000..cfde5a2bfd49
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-stale.mk
@@ -0,0 +1,8 @@
+# $NetBSD: deptgt-stale.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special target .STALE in dependency declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt-suffixes.exp b/contrib/bmake/unit-tests/deptgt-suffixes.exp
new file mode 100644
index 000000000000..b24f47becdaf
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-suffixes.exp
@@ -0,0 +1,7 @@
+#*** Suffixes:
+# `.custom-null' [1] (SUFF_NULL)
+# To:
+# From:
+# Search Path: . ..
+#*** Transformations:
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-suffixes.mk b/contrib/bmake/unit-tests/deptgt-suffixes.mk
new file mode 100644
index 000000000000..791ff5eb5f03
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt-suffixes.mk
@@ -0,0 +1,18 @@
+# $NetBSD: deptgt-suffixes.mk,v 1.3 2020/08/28 04:05:35 rillig Exp $
+#
+# Tests for the special target .SUFFIXES in dependency declarations.
+#
+# See also:
+# varname-dot-includes.mk
+# varname-dot-libs.mk
+
+.MAKEFLAGS: -dg1
+
+.SUFFIXES: .custom-null
+
+# TODO: What is the effect of this? How is it useful?
+.NULL: .custom-null
+.PATH.custom-null: . ..
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/deptgt.exp b/contrib/bmake/unit-tests/deptgt.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt.mk b/contrib/bmake/unit-tests/deptgt.mk
new file mode 100644
index 000000000000..add21dc0baee
--- /dev/null
+++ b/contrib/bmake/unit-tests/deptgt.mk
@@ -0,0 +1,9 @@
+# $NetBSD: deptgt.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for special targets like .BEGIN or .SUFFIXES in dependency
+# declarations.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/dir-expand-path.exp b/contrib/bmake/unit-tests/dir-expand-path.exp
new file mode 100644
index 000000000000..74de255298bb
--- /dev/null
+++ b/contrib/bmake/unit-tests/dir-expand-path.exp
@@ -0,0 +1,4 @@
+dir-expand-path.dir.1/file1.src
+dir-expand-path.dir.1/file2.src
+dir-expand-path.dir.2/file3.src
+exit status 0
diff --git a/contrib/bmake/unit-tests/dir-expand-path.mk b/contrib/bmake/unit-tests/dir-expand-path.mk
new file mode 100755
index 000000000000..1ce349cce385
--- /dev/null
+++ b/contrib/bmake/unit-tests/dir-expand-path.mk
@@ -0,0 +1,19 @@
+# $NetBSD: dir-expand-path.mk,v 1.1 2020/08/22 21:55:54 rillig Exp $
+#
+# Tests for filename expansion in the search path.
+
+_!= rm -rf dir-expand-path.dir.*
+_!= mkdir dir-expand-path.dir.1
+_!= mkdir dir-expand-path.dir.2
+_!= touch dir-expand-path.dir.1/file1.src
+_!= touch dir-expand-path.dir.1/file2.src
+_!= touch dir-expand-path.dir.2/file3.src
+
+.PATH: dir-expand-path.dir.1
+.PATH: dir-expand-path.dir.2
+
+all: *.src
+ @printf '%s\n' ${.ALLSRC:O}
+
+.END:
+ @rm -rf dir-expand-path.dir.*
diff --git a/contrib/bmake/unit-tests/dir.exp b/contrib/bmake/unit-tests/dir.exp
new file mode 100644
index 000000000000..874a081e97ab
--- /dev/null
+++ b/contrib/bmake/unit-tests/dir.exp
@@ -0,0 +1,19 @@
+1
+2
+3
+4
+5
+13
+14
+15
+pre-patch
+pre-configure
+patch
+configure
+fetch
+fetch-post
+extract
+extract-post
+dup-1
+single-word
+exit status 0
diff --git a/contrib/bmake/unit-tests/dir.mk b/contrib/bmake/unit-tests/dir.mk
new file mode 100644
index 000000000000..f5926375312e
--- /dev/null
+++ b/contrib/bmake/unit-tests/dir.mk
@@ -0,0 +1,58 @@
+# $NetBSD: dir.mk,v 1.4 2020/07/31 20:16:21 rillig Exp $
+#
+# Tests for dir.c.
+
+# Dependency lines may use braces for expansion.
+all: {one,two,three}
+
+one:
+ @echo 1
+two:
+ @echo 2
+three:
+ @echo 3
+
+# The braces may start in the middle of a word.
+all: f{our,ive}
+
+four:
+ @echo 4
+five:
+ @echo 5
+six:
+ @echo 6
+
+# But nested braces don't work.
+all: {{thi,fou}r,fif}teen
+
+thirteen:
+ @echo 13
+fourteen:
+ @echo 14
+fifteen:
+ @echo 15
+
+# There may be multiple brace groups side by side.
+all: {pre-,}{patch,configure}
+
+pre-patch patch pre-configure configure:
+ @echo $@
+
+# Empty pieces are allowed in the braces.
+all: {fetch,extract}{,-post}
+
+fetch fetch-post extract extract-post:
+ @echo $@
+
+# The expansions may have duplicates.
+# These are merged together because of the dependency line.
+all: dup-{1,1,1,1,1,1,1}
+
+dup-1:
+ @echo $@
+
+# Other than in Bash, the braces are also expanded if there is no comma.
+all: {{{{{{{{{{single-word}}}}}}}}}}
+
+single-word:
+ @echo $@
diff --git a/contrib/bmake/unit-tests/directive-elif.exp b/contrib/bmake/unit-tests/directive-elif.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-elif.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-elif.mk b/contrib/bmake/unit-tests/directive-elif.mk
new file mode 100644
index 000000000000..fc8bb7e7d197
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-elif.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-elif.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .elif directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-elifdef.exp b/contrib/bmake/unit-tests/directive-elifdef.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-elifdef.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-elifdef.mk b/contrib/bmake/unit-tests/directive-elifdef.mk
new file mode 100644
index 000000000000..f960c1513e8e
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-elifdef.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-elifdef.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .elifdef directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-elifmake.exp b/contrib/bmake/unit-tests/directive-elifmake.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-elifmake.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-elifmake.mk b/contrib/bmake/unit-tests/directive-elifmake.mk
new file mode 100644
index 000000000000..c78e3aa91332
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-elifmake.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-elifmake.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .elifmake directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-elifndef.exp b/contrib/bmake/unit-tests/directive-elifndef.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-elifndef.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-elifndef.mk b/contrib/bmake/unit-tests/directive-elifndef.mk
new file mode 100644
index 000000000000..19bb66c11b01
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-elifndef.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-elifndef.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .elifndef directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-elifnmake.exp b/contrib/bmake/unit-tests/directive-elifnmake.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-elifnmake.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-elifnmake.mk b/contrib/bmake/unit-tests/directive-elifnmake.mk
new file mode 100644
index 000000000000..11c4b973c1c8
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-elifnmake.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-elifnmake.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .elifnmake directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-else.exp b/contrib/bmake/unit-tests/directive-else.exp
new file mode 100644
index 000000000000..387577099b81
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-else.exp
@@ -0,0 +1,8 @@
+make: "directive-else.mk" line 10: ok
+make: "directive-else.mk" line 14: ok
+make: "directive-else.mk" line 20: if-less else
+make: "directive-else.mk" line 26: ok
+make: "directive-else.mk" line 27: warning: extra else
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/directive-else.mk b/contrib/bmake/unit-tests/directive-else.mk
new file mode 100644
index 000000000000..3afa5648af67
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-else.mk
@@ -0,0 +1,32 @@
+# $NetBSD: directive-else.mk,v 1.3 2020/08/29 18:50:25 rillig Exp $
+#
+# Tests for the .else directive.
+
+# The .else directive does not take any arguments.
+# As of 2020-08-29, make doesn't warn about this.
+.if 0
+.warning must not be reached
+.else 123
+.info ok
+.endif
+
+.if 1
+.info ok
+.else 123
+.warning must not be reached
+.endif
+
+# An .else without a corresponding .if is an error.
+.else
+
+# Accidental extra .else directives are detected too.
+.if 0
+.warning must not be reached
+.else
+.info ok
+.else
+.info After an extra .else, everything is skipped.
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-endif.exp b/contrib/bmake/unit-tests/directive-endif.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-endif.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-endif.mk b/contrib/bmake/unit-tests/directive-endif.mk
new file mode 100644
index 000000000000..12b608ffbeee
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-endif.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-endif.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .endif directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-error.exp b/contrib/bmake/unit-tests/directive-error.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-error.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-error.mk b/contrib/bmake/unit-tests/directive-error.mk
new file mode 100644
index 000000000000..3980016221ab
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-error.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-error.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .error directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-export-env.exp b/contrib/bmake/unit-tests/directive-export-env.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-export-env.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-export-env.mk b/contrib/bmake/unit-tests/directive-export-env.mk
new file mode 100644
index 000000000000..49d1edb9f6fe
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-export-env.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-export-env.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .export-env directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-export-literal.exp b/contrib/bmake/unit-tests/directive-export-literal.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-export-literal.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-export-literal.mk b/contrib/bmake/unit-tests/directive-export-literal.mk
new file mode 100644
index 000000000000..ed3957b288d6
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-export-literal.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-export-literal.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .export-literal directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-export.exp b/contrib/bmake/unit-tests/directive-export.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-export.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-export.mk b/contrib/bmake/unit-tests/directive-export.mk
new file mode 100644
index 000000000000..c7f9181b4b5a
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-export.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-export.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .export directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-for-generating-endif.exp b/contrib/bmake/unit-tests/directive-for-generating-endif.exp
new file mode 100755
index 000000000000..9e1301abf96f
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-for-generating-endif.exp
@@ -0,0 +1,7 @@
+make: "directive-for-generating-endif.mk" line 21: if-less endif
+make: "directive-for-generating-endif.mk" line 21: if-less endif
+make: "directive-for-generating-endif.mk" line 21: if-less endif
+make: "directive-for-generating-endif.mk" line 0: 3 open conditionals
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/directive-for-generating-endif.mk b/contrib/bmake/unit-tests/directive-for-generating-endif.mk
new file mode 100755
index 000000000000..b4d709551003
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-for-generating-endif.mk
@@ -0,0 +1,25 @@
+# $NetBSD: directive-for-generating-endif.mk,v 1.1 2020/08/29 18:50:25 rillig Exp $
+#
+# Test whether a .for loop can be used to generate multiple .endif
+# directives to close nested .if directives. Depending on the exact
+# implementation, this might have been possible.
+#
+# If it were possible, the 3 .if directives would perfectly match the
+# 3 .endif directives generated by the .for loop.
+#
+# After the "included file" from the .for loop, the 3 .if directives
+# are still open.
+#
+# See For_Run and ParseReadLine. Each .for loop is treated like a separately
+# included file, and in each included file the .if/.endif directives must be
+# balanced.
+
+.if 1
+. if 2
+. if 3
+.for i in 3 2 1
+.endif
+.endfor
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-for.exp b/contrib/bmake/unit-tests/directive-for.exp
new file mode 100755
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-for.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-for.mk b/contrib/bmake/unit-tests/directive-for.mk
new file mode 100755
index 000000000000..5b3f299e5711
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-for.mk
@@ -0,0 +1,97 @@
+# $NetBSD: directive-for.mk,v 1.2 2020/09/02 22:58:59 rillig Exp $
+#
+# Tests for the .for directive.
+
+# Using the .for loop, lists of values can be produced.
+# In simple cases, the :@var@${var}@ variable modifier can be used to
+# reach the same effects.
+#
+.undef NUMBERS
+.for num in 1 2 3
+NUMBERS+= ${num}
+.endfor
+.if ${NUMBERS} != "1 2 3"
+. error
+.endif
+
+# The .for loop also works for multiple iteration variables.
+.for name value in VARNAME value NAME2 value2
+${name}= ${value}
+.endfor
+.if ${VARNAME} != "value" || ${NAME2} != "value2"
+. error
+.endif
+
+# The .for loop splits the items at whitespace, taking quotes into account,
+# just like the :M or :S variable modifiers.
+#
+# Until 2012-06-03, it had split the items exactly at whitespace, without
+# taking the quotes into account.
+#
+.undef WORDS
+.for var in one t\ w\ o "three three" 'four four' `five six`
+WORDS+= counted
+.endfor
+.if ${WORDS:[#]} != 6
+. error
+.endif
+
+# In the body of the .for loop, the iteration variables can be accessed
+# like normal variables, even though they are not really variables.
+#
+# Instead, the expression ${var} is transformed into ${:U1}, ${:U2} and so
+# on, before the loop body is evaluated.
+#
+# A notable effect of this implementation technique is that the .for
+# iteration variables and the normal global variables live in separate
+# namespaces and do not influence each other.
+#
+var= value before
+var2= value before
+.for var var2 in 1 2 3 4
+.endfor
+.if ${var} != "value before"
+. warning After the .for loop, var must still have its original value.
+.endif
+.if ${var2} != "value before"
+. warning After the .for loop, var2 must still have its original value.
+.endif
+
+# Everything from the paragraph above also applies if the loop body is
+# empty, even if there is no actual iteration since the loop items are
+# also empty.
+#
+var= value before
+var2= value before
+.for var var2 in ${:U}
+.endfor
+.if ${var} != "value before"
+. warning After the .for loop, var must still have its original value.
+.endif
+.if ${var2} != "value before"
+. warning After the .for loop, var2 must still have its original value.
+.endif
+
+# Until 2008-12-21, the values of the iteration variables were simply
+# inserted as plain text and then parsed as usual, which made it possible
+# to achieve all kinds of strange effects.
+#
+# Before that date, the .for loop expanded to:
+# EXPANSION+= value
+# Since that date, the .for loop expands to:
+# EXPANSION${:U+}= value
+#
+EXPANSION= before
+EXPANSION+ = before
+.for plus in +
+EXPANSION${plus}= value
+.endfor
+.if ${EXPANSION} != "before"
+. error This must be a make from before 2009.
+.endif
+.if ${EXPANSION+} != "value"
+. error This must be a make from before 2009.
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-if.exp b/contrib/bmake/unit-tests/directive-if.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-if.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-if.mk b/contrib/bmake/unit-tests/directive-if.mk
new file mode 100644
index 000000000000..72d7d0e2d920
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-if.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-if.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .if directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-ifdef.exp b/contrib/bmake/unit-tests/directive-ifdef.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-ifdef.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-ifdef.mk b/contrib/bmake/unit-tests/directive-ifdef.mk
new file mode 100644
index 000000000000..64f073fb5ae1
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-ifdef.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-ifdef.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .ifdef directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-ifmake.exp b/contrib/bmake/unit-tests/directive-ifmake.exp
new file mode 100644
index 000000000000..5fefeb252b48
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-ifmake.exp
@@ -0,0 +1,10 @@
+make: "directive-ifmake.mk" line 8: ok: positive condition works
+make: "directive-ifmake.mk" line 19: ok: negation works
+make: "directive-ifmake.mk" line 25: ok: double negation works
+make: "directive-ifmake.mk" line 32: ok: both mentioned
+make: "directive-ifmake.mk" line 39: ok: only those mentioned
+make: "directive-ifmake.mk" line 49: Targets can even be added at parse time.
+: first
+: second
+: late-target
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-ifmake.mk b/contrib/bmake/unit-tests/directive-ifmake.mk
new file mode 100644
index 000000000000..20329bc5ce25
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-ifmake.mk
@@ -0,0 +1,55 @@
+# $NetBSD: directive-ifmake.mk,v 1.4 2020/08/30 14:25:45 rillig Exp $
+#
+# Tests for the .ifmake directive, which provides a shortcut for asking
+# whether a certain target is requested to be made from the command line.
+
+# This is the most basic form.
+.ifmake first
+.info ok: positive condition works
+.else
+.warning positive condition fails
+.endif
+
+# The not operator works as expected.
+# An alternative interpretation were that this condition is asking whether
+# the target "!first" was requested. To distinguish this, see the next test.
+.ifmake !first
+.warning unexpected
+.else
+.info ok: negation works
+.endif
+
+# See if the exclamation mark really means "not", or if it is just part of
+# the target name.
+.ifmake !!first
+.info ok: double negation works
+.else
+.warning double negation fails
+.endif
+
+# Multiple targets can be combined using the && and || operators.
+.ifmake first && second
+.info ok: both mentioned
+.else
+.warning && does not work as expected
+.endif
+
+# Negation also works in complex conditions.
+.ifmake first && !unmentioned
+.info ok: only those mentioned
+.else
+.warning && with ! does not work as expected
+.endif
+
+# Using the .MAKEFLAGS special dependency target, arbitrary command
+# line options can be added at parse time. This means that it is
+# possible to extend the targets to be made.
+.MAKEFLAGS: late-target
+.ifmake late-target
+.info Targets can even be added at parse time.
+.else
+.info No, targets cannot be added at parse time anymore.
+.endif
+
+first second unmentioned late-target:
+ : $@
diff --git a/contrib/bmake/unit-tests/directive-ifndef.exp b/contrib/bmake/unit-tests/directive-ifndef.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-ifndef.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-ifndef.mk b/contrib/bmake/unit-tests/directive-ifndef.mk
new file mode 100644
index 000000000000..0981f817fcfd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-ifndef.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-ifndef.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .ifndef directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-ifnmake.exp b/contrib/bmake/unit-tests/directive-ifnmake.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-ifnmake.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-ifnmake.mk b/contrib/bmake/unit-tests/directive-ifnmake.mk
new file mode 100644
index 000000000000..2a20249f6c76
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-ifnmake.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-ifnmake.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .ifnmake directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-info.exp b/contrib/bmake/unit-tests/directive-info.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-info.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-info.mk b/contrib/bmake/unit-tests/directive-info.mk
new file mode 100644
index 000000000000..3eb972ad7a0e
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-info.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-info.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .info directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-undef.exp b/contrib/bmake/unit-tests/directive-undef.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-undef.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-undef.mk b/contrib/bmake/unit-tests/directive-undef.mk
new file mode 100644
index 000000000000..ff91eeddb41f
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-undef.mk
@@ -0,0 +1,17 @@
+# $NetBSD: directive-undef.mk,v 1.3 2020/08/23 19:30:13 rillig Exp $
+#
+# Tests for the .undef directive.
+
+# As of 2020-07-28, .undef only undefines the first variable.
+# All further variable names are silently ignored.
+# See parse.c, string literal "undef".
+1= 1
+2= 2
+3= 3
+.undef 1 2 3
+.if ${1:U_}${2:U_}${3:U_} != _23
+.warning $1$2$3
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-unexport-env.exp b/contrib/bmake/unit-tests/directive-unexport-env.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-unexport-env.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-unexport-env.mk b/contrib/bmake/unit-tests/directive-unexport-env.mk
new file mode 100644
index 000000000000..34d867d706ef
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-unexport-env.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-unexport-env.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .unexport-env directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-unexport.exp b/contrib/bmake/unit-tests/directive-unexport.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-unexport.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-unexport.mk b/contrib/bmake/unit-tests/directive-unexport.mk
new file mode 100644
index 000000000000..679387aac083
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-unexport.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-unexport.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .unexport directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive-warning.exp b/contrib/bmake/unit-tests/directive-warning.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-warning.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-warning.mk b/contrib/bmake/unit-tests/directive-warning.mk
new file mode 100644
index 000000000000..e1e636e3ec9f
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-warning.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive-warning.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the .warning directive.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directive.exp b/contrib/bmake/unit-tests/directive.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive.mk b/contrib/bmake/unit-tests/directive.mk
new file mode 100644
index 000000000000..8d01a49a34cf
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive.mk
@@ -0,0 +1,8 @@
+# $NetBSD: directive.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the preprocessing directives, such as .if or .info.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/directives.exp b/contrib/bmake/unit-tests/directives.exp
new file mode 100644
index 000000000000..15dd9aa9e962
--- /dev/null
+++ b/contrib/bmake/unit-tests/directives.exp
@@ -0,0 +1,42 @@
+make: "directives.mk" line 10: begin .export tests
+make: "directives.mk" line 11: Unknown directive "expor"
+make: "directives.mk" line 20: begin .export-env tests
+make: "directives.mk" line 30: begin .export-literal tests
+make: "directives.mk" line 40: begin .info tests
+make: "directives.mk" line 41: Unknown directive "inf"
+make: "directives.mk" line 42: Unknown directive "info"
+make: "directives.mk" line 43: message
+make: "directives.mk" line 44: indented message
+make: "directives.mk" line 45: Unknown directive "information"
+make: "directives.mk" line 46: message
+make: "directives.mk" line 50: begin .undef tests
+make: "directives.mk" line 51: Unknown directive "unde"
+make: "directives.mk" line 60: begin .unexport tests
+make: "directives.mk" line 61: Unknown directive "unexpor"
+make: "directives.mk" line 70: begin .unexport-env tests
+make: "directives.mk" line 80: begin .warning tests
+make: "directives.mk" line 81: Unknown directive "warn"
+make: "directives.mk" line 82: Unknown directive "warnin"
+make: "directives.mk" line 83: Unknown directive "warning"
+make: "directives.mk" line 84: warning: message
+make: "directives.mk" line 85: Unknown directive "warnings"
+make: "directives.mk" line 86: warning: messages
+make: "directives.mk" line 90: begin .elif misspellings tests, part 1
+make: "directives.mk" line 100: begin .elif misspellings tests, part 2
+make: "directives.mk" line 110: begin .elif misspellings tests, part 3
+make: "directives.mk" line 120: which branch is taken on misspelling after false?
+make: "directives.mk" line 127: else taken
+make: "directives.mk" line 130: which branch is taken on misspelling after true?
+make: "directives.mk" line 132: Unknown directive "elsif"
+make: "directives.mk" line 133: 1 taken
+make: "directives.mk" line 134: Unknown directive "elsif"
+make: "directives.mk" line 135: 2 taken
+make: "directives.mk" line 140: Unknown directive "indented"
+make: "directives.mk" line 141: Unknown directive "indented"
+make: "directives.mk" line 142: Unknown directive "indented"
+make: "directives.mk" line 143: Unknown directive "info"
+make: "directives.mk" line 150: Could not find nonexistent.mk
+make: "directives.mk" line 160: end of the tests
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/directives.mk b/contrib/bmake/unit-tests/directives.mk
new file mode 100644
index 000000000000..50e8e1315dda
--- /dev/null
+++ b/contrib/bmake/unit-tests/directives.mk
@@ -0,0 +1,163 @@
+# $NetBSD: directives.mk,v 1.5 2020/07/28 20:57:59 rillig Exp $
+#
+# Tests for parsing directives, in the same order as in the manual page.
+#
+# Each test group has 10 lines, to keep the line numbers in directives.exp
+# stable.
+#
+# no tests for .error since it exits immediately, see ParseMessage.
+
+.info begin .export tests
+.expor # misspelled
+.export # oops: missing argument
+.export VARNAME
+.exporting works # oops: misspelled
+
+
+
+
+
+.info begin .export-env tests
+.export-en # oops: misspelled
+.export-env
+.export-environment # oops: misspelled
+
+
+
+
+
+
+.info begin .export-literal tests
+.export-litera # oops: misspelled
+.export-literal # oops: missing argument
+.export-literal VARNAME
+.export-literally # oops: misspelled
+
+
+
+
+
+.info begin .info tests
+.inf # misspelled
+.info # oops: message should be "missing parameter"
+.info message
+.info indented message
+.information
+.information message # oops: misspelled
+.info.man: # not a message, but a suffix rule
+
+
+.info begin .undef tests
+.unde # misspelled
+.undef # oops: missing argument
+.undefined # oops: misspelled
+.undef VARNAME
+
+
+
+
+
+.info begin .unexport tests
+.unexpor # misspelled
+.unexport # oops: missing argument
+.unexport VARNAME # ok
+.unexporting works # oops: misspelled
+
+
+
+
+
+.info begin .unexport-env tests
+.unexport-en # misspelled
+.unexport-env # ok
+.unexport-environment # oops: misspelled
+
+
+
+
+
+
+.info begin .warning tests
+.warn # misspelled
+.warnin # misspelled
+.warning # oops: should be "missing argument"
+.warning message # ok
+.warnings # misspelled
+.warnings messages # oops
+
+
+
+.info begin .elif misspellings tests, part 1
+.if 1
+.elif 1 # ok
+.elsif 1 # oops: misspelled
+.elseif 1 # oops: misspelled
+.endif
+
+
+
+
+.info begin .elif misspellings tests, part 2
+.if 0
+.elif 0 # ok
+.elsif 0 # oops: misspelled
+.elseif 0 # oops: misspelled
+.endif
+
+
+
+
+.info begin .elif misspellings tests, part 3
+.if 0
+.elsif 0 # oops: misspelled
+.endif
+.if 0
+.elseif 0 # oops: misspelled
+.endif
+
+
+
+.info which branch is taken on misspelling after false?
+.if 0
+.elsif 1
+.info 1 taken
+.elsif 2
+.info 2 taken
+.else
+.info else taken
+.endif
+
+.info which branch is taken on misspelling after true?
+.if 1
+.elsif 1
+.info 1 taken
+.elsif 2
+.info 2 taken
+.else
+.info else taken
+.endif
+
+.indented none
+. indented 2 spaces
+. indented tab
+.${:Uinfo} directives cannot be indirect
+
+
+
+
+
+
+.include "nonexistent.mk"
+.include "/dev/null" # size 0
+# including a directory technically succeeds, but shouldn't.
+#.include "." # directory
+
+
+
+
+
+
+.info end of the tests
+
+all:
+ @:
diff --git a/contrib/bmake/unit-tests/envfirst.exp b/contrib/bmake/unit-tests/envfirst.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/envfirst.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/envfirst.mk b/contrib/bmake/unit-tests/envfirst.mk
new file mode 100644
index 000000000000..f4744bd61285
--- /dev/null
+++ b/contrib/bmake/unit-tests/envfirst.mk
@@ -0,0 +1,42 @@
+# $NetBSD: envfirst.mk,v 1.2 2020/07/27 18:57:42 rillig Exp $
+#
+# The -e option makes environment variables stronger than global variables.
+
+.if ${FROM_ENV} != value-from-env
+.error ${FROM_ENV}
+.endif
+
+# Try to override the variable; this does not have any effect.
+FROM_ENV= value-from-mk
+.if ${FROM_ENV} != value-from-env
+.error ${FROM_ENV}
+.endif
+
+# Try to append to the variable; this also doesn't have any effect.
+FROM_ENV+= appended
+.if ${FROM_ENV} != value-from-env
+.error ${FROM_ENV}
+.endif
+
+# The default assignment also cannot change the variable.
+FROM_ENV?= default
+.if ${FROM_ENV} != value-from-env
+.error ${FROM_ENV}
+.endif
+
+# Neither can the assignment modifiers.
+.if ${FROM_ENV::=from-condition}
+.endif
+.if ${FROM_ENV} != value-from-env
+.error ${FROM_ENV}
+.endif
+
+# Even .undef doesn't work since it only affects the global context,
+# which is independent from the environment variables.
+.undef FROM_ENV
+.if ${FROM_ENV} != value-from-env
+.error ${FROM_ENV}
+.endif
+
+all:
+ @: nothing
diff --git a/contrib/bmake/unit-tests/export-all.mk b/contrib/bmake/unit-tests/export-all.mk
index 200412f3b90a..c1910cac96b9 100644
--- a/contrib/bmake/unit-tests/export-all.mk
+++ b/contrib/bmake/unit-tests/export-all.mk
@@ -1,4 +1,4 @@
-# $Id: export-all.mk,v 1.1.1.2 2015/04/10 20:43:38 sjg Exp $
+# $Id: export-all.mk,v 1.1.1.3 2020/07/28 16:57:18 sjg Exp $
UT_OK=good
UT_F=fine
@@ -17,6 +17,7 @@ UT_OKDIR = ${${here}/../${here:T}:L:${M_tA}:T}
.export
+FILTER_CMD= grep ^UT_
.include "export.mk"
UT_TEST=export-all
diff --git a/contrib/bmake/unit-tests/export-variants.exp b/contrib/bmake/unit-tests/export-variants.exp
new file mode 100755
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/export-variants.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/export-variants.mk b/contrib/bmake/unit-tests/export-variants.mk
new file mode 100755
index 000000000000..1e254f1816cf
--- /dev/null
+++ b/contrib/bmake/unit-tests/export-variants.mk
@@ -0,0 +1,40 @@
+# $NetBSD: export-variants.mk,v 1.2 2020/08/08 13:00:07 rillig Exp $
+#
+# Test whether exported variables apply to each variant of running
+# external commands:
+#
+# The != assignments.
+# The :!cmd! modifier.
+# The :sh modifier.
+
+SHVAR!= env | grep ^UT_ || true
+.if ${SHVAR} != ""
+.warning At this point, no variable should be exported.
+.endif
+
+.if ${:!env | grep ^UT_ || true!} != ""
+.warning At this point, no variable should be exported.
+.endif
+
+.if ${env | grep ^UT_ || true:L:sh} != ""
+.warning At this point, no variable should be exported.
+.endif
+
+UT_VAR= value
+.export UT_VAR
+
+SHVAR!= env | grep ^UT_ || true
+.if ${SHVAR} != "UT_VAR=value"
+.warning At this point, no variable should be exported.
+.endif
+
+.if ${:!env | grep ^UT_ || true!} != "UT_VAR=value"
+.warning At this point, some variables should be exported.
+.endif
+
+.if ${env | grep ^UT_ || true:L:sh} != "UT_VAR=value"
+.warning At this point, some variables should be exported.
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/export.exp b/contrib/bmake/unit-tests/export.exp
index 143771ce90ae..c0e9b98c5a44 100644
--- a/contrib/bmake/unit-tests/export.exp
+++ b/contrib/bmake/unit-tests/export.exp
@@ -1,3 +1,5 @@
+&=ampersand
+MAKELEVEL=1
UT_DOLLAR=This is $UT_FU
UT_FOO=foobar is fubar
UT_FU=fubar
diff --git a/contrib/bmake/unit-tests/export.mk b/contrib/bmake/unit-tests/export.mk
index 01f69f918161..b98d175af314 100644
--- a/contrib/bmake/unit-tests/export.mk
+++ b/contrib/bmake/unit-tests/export.mk
@@ -1,22 +1,43 @@
-# $Id: export.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $
+# $Id: export.mk,v 1.1.1.4 2020/08/08 22:34:25 sjg Exp $
UT_TEST=export
UT_FOO=foo${BAR}
UT_FU=fubar
UT_ZOO=hoopie
UT_NO=all
-# belive it or not, we expect this one to come out with $UT_FU unexpanded.
+# believe it or not, we expect this one to come out with $UT_FU unexpanded.
UT_DOLLAR= This is $$UT_FU
.export UT_FU UT_FOO
.export UT_DOLLAR
-# this one will be ignored
+
+.if !defined(.MAKE.PID)
+.error .MAKE.PID must be defined
+.endif
+@= at
+%= percent
+*= asterisk
+${:U!}= exclamation # A direct != would try to run "exclamation"
+ # as a shell command and assign its output
+ # to the empty variable.
+&= ampersand
+
+# This is ignored because it is internal.
.export .MAKE.PID
+# These are ignored because they are local to the target.
+.export @
+.export %
+.export *
+.export !
+.export &
+# This is ignored because it is undefined.
+.export UNDEFINED
BAR=bar is ${UT_FU}
.MAKE.EXPORTED+= UT_ZOO UT_TEST
-all:
- @env | grep '^UT_' | sort
+FILTER_CMD?= egrep -v '^(MAKEFLAGS|PATH|PWD|SHLVL|_)='
+all:
+ @env | ${FILTER_CMD} | sort
diff --git a/contrib/bmake/unit-tests/forloop.mk b/contrib/bmake/unit-tests/forloop.mk
index dd3f07ddb19a..d61a53816df7 100644
--- a/contrib/bmake/unit-tests/forloop.mk
+++ b/contrib/bmake/unit-tests/forloop.mk
@@ -1,4 +1,4 @@
-# $Id: forloop.mk,v 1.1.1.2 2020/05/05 00:54:40 sjg Exp $
+# $Id: forloop.mk,v 1.1.1.3 2020/09/02 18:35:14 sjg Exp $
all: for-loop
@@ -36,7 +36,7 @@ X!= echo 'a=$a b=$b' >&2; echo
# 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.
+# that a newline ever occurred.
.for var in a${.newline}b${.newline}c
X!= echo 'newline-item=('${var:Q}')' 1>&2; echo
.endfor
diff --git a/contrib/bmake/unit-tests/impsrc.exp b/contrib/bmake/unit-tests/impsrc.exp
index 23e8347d205d..3eb050e080c5 100644
--- a/contrib/bmake/unit-tests/impsrc.exp
+++ b/contrib/bmake/unit-tests/impsrc.exp
@@ -1,13 +1,13 @@
-expected: source4
-actual: source4
+expected:
+actual:
expected: target1.x
actual: target1.x
expected: target1.y
actual: target1.y
-expected: source1
-actual: source1
-expected: source2
-actual: source2
-expected: source1
-actual: source1
+expected:
+actual:
+expected:
+actual:
+expected:
+actual:
exit status 0
diff --git a/contrib/bmake/unit-tests/impsrc.mk b/contrib/bmake/unit-tests/impsrc.mk
index 95ae0c34f3b5..6fb31adac938 100644
--- a/contrib/bmake/unit-tests/impsrc.mk
+++ b/contrib/bmake/unit-tests/impsrc.mk
@@ -1,4 +1,4 @@
-# $NetBSD: impsrc.mk,v 1.2 2014/08/30 22:21:07 sjg Exp $
+# $NetBSD: impsrc.mk,v 1.3 2020/08/07 13:43:50 rillig Exp $
# Does ${.IMPSRC} work properly?
# It should be set, in order of precedence, to ${.TARGET} of:
@@ -6,6 +6,8 @@
# 2) the first prerequisite from the dependency line of an explicit rule, or
# 3) the first prerequisite of an explicit rule.
#
+# Items 2 and 3 work in GNU make.
+# Items 2 and 3 are not required by POSIX 2018.
all: target1.z target2 target3 target4
@@ -19,25 +21,40 @@ all: target1.z target2 target3 target4
@echo 'expected: target1.y'
@echo 'actual: $<'
+# (3) Making target1.z out of target1.y is done because of an inference rule.
+# Therefore $< is available here.
+
+# (2) This is an additional dependency on the inference rule .x.y.
+# The dependency target1.x comes from the inference rule,
+# therefore it is available as $<.
target1.y: source3
+# (1) This is an explicit dependency, not an inference rule.
+# Therefore POSIX does not specify that $< be available here.
target1.x: source4
- @echo 'expected: source4'
+ @echo 'expected: ' # either 'source4' or ''
@echo 'actual: $<'
+# (4) This is an explicit dependency, independent of any inference rule.
+# Therefore $< is not available here.
target2: source1 source2
- @echo 'expected: source1'
+ @echo 'expected: '
@echo 'actual: $<'
+# (5) These are two explicit dependency rules.
+# The first doesn't have any dependencies, only the second has.
+# If any, the value of $< would be 'source2'.
target3: source1
target3: source2 source3
- @echo 'expected: source2'
+ @echo 'expected: '
@echo 'actual: $<'
+# (6) The explicit rule does not have associated commands.
+# The value of $< might come from that rule,
+# but it's equally fine to leave $< undefined.
target4: source1
target4:
- @echo 'expected: source1'
+ @echo 'expected: '
@echo 'actual: $<'
source1 source2 source3 source4:
-
diff --git a/contrib/bmake/unit-tests/include-main.mk b/contrib/bmake/unit-tests/include-main.mk
index 452b6102a5ce..a53dd886b800 100644
--- a/contrib/bmake/unit-tests/include-main.mk
+++ b/contrib/bmake/unit-tests/include-main.mk
@@ -1,11 +1,11 @@
-# $NetBSD: include-main.mk,v 1.1 2020/05/17 12:36:26 rillig Exp $
+# $NetBSD: include-main.mk,v 1.2 2020/07/27 20:55:59 rillig 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".
+# latest .include happened". See parse.c, function ParseSetIncludeFile.
#
.if !defined(.INCLUDEDFROMFILE)
diff --git a/contrib/bmake/unit-tests/lint.exp b/contrib/bmake/unit-tests/lint.exp
new file mode 100755
index 000000000000..9cd0eeb1ddf4
--- /dev/null
+++ b/contrib/bmake/unit-tests/lint.exp
@@ -0,0 +1,4 @@
+make: In the :@ modifier of "VAR", the variable name "${:Ubar:S,b,v,}" must not contain a dollar.
+y@:Q}
+xvaluey
+exit status 0
diff --git a/contrib/bmake/unit-tests/lint.mk b/contrib/bmake/unit-tests/lint.mk
new file mode 100755
index 000000000000..56cd6be27860
--- /dev/null
+++ b/contrib/bmake/unit-tests/lint.mk
@@ -0,0 +1,17 @@
+# $NetBSD: lint.mk,v 1.2 2020/08/08 13:00:07 rillig Exp $
+#
+# Demonstrates stricter checks that are only enabled in the lint mode,
+# using the -dL option.
+
+# Ouch: as of 2020-08-03, make exits successfully even though the error
+# message has been issued as PARSE_FATAL.
+
+# Ouch: as of 2020-08-03, the variable is malformed and parsing stops
+# for a moment, but is continued after the wrongly-guessed end of the
+# variable, which echoes "y@:Q}".
+
+all: mod-loop-varname
+
+mod-loop-varname:
+ @echo ${VAR:Uvalue:@${:Ubar:S,b,v,}@x${var}y@:Q}
+ @echo ${VAR:Uvalue:@!@x$!y@:Q} # surprisingly allowed
diff --git a/contrib/bmake/unit-tests/make-exported.exp b/contrib/bmake/unit-tests/make-exported.exp
new file mode 100755
index 000000000000..7408f16c4e6c
--- /dev/null
+++ b/contrib/bmake/unit-tests/make-exported.exp
@@ -0,0 +1,3 @@
+-literal=make-exported-value
+UT_VAR=
+exit status 0
diff --git a/contrib/bmake/unit-tests/make-exported.mk b/contrib/bmake/unit-tests/make-exported.mk
new file mode 100755
index 000000000000..36f4b356ae53
--- /dev/null
+++ b/contrib/bmake/unit-tests/make-exported.mk
@@ -0,0 +1,16 @@
+# $NetBSD: make-exported.mk,v 1.1 2020/08/09 12:59:16 rillig Exp $
+#
+# As of 2020-08-09, the code in Var_Export is shared between the .export
+# directive and the .MAKE.EXPORTED variable. This leads to non-obvious
+# behavior for certain variable assignments.
+
+-env= make-exported-value
+-literal= make-exported-value
+UT_VAR= ${UNEXPANDED}
+
+# The following behavior is probably not intended.
+.MAKE.EXPORTED= -env # like .export-env
+.MAKE.EXPORTED= -literal UT_VAR # like .export-literal PATH
+
+all:
+ @env | sort | grep -E '^UT_|make-exported-value' || true
diff --git a/contrib/bmake/unit-tests/moderrs.exp b/contrib/bmake/unit-tests/moderrs.exp
index cb51aa09d909..f017cae4633a 100644
--- a/contrib/bmake/unit-tests/moderrs.exp
+++ b/contrib/bmake/unit-tests/moderrs.exp
@@ -10,7 +10,132 @@ VAR:S,V,v,=Thevariable
Expect: Unclosed variable specification for VAR
make: Unclosed variable specification after complex modifier (expecting '}') for VAR
VAR:S,V,v,=Thevariable
-Expect: Unclosed substitution for VAR (, missing)
-make: Unclosed substitution for VAR (, missing)
+Expect: Unfinished modifier for VAR (',' missing)
+make: Unfinished modifier for VAR (',' missing)
VAR:S,V,v=
+Expect: 2 errors about missing @ delimiter
+make: Unfinished modifier for UNDEF ('@' missing)
+
+make: Unfinished modifier for UNDEF ('@' missing)
+
+1 2 3
+modloop-close:
+make: Unclosed variable specification (expecting '}') for "UNDEF" (value "1}... 2}... 3}...") modifier @
+1}... 2}... 3}...
+1}... 2}... 3}...
+Expect: 2 errors about missing ] delimiter
+make: Unfinished modifier for UNDEF (']' missing)
+
+make: Unfinished modifier for UNDEF (']' missing)
+
+13=
+12345=ok
+Expect: 2 errors about missing ! delimiter
+make: Unfinished modifier for VARNAME ('!' missing)
+
+make: Unfinished modifier for ! ('!' missing)
+
+mod-subst-delimiter:
+make: Missing delimiter for :S modifier
+
+make: Unfinished modifier for VAR (',' missing)
+
+make: Unfinished modifier for VAR (',' missing)
+
+make: Unfinished modifier for VAR (',' missing)
+
+make: Unfinished modifier for VAR (',' missing)
+
+make: Unclosed variable specification (expecting '}') for "VAR" (value "TheVariable") modifier S
+TheVariable
+TheVariable
+make: Missing delimiter for :S modifier
+1:
+make: Unfinished modifier for VAR (',' missing)
+2:
+make: Unfinished modifier for VAR (',' missing)
+3:
+make: Unfinished modifier for VAR (',' missing)
+
+make: Unfinished modifier for VAR (',' missing)
+
+make: Unclosed variable specification (expecting '}') for "VAR" (value "TheVariable") modifier S
+TheVariable
+TheVariable
+mod-regex-delimiter:
+make: Missing delimiter for :C modifier
+
+make: Unfinished modifier for VAR (',' missing)
+
+make: Unfinished modifier for VAR (',' missing)
+
+make: Unfinished modifier for VAR (',' missing)
+
+make: Unfinished modifier for VAR (',' missing)
+
+make: Unclosed variable specification (expecting '}') for "VAR" (value "TheVariable") modifier C
+TheVariable
+TheVariable
+make: Missing delimiter for :C modifier
+1:
+make: Unfinished modifier for VAR (',' missing)
+2:
+make: Unfinished modifier for VAR (',' missing)
+3:
+make: Unfinished modifier for VAR (',' missing)
+
+make: Unfinished modifier for VAR (',' missing)
+
+make: Unclosed variable specification (expecting '}') for "VAR" (value "TheVariable") modifier C
+TheVariable
+TheVariable
+mod-regex-undefined-subexpression:
+one one 2 3 5 8 one3 2one 34
+make: No match for subexpression \2
+make: No match for subexpression \2
+make: No match for subexpression \1
+make: No match for subexpression \2
+make: No match for subexpression \1
+()+() ()+() ()+() 3 5 8 (3)+() ()+(1) 34
+mod-ts-parse:
+112358132134
+15152535558513521534
+make: Bad modifier `:ts\65oct' for FIB
+65oct}
+make: Bad modifier `:tsxy' for FIB
+xy}
+mod-t-parse:
+make: Bad modifier `:t' for FIB
+
+make: Bad modifier `:txy' for FIB
+y}
+make: Bad modifier `:t' for FIB
+
+make: Bad modifier `:t' for FIB
+M*}
+mod-ifelse-parse:
+make: Unfinished modifier for FIB (':' missing)
+
+make: Unfinished modifier for FIB (':' missing)
+
+make: Unfinished modifier for FIB ('}' missing)
+
+make: Unfinished modifier for FIB ('}' missing)
+
+then
+mod-remember-parse:
+1 1 2 3 5 8 13 21 34
+make: Unknown modifier '_'
+
+mod-sysv-parse:
+make: Unknown modifier '3'
+make: Unclosed variable specification (expecting '}') for "FIB" (value "") modifier 3
+
+make: Unknown modifier '3'
+make: Unclosed variable specification (expecting '}') for "FIB" (value "") modifier 3
+
+make: Unknown modifier '3'
+make: Unclosed variable specification (expecting '}') for "FIB" (value "") modifier 3
+
+1 1 2 x3 5 8 1x3 21 34
exit status 0
diff --git a/contrib/bmake/unit-tests/moderrs.mk b/contrib/bmake/unit-tests/moderrs.mk
index d0eb17ff6df6..931158c8ecb9 100644
--- a/contrib/bmake/unit-tests/moderrs.mk
+++ b/contrib/bmake/unit-tests/moderrs.mk
@@ -1,4 +1,4 @@
-# $Id: moderrs.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $
+# $Id: moderrs.mk,v 1.1.1.8 2020/08/26 16:40:43 sjg Exp $
#
# various modifier error tests
@@ -8,7 +8,20 @@ MOD_UNKN=Z
MOD_TERM=S,V,v
MOD_S:= ${MOD_TERM},
-all: modunkn modunknV varterm vartermV modtermV
+FIB= 1 1 2 3 5 8 13 21 34
+
+all: modunkn modunknV varterm vartermV modtermV modloop
+all: modloop-close
+all: modwords
+all: modexclam
+all: mod-subst-delimiter
+all: mod-regex-delimiter
+all: mod-regex-undefined-subexpression
+all: mod-ts-parse
+all: mod-t-parse
+all: mod-ifelse-parse
+all: mod-remember-parse
+all: mod-sysv-parse
modunkn:
@echo "Expect: Unknown modifier 'Z'"
@@ -27,5 +40,137 @@ vartermV:
@echo VAR:${MOD_TERM},=${VAR:${MOD_S}
modtermV:
- @echo "Expect: Unclosed substitution for VAR (, missing)"
+ @echo "Expect: Unfinished modifier for VAR (',' missing)"
-@echo "VAR:${MOD_TERM}=${VAR:${MOD_TERM}}"
+
+modloop:
+ @echo "Expect: 2 errors about missing @ delimiter"
+ @echo ${UNDEF:U1 2 3:@var}
+ @echo ${UNDEF:U1 2 3:@var@...}
+ @echo ${UNDEF:U1 2 3:@var@${var}@}
+
+# The closing brace after the ${var} is part of the replacement string.
+# In ParseModifierPart, braces and parentheses don't have to be balanced.
+# This is contrary to the :M, :N modifiers, where both parentheses and
+# braces must be balanced.
+# This is also contrary to the SysV modifier, where only the actually
+# used delimiter (either braces or parentheses) must be balanced.
+modloop-close:
+ @echo $@:
+ @echo ${UNDEF:U1 2 3:@var@${var}}...@
+ @echo ${UNDEF:U1 2 3:@var@${var}}...@}
+
+modwords:
+ @echo "Expect: 2 errors about missing ] delimiter"
+ @echo ${UNDEF:U1 2 3:[}
+ @echo ${UNDEF:U1 2 3:[#}
+
+ # out of bounds => empty
+ @echo 13=${UNDEF:U1 2 3:[13]}
+
+ # Word index out of bounds.
+ #
+ # On LP64I32, strtol returns LONG_MAX,
+ # which is then truncated to int (undefined behavior),
+ # typically resulting in -1.
+ # This -1 is interpreted as "the last word".
+ #
+ # On ILP32, strtol returns LONG_MAX,
+ # which is a large number.
+ # This results in a range from LONG_MAX - 1 to 3,
+ # which is empty.
+ @echo 12345=${UNDEF:U1 2 3:[123451234512345123451234512345]:S,^$,ok,:S,^3$,ok,}
+
+modexclam:
+ @echo "Expect: 2 errors about missing ! delimiter"
+ @echo ${VARNAME:!echo}
+ # When the final exclamation mark is missing, there is no
+ # fallback to the SysV substitution modifier.
+ # If there were a fallback, the output would be "exclam",
+ # and the above would have produced an "Unknown modifier '!'".
+ @echo ${!:L:!=exclam}
+
+mod-subst-delimiter:
+ @echo $@:
+ @echo ${VAR:S
+ @echo ${VAR:S,
+ @echo ${VAR:S,from
+ @echo ${VAR:S,from,
+ @echo ${VAR:S,from,to
+ @echo ${VAR:S,from,to,
+ @echo ${VAR:S,from,to,}
+ @echo 1: ${VAR:S
+ @echo 2: ${VAR:S,
+ @echo 3: ${VAR:S,from
+ @echo ${VAR:S,from,
+ @echo ${VAR:S,from,to
+ @echo ${VAR:S,from,to,
+ @echo ${VAR:S,from,to,}
+
+mod-regex-delimiter:
+ @echo $@:
+ @echo ${VAR:C
+ @echo ${VAR:C,
+ @echo ${VAR:C,from
+ @echo ${VAR:C,from,
+ @echo ${VAR:C,from,to
+ @echo ${VAR:C,from,to,
+ @echo ${VAR:C,from,to,}
+ @echo 1: ${VAR:C
+ @echo 2: ${VAR:C,
+ @echo 3: ${VAR:C,from
+ @echo ${VAR:C,from,
+ @echo ${VAR:C,from,to
+ @echo ${VAR:C,from,to,
+ @echo ${VAR:C,from,to,}
+
+# In regular expressions with alternatives, not all capturing groups are
+# always set; some may be missing. Warn about these.
+#
+# Since there is no way to turn off this warning, the combination of
+# alternative matches and capturing groups is not widely used.
+#
+# A newly added modifier 'U' such as in :C,(a.)|(b.),\1\2,U might be added
+# for treating undefined capturing groups as empty, but that would create a
+# syntactical ambiguity since the :S and :C modifiers are open-ended (see
+# mod-subst-chain). Luckily the modifier :U does not make sense after :C,
+# therefore this case does not happen in practice.
+# The sub-modifier for the :C modifier would have to be chosen wisely.
+mod-regex-undefined-subexpression:
+ @echo $@:
+ @echo ${FIB:C,1(.*),one\1,} # all ok
+ @echo ${FIB:C,1(.*)|2(.*),(\1)+(\2),:Q} # no match for subexpression
+
+mod-ts-parse:
+ @echo $@:
+ @echo ${FIB:ts}
+ @echo ${FIB:ts\65} # octal 065 == U+0035 == '5'
+ @echo ${FIB:ts\65oct} # bad modifier
+ @echo ${FIB:tsxy} # modifier too long
+
+mod-t-parse:
+ @echo $@:
+ @echo ${FIB:t
+ @echo ${FIB:txy}
+ @echo ${FIB:t}
+ @echo ${FIB:t:M*}
+
+mod-ifelse-parse:
+ @echo $@:
+ @echo ${FIB:?
+ @echo ${FIB:?then
+ @echo ${FIB:?then:
+ @echo ${FIB:?then:else
+ @echo ${FIB:?then:else}
+
+mod-remember-parse:
+ @echo $@:
+ @echo ${FIB:_} # ok
+ @echo ${FIB:__} # modifier name too long
+
+mod-sysv-parse:
+ @echo $@:
+ @echo ${FIB:3
+ @echo ${FIB:3=
+ @echo ${FIB:3=x3
+ @echo ${FIB:3=x3} # ok
diff --git a/contrib/bmake/unit-tests/modmatch.exp b/contrib/bmake/unit-tests/modmatch.exp
index a7bf8b748f5b..fcaf6c02ed69 100644
--- a/contrib/bmake/unit-tests/modmatch.exp
+++ b/contrib/bmake/unit-tests/modmatch.exp
@@ -14,7 +14,4 @@ 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/contrib/bmake/unit-tests/modmatch.mk b/contrib/bmake/unit-tests/modmatch.mk
index c631bbd3440f..f15b3d699a8e 100644
--- a/contrib/bmake/unit-tests/modmatch.mk
+++ b/contrib/bmake/unit-tests/modmatch.mk
@@ -1,3 +1,6 @@
+# $NetBSD: modmatch.mk,v 1.8 2020/08/16 20:03:53 rillig Exp $
+#
+# Tests for the :M and :S modifiers.
X=a b c d e
@@ -15,7 +18,7 @@ res = no
res = OK
.endif
-all: show-libs check-cclass slow
+all: show-libs
show-libs:
@for x in $X; do ${.MAKE} -f ${MAKEFILE} show LIB=$$x; done
@@ -25,15 +28,3 @@ 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]}
-
-# Before 2020-06-13, this expression took quite a long time in Str_Match,
-# calling itself 601080390 times for 16 asterisks.
-slow: .PHONY
- @:;: ${:U****************:M****************b:Q}
diff --git a/contrib/bmake/unit-tests/modmisc.exp b/contrib/bmake/unit-tests/modmisc.exp
index 619ae3c7fc11..94f131052fdc 100644
--- a/contrib/bmake/unit-tests/modmisc.exp
+++ b/contrib/bmake/unit-tests/modmisc.exp
@@ -1,3 +1,4 @@
+make: Unknown modifier '$'
path=':/bin:/tmp::/:.:/no/such/dir:.'
path='/bin:/tmp:/:/no/such/dir'
path='/bin:/tmp:/:/no/such/dir'
@@ -7,45 +8,14 @@ path_/usr/xbin=/opt/xbin/
paths=/bin /tmp / /no/such/dir /opt/xbin
PATHS=/BIN /TMP / /NO/SUCH/DIR /OPT/XBIN
The answer is 42
-dirname of 'a/b/c def a.b.c a.b/c a a.a .gitignore a a.a' is 'a/b . . a.b . . . . .'
-basename of 'a/b/c def a.b.c a.b/c a a.a .gitignore a a.a' is 'c def a.b.c c a a.a .gitignore a a.a'
-suffix of 'a/b/c def a.b.c a.b/c a a.a .gitignore a a.a' is 'c b/c a gitignore a'
-root of 'a/b/c def a.b.c a.b/c a a.a .gitignore a a.a' is 'a/b/c def a.b a a a a a'
S:
C:
@:
S:empty
C:empty
@:
-:a b b c:
-:a b b c:
-: b c:
-:a c:
-:x__ 3 x__ 3:
-:a b b c:
-:a b b c:
-: b c:
-make: RE substitution error: (details omitted)
-make: Unclosed substitution for (, missing)
-:C,word,____,:Q}:
-:a c:
-:x__ 3 x__ 3:
-:+one+ +two+ +three+:
-mod-at-resolve:w1d2d3w w2i3w w1i2d3 2i${RES3}w w1d2d3 2i${RES3} 1i${RES2}w:
-mod-subst-dollar:$1:
-mod-subst-dollar:$2:
-mod-subst-dollar:$3:
-mod-subst-dollar:$4:
-mod-subst-dollar:$5:
-mod-subst-dollar:$6:
-mod-subst-dollar:$7:
-mod-subst-dollar:$8:
-mod-subst-dollar:U8:
-mod-subst-dollar:$$$$:
-mod-loop-dollar:1:
-mod-loop-dollar:${word}:
-mod-loop-dollar:$3$:
-mod-loop-dollar:$${word}$:
-mod-loop-dollar:$$5$$:
-mod-loop-dollar:$$${word}$$:
+mod-quote: new
+
+line
+mod-break-many-words: 500
exit status 0
diff --git a/contrib/bmake/unit-tests/modmisc.mk b/contrib/bmake/unit-tests/modmisc.mk
index d0c334342934..f2977da8a0ba 100644
--- a/contrib/bmake/unit-tests/modmisc.mk
+++ b/contrib/bmake/unit-tests/modmisc.mk
@@ -1,4 +1,4 @@
-# $Id: modmisc.mk,v 1.1.1.3 2020/07/09 22:35:19 sjg Exp $
+# $Id: modmisc.mk,v 1.1.1.15 2020/08/23 15:52:08 sjg Exp $
#
# miscellaneous modifier tests
@@ -15,13 +15,15 @@ MOD_HOMES=S,/home/,/homes/,
MOD_OPT=@d@$${exists($$d):?$$d:$${d:S,/usr,/opt,}}@
MOD_SEP=S,:, ,g
-all: modvar modvarloop modsysv mod-HTE emptyvar undefvar
-all: mod-S mod-C mod-at-varname mod-at-resolve
-all: mod-subst-dollar mod-loop-dollar
+all: modvar modvarloop modsysv emptyvar undefvar
+all: mod-quote
+all: mod-break-many-words
+# See also sysv.mk.
modsysv:
@echo "The answer is ${libfoo.a:L:libfoo.a=42}"
+# Demonstrates modifiers that are given indirectly from a variable.
modvar:
@echo "path='${path}'"
@echo "path='${path:${MOD_NODOT}}'"
@@ -39,13 +41,6 @@ modvarloop:
@echo "paths=${paths}"
@echo "PATHS=${paths:tu}"
-PATHNAMES= a/b/c def a.b.c a.b/c a a.a .gitignore a a.a
-mod-HTE:
- @echo "dirname of '"${PATHNAMES:Q}"' is '"${PATHNAMES:H:Q}"'"
- @echo "basename of '"${PATHNAMES:Q}"' is '"${PATHNAMES:T:Q}"'"
- @echo "suffix of '"${PATHNAMES:Q}"' is '"${PATHNAMES:E:Q}"'"
- @echo "root of '"${PATHNAMES:Q}"' is '"${PATHNAMES:R:Q}"'"
-
# When a modifier is applied to the "" variable, the result is discarded.
emptyvar:
@echo S:${:S,^$,empty,}
@@ -61,67 +56,37 @@ undefvar:
@echo C:${:U:C,^$,empty,}
@echo @:${:U:@var@empty@}
-mod-S:
- @echo :${:Ua b b c:S,a b,,:Q}:
- @echo :${:Ua b b c:S,a b,,1:Q}:
- @echo :${:Ua b b c:S,a b,,W:Q}:
- @echo :${:Ua b b c:S,b,,g:Q}:
- @echo :${:U1 2 3 1 2 3:S,1 2,___,Wg:S,_,x,:Q}:
-
-mod-C:
- @echo :${:Ua b b c:C,a b,,:Q}:
- @echo :${:Ua b b c:C,a b,,1:Q}:
- @echo :${:Ua b b c:C,a b,,W:Q}:
- @echo :${:Uword1 word2:C,****,____,g:C,word,____,:Q}:
- @echo :${:Ua b b c:C,b,,g:Q}:
- @echo :${:U1 2 3 1 2 3:C,1 2,___,Wg:C,_,x,:Q}:
-
-# In the :@ modifier, the name of the loop variable can even be generated
-# dynamically. There's no practical use-case for this, and hopefully nobody
-# will ever depend on this, but technically it's possible.
-mod-at-varname:
- @echo :${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@:Q}:
-
-# The :@ modifier resolves the variables a little more often than expected.
-# In particular, it resolves _all_ variables from the context, and not only
-# the loop variable (in this case v).
-#
-# The d means direct reference, the i means indirect reference.
-RESOLVE= ${RES1} $${RES1}
-RES1= 1d${RES2} 1i$${RES2}
-RES2= 2d${RES3} 2i$${RES3}
-RES3= 3
-
-mod-at-resolve:
- @echo $@:${RESOLVE:@v@w${v}w@:Q}:
-
-# No matter how many dollar characters there are, they all get merged
-# into a single dollar by the :S modifier.
-mod-subst-dollar:
- @echo $@:${:U1:S,^,$,:Q}:
- @echo $@:${:U2:S,^,$$,:Q}:
- @echo $@:${:U3:S,^,$$$,:Q}:
- @echo $@:${:U4:S,^,$$$$,:Q}:
- @echo $@:${:U5:S,^,$$$$$,:Q}:
- @echo $@:${:U6:S,^,$$$$$$,:Q}:
- @echo $@:${:U7:S,^,$$$$$$$,:Q}:
- @echo $@:${:U8:S,^,$$$$$$$$,:Q}:
-# This generates no dollar at all:
- @echo $@:${:UU8:S,^,${:U$$$$$$$$},:Q}:
-# Here is an alternative way to generate dollar characters.
-# It's unexpectedly complicated though.
- @echo $@:${:U:range=5:ts\x24:C,[0-9],,g:Q}:
-
-# Demonstrate that it is possible to generate dollar characters using the
-# :@ modifier.
+
+mod-quote:
+ @echo $@: new${.newline:Q}${.newline:Q}line
+
+# Cover the bmake_realloc in brk_string.
+mod-break-many-words:
+ @echo $@: ${UNDEF:U:range=500:[#]}
+
+# To apply a modifier indirectly via another variable, the whole
+# modifier must be put into a single variable.
+.if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}"
+.warning unexpected
+.endif
+
+# Adding another level of indirection (the 2 nested :U expressions) helps.
+.if ${value:L:${:U${:US}${:U,value,replacement,}}} != "replacement"
+.warning unexpected
+.endif
+
+# Multiple indirect modifiers can be applied one after another as long as
+# they are separated with colons.
+.if ${value:L:${:US,a,A,}:${:US,e,E,}} != "vAluE"
+.warning unexpected
+.endif
+
+# An indirect variable that evaluates to the empty string is allowed though.
+# This makes it possible to define conditional modifiers, like this:
#
-# These are edge cases that could have resulted in a parse error as well
-# since the $@ at the end could have been interpreted as a variable, which
-# would mean a missing closing @ delimiter.
-mod-loop-dollar:
- @echo $@:${:U1:@word@${word}$@:Q}:
- @echo $@:${:U2:@word@$${word}$$@:Q}:
- @echo $@:${:U3:@word@$$${word}$$$@:Q}:
- @echo $@:${:U4:@word@$$$${word}$$$$@:Q}:
- @echo $@:${:U5:@word@$$$$${word}$$$$$@:Q}:
- @echo $@:${:U6:@word@$$$$$${word}$$$$$$@:Q}:
+# M.little-endian= S,1234,4321,
+# M.big-endian= # none
+.if ${value:L:${:Dempty}S,a,A,} != "vAlue"
+.warning unexpected
+.endif
+
diff --git a/contrib/bmake/unit-tests/modorder.exp b/contrib/bmake/unit-tests/modorder.exp
deleted file mode 100644
index 8e0aad2e2027..000000000000
--- a/contrib/bmake/unit-tests/modorder.exp
+++ /dev/null
@@ -1,12 +0,0 @@
-LIST = one two three four five six seven eight nine ten
-LIST:O = eight five four nine one seven six ten three two
-LIST:Or = two three ten six seven one nine four five eight
-LIST:Ox = Ok
-LIST:O:Ox = Ok
-LISTX = Ok
-LISTSX = Ok
-make: Bad modifier `:OX' for LIST
-BADMOD 1 = }
-make: Bad modifier `:OxXX' for LIST
-BADMOD 2 = XX}
-exit status 0
diff --git a/contrib/bmake/unit-tests/modorder.mk b/contrib/bmake/unit-tests/modorder.mk
deleted file mode 100644
index 89e64b43c57c..000000000000
--- a/contrib/bmake/unit-tests/modorder.mk
+++ /dev/null
@@ -1,24 +0,0 @@
-# $NetBSD: modorder.mk,v 1.3 2020/06/09 01:48:17 sjg Exp $
-
-LIST= one two three four five six seven eight nine ten
-LISTX= ${LIST:Ox}
-LISTSX:= ${LIST:Ox}
-TEST_RESULT= && echo Ok || echo Failed
-
-# unit-tests have to produce the same results on each run
-# so we cannot actually include :Ox output.
-all:
- @echo "LIST = ${LIST}"
- @echo "LIST:O = ${LIST:O}"
- @echo "LIST:Or = ${LIST:Or}"
- # 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 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}`"
- @echo "LISTSX = `test '${LISTSX}' = '${LISTSX}' ${TEST_RESULT}`"
- @echo "BADMOD 1 = ${LIST:OX}"
- @echo "BADMOD 2 = ${LIST:OxXX}"
diff --git a/contrib/bmake/unit-tests/modts.exp b/contrib/bmake/unit-tests/modts.exp
index 338964963a86..5db79fc96586 100644
--- a/contrib/bmake/unit-tests/modts.exp
+++ b/contrib/bmake/unit-tests/modts.exp
@@ -1,34 +1,3 @@
-LIST="one two three four five six"
-LIST:ts,="one,two,three,four,five,six"
-LIST:ts/:tu="ONE/TWO/THREE/FOUR/FIVE/SIX"
-LIST:ts::tu="ONE:TWO:THREE:FOUR:FIVE:SIX"
-LIST:ts:tu="ONETWOTHREEFOURFIVESIX"
-LIST:tu:ts/="ONE/TWO/THREE/FOUR/FIVE/SIX"
-LIST:ts:="one:two:three:four:five:six"
-LIST:ts="onetwothreefourfivesix"
-LIST:ts:S/two/2/="one2threefourfivesix"
-LIST:S/two/2/:ts="one2threefourfivesix"
-LIST:ts/:S/two/2/="one/2/three/four/five/six"
-Pretend the '/' in '/n' etc. below are back-slashes.
-LIST:ts/n="one
-two
-three
-four
-five
-six"
-LIST:ts/t="one two three four five six"
-LIST:ts/012:tu="ONE
-TWO
-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
@@ -36,4 +5,10 @@ LIST:ts/x:tu="\X:tu}"
FU_mod-ts="a/b/cool"
FU_mod-ts:ts:T="cool" == cool?
B.${AAA:ts}="Baaa" == Baaa?
+:ts :S => aaxBbxaaxbbxaaxbb
+:ts :S space => axa a axc
+:ts :S space :M => axaxaxaxc
+:ts :S => axa a axc
+:ts :S :@ => axa a axc
+:ts :S :@ :M => axaxaxaxc
exit status 0
diff --git a/contrib/bmake/unit-tests/modts.mk b/contrib/bmake/unit-tests/modts.mk
index e66dc25a2a02..3219538d5e4a 100644
--- a/contrib/bmake/unit-tests/modts.mk
+++ b/contrib/bmake/unit-tests/modts.mk
@@ -7,7 +7,7 @@ FU_mod-ts = a / b / cool
AAA= a a a
B.aaa= Baaa
-all: mod-ts
+all: mod-ts mod-ts-space
# Use print or printf iff they are builtin.
# XXX note that this causes problems, when make decides
@@ -21,24 +21,27 @@ PRINT= echo
.endif
mod-ts:
- @echo 'LIST="${LIST}"'
- @echo 'LIST:ts,="${LIST:ts,}"'
- @echo 'LIST:ts/:tu="${LIST:ts/:tu}"'
- @echo 'LIST:ts::tu="${LIST:ts::tu}"'
- @echo 'LIST:ts:tu="${LIST:ts:tu}"'
- @echo 'LIST:tu:ts/="${LIST:tu:ts/}"'
- @echo 'LIST:ts:="${LIST:ts:}"'
- @echo 'LIST:ts="${LIST:ts}"'
- @echo 'LIST:ts:S/two/2/="${LIST:ts:S/two/2/}"'
- @echo 'LIST:S/two/2/:ts="${LIST:S/two/2/:ts}"'
- @echo 'LIST:ts/:S/two/2/="${LIST:ts/:S/two/2/}"'
- @echo "Pretend the '/' in '/n' etc. below are back-slashes."
- @${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} 'FU_$@="${FU_${@:ts}:ts}"'
@${PRINT} 'FU_$@:ts:T="${FU_${@:ts}:ts:T}" == cool?'
@${PRINT} 'B.$${AAA:ts}="${B.${AAA:ts}}" == Baaa?'
+
+mod-ts-space:
+ # After the :ts modifier, the whole string is interpreted as a single
+ # word since all spaces have been replaced with x.
+ @${PRINT} ':ts :S => '${aa bb aa bb aa bb:L:tsx:S,b,B,:Q}
+
+ # The :ts modifier also applies to word separators that are added
+ # afterwards.
+ @${PRINT} ':ts :S space => '${a ababa c:L:tsx:S,b, ,g:Q}
+ @${PRINT} ':ts :S space :M => '${a ababa c:L:tsx:S,b, ,g:M*:Q}
+
+ # Not all modifiers behave this way though. Some of them always use
+ # a space as word separator instead of the :ts separator.
+ # This seems like an oversight during implementation.
+ @${PRINT} ':ts :S => '${a ababa c:L:tsx:S,b, ,g:Q}
+ @${PRINT} ':ts :S :@ => '${a ababa c:L:tsx:S,b, ,g:@v@${v}@:Q}
+
+ # A final :M* modifier applies the :ts separator again, though.
+ @${PRINT} ':ts :S :@ :M => '${a ababa c:L:tsx:S,b, ,g:@v@${v}@:M*:Q}
diff --git a/contrib/bmake/unit-tests/opt-backwards.exp b/contrib/bmake/unit-tests/opt-backwards.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-backwards.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-backwards.mk b/contrib/bmake/unit-tests/opt-backwards.mk
new file mode 100644
index 000000000000..bc0b1eff8530
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-backwards.mk
@@ -0,0 +1,8 @@
+# $NetBSD: opt-backwards.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the -B command line option.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt-chdir.exp b/contrib/bmake/unit-tests/opt-chdir.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-chdir.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-chdir.mk b/contrib/bmake/unit-tests/opt-chdir.mk
new file mode 100644
index 000000000000..8735fddbef9e
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-chdir.mk
@@ -0,0 +1,8 @@
+# $NetBSD: opt-chdir.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the -C command line option.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt-debug-g1.exp b/contrib/bmake/unit-tests/opt-debug-g1.exp
new file mode 100755
index 000000000000..d6d014a0353f
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-debug-g1.exp
@@ -0,0 +1,15 @@
+#*** Input graph:
+# all, made UNMADE, type OP_DEPENDS, flags none
+# made-target, made UNMADE, type OP_DEPENDS, flags none
+# made-target-no-sources, made UNMADE, type OP_DEPENDS, flags none
+# made-source, made UNMADE, type OP_DEPENDS, flags none
+# unmade-target, made UNMADE, type OP_DEPENDS, flags none
+# unmade-sources, made UNMADE, type none, flags none
+# unmade-target-no-sources, made UNMADE, type OP_DEPENDS, flags none
+
+
+#
+# Files that are only sources:
+# unmade-sources [unmade-sources]
+#*** Transformations:
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-debug-g1.mk b/contrib/bmake/unit-tests/opt-debug-g1.mk
new file mode 100755
index 000000000000..3104fbf91bc1
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-debug-g1.mk
@@ -0,0 +1,19 @@
+# $NetBSD: opt-debug-g1.mk,v 1.1 2020/08/27 19:00:17 rillig Exp $
+#
+# Tests for the -dg1 command line option, which prints the input
+# graph before making anything.
+
+all: made-target made-target-no-sources
+
+made-target: made-source
+
+made-source:
+
+made-target-no-sources:
+
+unmade-target: unmade-sources
+
+unmade-target-no-sources:
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt-debug.exp b/contrib/bmake/unit-tests/opt-debug.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-debug.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-debug.mk b/contrib/bmake/unit-tests/opt-debug.mk
new file mode 100644
index 000000000000..afd740a6caab
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-debug.mk
@@ -0,0 +1,8 @@
+# $NetBSD: opt-debug.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the -d command line option.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt-define.exp b/contrib/bmake/unit-tests/opt-define.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-define.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-define.mk b/contrib/bmake/unit-tests/opt-define.mk
new file mode 100644
index 000000000000..ce0516ba44bc
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-define.mk
@@ -0,0 +1,8 @@
+# $NetBSD: opt-define.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the -D command line option.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt-env.exp b/contrib/bmake/unit-tests/opt-env.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-env.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-env.mk b/contrib/bmake/unit-tests/opt-env.mk
new file mode 100644
index 000000000000..32e95ef41f5a
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-env.mk
@@ -0,0 +1,8 @@
+# $NetBSD: opt-env.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the -e command line option.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt-file.exp b/contrib/bmake/unit-tests/opt-file.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-file.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-file.mk b/contrib/bmake/unit-tests/opt-file.mk
new file mode 100644
index 000000000000..86bc100bebc2
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-file.mk
@@ -0,0 +1,8 @@
+# $NetBSD: opt-file.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the -f command line option.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt-ignore.exp b/contrib/bmake/unit-tests/opt-ignore.exp
new file mode 100644
index 000000000000..265e143e3471
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-ignore.exp
@@ -0,0 +1,12 @@
+dependency 1
+dependency 2
+dependency 3
+other 1
+other 2
+main 1
+main 2
+*** Error code 1 (ignored)
+*** Error code 7 (ignored)
+*** Error code 1 (ignored)
+*** Error code 1 (ignored)
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-ignore.mk b/contrib/bmake/unit-tests/opt-ignore.mk
new file mode 100644
index 000000000000..6d6d8dc3a339
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-ignore.mk
@@ -0,0 +1,30 @@
+# $NetBSD: opt-ignore.mk,v 1.3 2020/08/23 14:28:04 rillig Exp $
+#
+# Tests for the -i command line option, which ignores the exit status of the
+# shell commands, and just continues with the next command, even from the same
+# target.
+#
+# Is there a situation in which this option is useful?
+#
+# Why are the "Error code" lines all collected at the bottom of the output
+# file, where they cannot be related to the individual shell commands that
+# failed?
+
+all: dependency other
+
+dependency:
+ @echo dependency 1
+ @false
+ @echo dependency 2
+ @:; exit 7
+ @echo dependency 3
+
+other:
+ @echo other 1
+ @false
+ @echo other 2
+
+all:
+ @echo main 1
+ @false
+ @echo main 2
diff --git a/contrib/bmake/unit-tests/opt-include-dir.exp b/contrib/bmake/unit-tests/opt-include-dir.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-include-dir.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-include-dir.mk b/contrib/bmake/unit-tests/opt-include-dir.mk
new file mode 100644
index 000000000000..d61a6c979ea5
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-include-dir.mk
@@ -0,0 +1,8 @@
+# $NetBSD: opt-include-dir.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the -I command line option.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt-jobs-internal.exp b/contrib/bmake/unit-tests/opt-jobs-internal.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-jobs-internal.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-jobs-internal.mk b/contrib/bmake/unit-tests/opt-jobs-internal.mk
new file mode 100644
index 000000000000..5426807ca98b
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-jobs-internal.mk
@@ -0,0 +1,8 @@
+# $NetBSD: opt-jobs-internal.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the (intentionally undocumented) -J command line option.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt-jobs.exp b/contrib/bmake/unit-tests/opt-jobs.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-jobs.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-jobs.mk b/contrib/bmake/unit-tests/opt-jobs.mk
new file mode 100644
index 000000000000..7d54d08a8421
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-jobs.mk
@@ -0,0 +1,8 @@
+# $NetBSD: opt-jobs.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the -j command line option.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt-keep-going.exp b/contrib/bmake/unit-tests/opt-keep-going.exp
new file mode 100644
index 000000000000..de1b1ae582f4
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-keep-going.exp
@@ -0,0 +1,6 @@
+dependency 1
+other 1
+*** Error code 1 (continuing)
+*** Error code 1 (continuing)
+`all' not remade because of errors.
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-keep-going.mk b/contrib/bmake/unit-tests/opt-keep-going.mk
new file mode 100644
index 000000000000..1a2124ccb5f1
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-keep-going.mk
@@ -0,0 +1,24 @@
+# $NetBSD: opt-keep-going.mk,v 1.3 2020/08/23 14:28:04 rillig Exp $
+#
+# Tests for the -k command line option, which stops building a target as soon
+# as an error is detected, but continues building the other, independent
+# targets, as far as possible.
+
+all: dependency other
+
+dependency:
+ @echo dependency 1
+ @false
+ @echo dependency 2
+ @:; exit 7
+ @echo dependency 3
+
+other:
+ @echo other 1
+ @false
+ @echo other 2
+
+all:
+ @echo main 1
+ @false
+ @echo main 2
diff --git a/contrib/bmake/unit-tests/opt-m-include-dir.exp b/contrib/bmake/unit-tests/opt-m-include-dir.exp
new file mode 100644
index 000000000000..bce0e4be05ac
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-m-include-dir.exp
@@ -0,0 +1,2 @@
+ok
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-m-include-dir.mk b/contrib/bmake/unit-tests/opt-m-include-dir.mk
new file mode 100644
index 000000000000..6e0801390395
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-m-include-dir.mk
@@ -0,0 +1,61 @@
+# $NetBSD: opt-m-include-dir.mk,v 1.4 2020/09/01 20:14:34 rillig Exp $
+#
+# Tests for the -m command line option, which adds a directory to the
+# search path for the .include <...> directive.
+#
+# The .../canary.mk special argument starts searching in the current
+# directory and walks towards the file system root, until it finds a
+# directory that contains a file called canary.mk.
+#
+# To set up this scenario, the file step2.mk is created deep in a hierarchy
+# of subdirectories. Another file called opt-m-step3.mk is created a few
+# steps up in the directory hierarchy, serving as the canary file.
+#
+# Next to the canary file, there is opt-m-step3.mk. This file is found
+# by mentioning its simple name in an .include directive. It defines the
+# target "step2" that is needed by "step2.mk".
+
+.if ${.PARSEFILE:T} == "opt-m-include-dir.mk"
+
+# Set up the other files needed for this test.
+
+TEST_DIR:= ${.PARSEFILE:R}.tmp/sub/sub/sub/workdir
+CANARY_FILE:= ${.PARSEFILE:R}.tmp/sub/opt-m-canary.mk
+ACTUAL_FILE:= ${.PARSEFILE:R}.tmp/sub/opt-m-step3.mk
+
+_!= mkdir -p ${TEST_DIR}
+_!= > ${CANARY_FILE}
+_!= cp ${MAKEFILE} ${TEST_DIR}/step2.mk
+_!= cp ${MAKEFILE} ${ACTUAL_FILE}
+
+step1:
+ @${.MAKE} -C ${TEST_DIR} -f step2.mk step2
+
+.END:
+ @rm -rf ${MAKEFILE:R}.tmp
+
+.elif ${.PARSEFILE:T} == "step2.mk"
+
+# This is the file deep in the directory hierarchy. It sets up the
+# search path for the .include <...> directive and then includes a
+# single file from that search path.
+
+# This option adds .tmp/sub to the search path for .include <...>.
+.MAKEFLAGS: -m .../opt-m-canary.mk
+
+# This option does not add any directory to the search path since the
+# canary file does not exist.
+.MAKEFLAGS: -m .../does-not-exist
+
+.include <opt-m-step3.mk>
+
+.elif ${.PARSEFILE:T} == "opt-m-step3.mk"
+
+# This file is included by step2.mk.
+
+step2:
+ @echo ok
+
+.else
+. error
+.endif
diff --git a/contrib/bmake/unit-tests/opt-no-action-at-all.exp b/contrib/bmake/unit-tests/opt-no-action-at-all.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-no-action-at-all.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-no-action-at-all.mk b/contrib/bmake/unit-tests/opt-no-action-at-all.mk
new file mode 100644
index 000000000000..6ab385946691
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-no-action-at-all.mk
@@ -0,0 +1,8 @@
+# $NetBSD: opt-no-action-at-all.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the -N command line option.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt-no-action.exp b/contrib/bmake/unit-tests/opt-no-action.exp
new file mode 100644
index 000000000000..381d68e2f2d9
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-no-action.exp
@@ -0,0 +1,13 @@
+command during parsing
+echo '.BEGIN: hidden command'
+echo '.BEGIN: run always'
+.BEGIN: run always
+echo 'main: hidden command'
+echo 'main: run always'
+main: run always
+run-always: hidden command
+run-always: run always
+echo '.END: hidden command'
+echo '.END: run always'
+.END: run always
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-no-action.mk b/contrib/bmake/unit-tests/opt-no-action.mk
new file mode 100644
index 000000000000..32b3b1564acb
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-no-action.mk
@@ -0,0 +1,33 @@
+# $NetBSD: opt-no-action.mk,v 1.3 2020/08/19 05:25:26 rillig Exp $
+#
+# Tests for the -n command line option, which runs almost no commands.
+# It just outputs them, to be inspected by human readers.
+# Only commands that are in a .MAKE target or prefixed by '+' are run.
+
+# This command cannot be prevented from being run since it is used at parse
+# time, and any later variable assignments may depend on its result.
+!= echo 'command during parsing' 1>&2; echo
+
+all: main
+all: run-always
+
+# Both of these commands are printed, but only the '+' command is run.
+.BEGIN:
+ @echo '$@: hidden command'
+ @+echo '$@: run always'
+
+# Both of these commands are printed, but only the '+' command is run.
+main:
+ @echo '$@: hidden command'
+ @+echo '$@: run always'
+
+# None of these commands is printed, but both are run, because this target
+# depends on the special source ".MAKE".
+run-always: .MAKE
+ @echo '$@: hidden command'
+ @+echo '$@: run always'
+
+# Both of these commands are printed, but only the '+' command is run.
+.END:
+ @echo '$@: hidden command'
+ @+echo '$@: run always'
diff --git a/contrib/bmake/unit-tests/opt-query.exp b/contrib/bmake/unit-tests/opt-query.exp
new file mode 100644
index 000000000000..38025dcf4d3a
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-query.exp
@@ -0,0 +1,2 @@
+command during parsing
+exit status 1
diff --git a/contrib/bmake/unit-tests/opt-query.mk b/contrib/bmake/unit-tests/opt-query.mk
new file mode 100644
index 000000000000..04e605991140
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-query.mk
@@ -0,0 +1,24 @@
+# $NetBSD: opt-query.mk,v 1.3 2020/08/19 05:13:18 rillig Exp $
+#
+# Tests for the -q command line option.
+#
+# The -q option only looks at the dependencies between the targets.
+# None of the commands in the targets are run, not even those that are
+# prefixed with '+'.
+
+# This command cannot be prevented from being run since it is used at parse
+# time, and any later variable assignments may depend on its result.
+!= echo 'command during parsing' 1>&2; echo
+
+# None of these commands are run.
+.BEGIN:
+ @echo '$@: hidden command'
+ @+echo '$@: run always'
+
+# None of these commands are run.
+all:
+ @echo '$@: hidden command'
+ @+echo '$@: run always'
+
+# The exit status 1 is because the "all" target has to be made, that is,
+# it is not up-to-date.
diff --git a/contrib/bmake/unit-tests/opt-raw.exp b/contrib/bmake/unit-tests/opt-raw.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-raw.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-raw.mk b/contrib/bmake/unit-tests/opt-raw.mk
new file mode 100644
index 000000000000..d3591bb99dab
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-raw.mk
@@ -0,0 +1,8 @@
+# $NetBSD: opt-raw.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the -r command line option.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt-silent.exp b/contrib/bmake/unit-tests/opt-silent.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-silent.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-silent.mk b/contrib/bmake/unit-tests/opt-silent.mk
new file mode 100644
index 000000000000..7822d46ac48a
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-silent.mk
@@ -0,0 +1,8 @@
+# $NetBSD: opt-silent.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the -s command line option.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt-touch.exp b/contrib/bmake/unit-tests/opt-touch.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-touch.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-touch.mk b/contrib/bmake/unit-tests/opt-touch.mk
new file mode 100644
index 000000000000..5093c5cad6ac
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-touch.mk
@@ -0,0 +1,8 @@
+# $NetBSD: opt-touch.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the -t command line option.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt-tracefile.exp b/contrib/bmake/unit-tests/opt-tracefile.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-tracefile.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-tracefile.mk b/contrib/bmake/unit-tests/opt-tracefile.mk
new file mode 100644
index 000000000000..b62392ca913c
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-tracefile.mk
@@ -0,0 +1,8 @@
+# $NetBSD: opt-tracefile.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the -T command line option.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt-var-expanded.exp b/contrib/bmake/unit-tests/opt-var-expanded.exp
new file mode 100644
index 000000000000..4fcc0b99ae1b
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-var-expanded.exp
@@ -0,0 +1,3 @@
+other value $$
+value
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-var-expanded.mk b/contrib/bmake/unit-tests/opt-var-expanded.mk
new file mode 100644
index 000000000000..0b4088a82082
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-var-expanded.mk
@@ -0,0 +1,6 @@
+# $NetBSD: opt-var-expanded.mk,v 1.3 2020/08/23 14:28:04 rillig Exp $
+#
+# Tests for the -v command line option.
+
+VAR= other ${VALUE} $$$$
+VALUE= value
diff --git a/contrib/bmake/unit-tests/opt-var-literal.exp b/contrib/bmake/unit-tests/opt-var-literal.exp
new file mode 100644
index 000000000000..e7f653b769b9
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-var-literal.exp
@@ -0,0 +1,3 @@
+other ${VALUE} $$$$
+value
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-var-literal.mk b/contrib/bmake/unit-tests/opt-var-literal.mk
new file mode 100644
index 000000000000..a819e7537105
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-var-literal.mk
@@ -0,0 +1,6 @@
+# $NetBSD: opt-var-literal.mk,v 1.3 2020/08/23 14:28:04 rillig Exp $
+#
+# Tests for the -V command line option.
+
+VAR= other ${VALUE} $$$$
+VALUE= value
diff --git a/contrib/bmake/unit-tests/opt-warnings-as-errors.exp b/contrib/bmake/unit-tests/opt-warnings-as-errors.exp
new file mode 100644
index 000000000000..bd54bb673f08
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-warnings-as-errors.exp
@@ -0,0 +1,7 @@
+make: "opt-warnings-as-errors.mk" line 5: warning: message 1
+make: parsing warnings being treated as errors
+make: "opt-warnings-as-errors.mk" line 6: warning: message 2
+parsing continues
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/opt-warnings-as-errors.mk b/contrib/bmake/unit-tests/opt-warnings-as-errors.mk
new file mode 100644
index 000000000000..905753410db0
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-warnings-as-errors.mk
@@ -0,0 +1,11 @@
+# $NetBSD: opt-warnings-as-errors.mk,v 1.3 2020/08/23 14:28:04 rillig Exp $
+#
+# Tests for the -W command line option, which turns warnings into errors.
+
+.warning message 1
+.warning message 2
+
+_!= echo 'parsing continues' 1>&2
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt-where-am-i.exp b/contrib/bmake/unit-tests/opt-where-am-i.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-where-am-i.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-where-am-i.mk b/contrib/bmake/unit-tests/opt-where-am-i.mk
new file mode 100644
index 000000000000..9158a598174c
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-where-am-i.mk
@@ -0,0 +1,8 @@
+# $NetBSD: opt-where-am-i.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the -w command line option.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt-x-reduce-exported.exp b/contrib/bmake/unit-tests/opt-x-reduce-exported.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-x-reduce-exported.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-x-reduce-exported.mk b/contrib/bmake/unit-tests/opt-x-reduce-exported.mk
new file mode 100644
index 000000000000..7ee8e7c7eff0
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-x-reduce-exported.mk
@@ -0,0 +1,8 @@
+# $NetBSD: opt-x-reduce-exported.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the -x command line option.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/opt.exp b/contrib/bmake/unit-tests/opt.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt.mk b/contrib/bmake/unit-tests/opt.mk
new file mode 100644
index 000000000000..eae430965df7
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt.mk
@@ -0,0 +1,8 @@
+# $NetBSD: opt.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the command line options.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/phony-end.exp b/contrib/bmake/unit-tests/phony-end.exp
index c3c517ccc25c..f1bff4ef6d5e 100644
--- a/contrib/bmake/unit-tests/phony-end.exp
+++ b/contrib/bmake/unit-tests/phony-end.exp
@@ -1,5 +1,5 @@
.TARGET="phony" .PREFIX="phony" .IMPSRC=""
-.TARGET="all" .PREFIX="all" .IMPSRC="phony"
+.TARGET="all" .PREFIX="all" .IMPSRC=""
.TARGET="ok" .PREFIX="ok" .IMPSRC=""
.TARGET="also.ok" .PREFIX="also.ok" .IMPSRC=""
.TARGET="bug" .PREFIX="bug" .IMPSRC=""
diff --git a/contrib/bmake/unit-tests/posix1.mk b/contrib/bmake/unit-tests/posix1.mk
index 50b0a63ee696..f5d8e21678ea 100644
--- a/contrib/bmake/unit-tests/posix1.mk
+++ b/contrib/bmake/unit-tests/posix1.mk
@@ -1,4 +1,4 @@
-# $NetBSD: posix1.mk,v 1.3 2014/08/30 22:21:08 sjg Exp $
+# $NetBSD: posix1.mk,v 1.4 2020/08/10 18:19:58 rillig Exp $
# Keep the default suffixes from interfering, just in case.
.SUFFIXES:
@@ -9,6 +9,8 @@ all: line-continuations suffix-substitution localvars
.BEGIN: clean
clean:
@rm -f lib.a dir/* dummy obj*
+.END:
+ @rm -f lib.a dir/* dummy obj*
#
# Line continuations
diff --git a/contrib/bmake/unit-tests/recursive.exp b/contrib/bmake/unit-tests/recursive.exp
new file mode 100644
index 000000000000..bb5db75a474c
--- /dev/null
+++ b/contrib/bmake/unit-tests/recursive.exp
@@ -0,0 +1,5 @@
+make: "recursive.mk" line 34: Unclosed variable "MISSING_PAREN"
+make: "recursive.mk" line 35: Unclosed variable "MISSING_BRACE"
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/recursive.mk b/contrib/bmake/unit-tests/recursive.mk
new file mode 100644
index 000000000000..bc5a2817b333
--- /dev/null
+++ b/contrib/bmake/unit-tests/recursive.mk
@@ -0,0 +1,37 @@
+# $NetBSD: recursive.mk,v 1.2 2020/08/06 05:52:45 rillig Exp $
+#
+# In -dL mode, a variable may get expanded before it makes sense.
+# This would stop make from doing anything since the "recursive" error
+# is fatal and exits immediately.
+#
+# The purpose of evaluating that variable early was just to detect
+# whether there are unclosed variables. It might be enough to parse the
+# variable value without VARE_WANTRES for that purpose.
+#
+# Seen in pkgsrc/x11/libXfixes, and probably many more package that use
+# GNU Automake.
+
+AM_V_lt = $(am__v_lt_$(V))
+am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY))
+am__v_lt_0 = --silent
+am__v_lt_1 =
+
+# On 2020-08-06, make reported: "Variable am__v_lt_ is recursive."
+libXfixes_la_LINK = ... $(AM_V_lt) ...
+
+# somewhere later ...
+AM_DEFAULT_VERBOSITY = 1
+
+
+# The purpose of the -dL flag is to detect unclosed variables. This
+# can be achieved by just parsing the variable and not evaluating it.
+#
+# When the variable is only parsed but not evaluated, bugs in nested
+# variables are not discovered. But these are hard to produce anyway,
+# therefore that's acceptable. In most practical cases, the missing
+# brace would be detected directly in the line where it is produced.
+MISSING_BRACE_INDIRECT:= ${:U\${MISSING_BRACE}
+UNCLOSED = $(MISSING_PAREN
+UNCLOSED = ${MISSING_BRACE
+UNCLOSED = ${MISSING_BRACE_INDIRECT}
+
diff --git a/contrib/bmake/unit-tests/sh-dots.exp b/contrib/bmake/unit-tests/sh-dots.exp
new file mode 100755
index 000000000000..19482717087b
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-dots.exp
@@ -0,0 +1,15 @@
+first first
+hidden hidden
+make: exec(...) failed (No such file or directory)
+hidden delayed hidden
+repeated repeated
+commented commented
+*** Error code 1 (ignored)
+... # Run the below commands later
+<normalized: ...: not found>
+commented delayed commented
+first delayed first
+repeated delayed repeated
+repeated delayed twice repeated
+*** Error code 127 (ignored)
+exit status 0
diff --git a/contrib/bmake/unit-tests/sh-dots.mk b/contrib/bmake/unit-tests/sh-dots.mk
new file mode 100755
index 000000000000..36da5bce7a53
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-dots.mk
@@ -0,0 +1,37 @@
+# $NetBSD: sh-dots.mk,v 1.1 2020/08/22 11:27:02 rillig Exp $
+#
+# Tests for the special shell command line "...", which does not run the
+# commands below it but appends them to the list of commands that are run
+# at the end.
+
+all: first hidden repeated commented
+
+# The ${.TARGET} correctly expands to the target name, even though the
+# commands are run separately from the main commands.
+first:
+ @echo first ${.TARGET}
+ ...
+ @echo first delayed ${.TARGET}
+
+# The dots cannot be prefixed by the usual @-+ characters.
+# They must be written exactly as dots.
+hidden: .IGNORE
+ @echo hidden ${.TARGET}
+ @...
+ @echo hidden delayed ${.TARGET}
+
+# Since the shell command lines don't recognize '#' as comment character,
+# the "..." is not interpreted specially here.
+commented: .IGNORE
+ @echo commented ${.TARGET}
+ ... # Run the below commands later
+ @echo commented delayed ${.TARGET}
+
+# The "..." can appear more than once, even though that doesn't make sense.
+# The second "..." is a no-op.
+repeated: .IGNORE
+ @echo repeated ${.TARGET}
+ ...
+ @echo repeated delayed ${.TARGET}
+ ...
+ @echo repeated delayed twice ${.TARGET}
diff --git a/contrib/bmake/unit-tests/sh-jobs-error.exp b/contrib/bmake/unit-tests/sh-jobs-error.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-jobs-error.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/sh-jobs-error.mk b/contrib/bmake/unit-tests/sh-jobs-error.mk
new file mode 100644
index 000000000000..febc06999366
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-jobs-error.mk
@@ -0,0 +1,9 @@
+# $NetBSD: sh-jobs-error.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for error handling in the "run in jobs mode" part of the "Shell
+# Commands" section from the manual page.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/sh-jobs.exp b/contrib/bmake/unit-tests/sh-jobs.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-jobs.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/sh-jobs.mk b/contrib/bmake/unit-tests/sh-jobs.mk
new file mode 100644
index 000000000000..62172c2a0c86
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-jobs.mk
@@ -0,0 +1,9 @@
+# $NetBSD: sh-jobs.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the "run in jobs mode" part of the "Shell Commands" section
+# from the manual page.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/sh-leading-at.exp b/contrib/bmake/unit-tests/sh-leading-at.exp
new file mode 100644
index 000000000000..5ffa84690a40
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-leading-at.exp
@@ -0,0 +1,5 @@
+ok
+space after @
+echo 'echoed'
+echoed
+exit status 0
diff --git a/contrib/bmake/unit-tests/sh-leading-at.mk b/contrib/bmake/unit-tests/sh-leading-at.mk
new file mode 100644
index 000000000000..19a6e59e4e6a
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-leading-at.mk
@@ -0,0 +1,10 @@
+# $NetBSD: sh-leading-at.mk,v 1.3 2020/08/22 09:16:08 rillig Exp $
+#
+# Tests for shell commands preceded by an '@', to suppress printing
+# the command to stdout.
+
+all:
+ @
+ @echo 'ok'
+ @ echo 'space after @'
+ echo 'echoed'
diff --git a/contrib/bmake/unit-tests/sh-leading-hyphen.exp b/contrib/bmake/unit-tests/sh-leading-hyphen.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-leading-hyphen.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/sh-leading-hyphen.mk b/contrib/bmake/unit-tests/sh-leading-hyphen.mk
new file mode 100644
index 000000000000..94be43495afb
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-leading-hyphen.mk
@@ -0,0 +1,9 @@
+# $NetBSD: sh-leading-hyphen.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for shell commands preceded by a '-', to ignore the exit status of
+# the command line.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/sh-leading-plus.exp b/contrib/bmake/unit-tests/sh-leading-plus.exp
new file mode 100644
index 000000000000..eb586d29f1c2
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-leading-plus.exp
@@ -0,0 +1,4 @@
+echo 'this command is not run'
+echo 'this command is run'
+this command is run
+exit status 0
diff --git a/contrib/bmake/unit-tests/sh-leading-plus.mk b/contrib/bmake/unit-tests/sh-leading-plus.mk
new file mode 100644
index 000000000000..75279d7d57fd
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-leading-plus.mk
@@ -0,0 +1,8 @@
+# $NetBSD: sh-leading-plus.mk,v 1.3 2020/08/23 14:46:33 rillig Exp $
+#
+# Tests for shell commands preceded by a '+', to run them even if
+# the command line option -n is given.
+
+all:
+ @echo 'this command is not run'
+ @+echo 'this command is run'
diff --git a/contrib/bmake/unit-tests/sh-meta-chars.exp b/contrib/bmake/unit-tests/sh-meta-chars.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-meta-chars.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/sh-meta-chars.mk b/contrib/bmake/unit-tests/sh-meta-chars.mk
new file mode 100644
index 000000000000..126ca2ceb118
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-meta-chars.mk
@@ -0,0 +1,11 @@
+# $NetBSD: sh-meta-chars.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for running shell commands that contain meta-characters.
+#
+# These meta-characters decide whether the command is run by the shell
+# or executed directly via execv. See Cmd_Exec for details.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/sh-multi-line.exp b/contrib/bmake/unit-tests/sh-multi-line.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-multi-line.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/sh-multi-line.mk b/contrib/bmake/unit-tests/sh-multi-line.mk
new file mode 100644
index 000000000000..35a773f5dde6
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-multi-line.mk
@@ -0,0 +1,9 @@
+# $NetBSD: sh-multi-line.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for multi-line shell commands, to ensure that the line breaks
+# are preserved.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/sh-single-line.exp b/contrib/bmake/unit-tests/sh-single-line.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-single-line.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/sh-single-line.mk b/contrib/bmake/unit-tests/sh-single-line.mk
new file mode 100644
index 000000000000..9dae7f80c9a9
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh-single-line.mk
@@ -0,0 +1,12 @@
+# $NetBSD: sh-single-line.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for running single-line shell commands.
+#
+# In jobs mode, the shell commands are combined into a single shell
+# program, as described in the manual page, section "Shell Commands",
+# "the entire script".
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/sh.exp b/contrib/bmake/unit-tests/sh.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/sh.mk b/contrib/bmake/unit-tests/sh.mk
new file mode 100644
index 000000000000..f79a4099e990
--- /dev/null
+++ b/contrib/bmake/unit-tests/sh.mk
@@ -0,0 +1,9 @@
+# $NetBSD: sh.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for running shell commands from the targets, or from the != variable
+# assignment operator or the :sh variable modifier.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/sysv.exp b/contrib/bmake/unit-tests/sysv.exp
index 780a187783f3..610f97c39e85 100644
--- a/contrib/bmake/unit-tests/sysv.exp
+++ b/contrib/bmake/unit-tests/sysv.exp
@@ -12,5 +12,4 @@ asam.c.c
asam.c
a.c.c
-ax:Q b c d eb
exit status 0
diff --git a/contrib/bmake/unit-tests/sysv.mk b/contrib/bmake/unit-tests/sysv.mk
index 3a987441ee42..5c87579cc11b 100644
--- a/contrib/bmake/unit-tests/sysv.mk
+++ b/contrib/bmake/unit-tests/sysv.mk
@@ -1,4 +1,6 @@
-# $Id: sysv.mk,v 1.5 2020/07/04 18:16:55 sjg Exp $
+# $Id: sysv.mk,v 1.9 2020/08/23 16:08:32 sjg Exp $
+
+all: foo fun sam bla
FOO ?=
FOOBAR = ${FOO:=bar}
@@ -11,8 +13,6 @@ FUN = ${B}${S}fun
SUN = the Sun
# we expect nothing when FOO is empty
-all: foo fun sam bla words
-
foo:
@echo FOOBAR = ${FOOBAR}
.if empty(FOO)
@@ -41,8 +41,3 @@ BLA=
bla:
@echo $(BLA:%=foo/%x)
-
-# The :Q looks like a modifier but isn't.
-# It is part of the replacement string.
-words:
- @echo a${a b c d e:L:%a=x:Q}b
diff --git a/contrib/bmake/unit-tests/unexport-env.mk b/contrib/bmake/unit-tests/unexport-env.mk
index aaabcd46464a..bc5fb4914ddc 100644
--- a/contrib/bmake/unit-tests/unexport-env.mk
+++ b/contrib/bmake/unit-tests/unexport-env.mk
@@ -1,6 +1,7 @@
-# $Id: unexport-env.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $
+# $Id: unexport-env.mk,v 1.1.1.2 2020/07/28 16:57:18 sjg Exp $
# pick up a bunch of exported vars
+FILTER_CMD= grep ^UT_
.include "export.mk"
# an example of setting up a minimal environment.
diff --git a/contrib/bmake/unit-tests/unexport.mk b/contrib/bmake/unit-tests/unexport.mk
index 0f1245292ba5..7d2e8f275173 100644
--- a/contrib/bmake/unit-tests/unexport.mk
+++ b/contrib/bmake/unit-tests/unexport.mk
@@ -1,8 +1,19 @@
-# $Id: unexport.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $
+# $Id: unexport.mk,v 1.1.1.3 2020/08/08 22:34:25 sjg Exp $
# pick up a bunch of exported vars
+FILTER_CMD= grep ^UT_
.include "export.mk"
.unexport UT_ZOO UT_FOO
UT_TEST = unexport
+
+# Until 2020-08-08, Var_UnExport had special handling for '\n', that code
+# was not reachable though. At that point, backslash-newline has already
+# been replaced with a simple space, and variables are not yet expanded.
+UT_BEFORE_NL= before
+UT_AFTER_NL= after
+.export UT_BEFORE_NL UT_AFTER_NL
+.unexport \
+ UT_BEFORE_NL
+.unexport ${.newline} UT_AFTER_NL
diff --git a/contrib/bmake/unit-tests/use-inference.exp b/contrib/bmake/unit-tests/use-inference.exp
new file mode 100644
index 000000000000..14ecf0550574
--- /dev/null
+++ b/contrib/bmake/unit-tests/use-inference.exp
@@ -0,0 +1,4 @@
+Building use-inference.from from nothing
+make: don't know how to make use-inference.to (continuing)
+`all' not remade because of errors.
+exit status 0
diff --git a/contrib/bmake/unit-tests/use-inference.mk b/contrib/bmake/unit-tests/use-inference.mk
new file mode 100644
index 000000000000..b0e5017bc6fb
--- /dev/null
+++ b/contrib/bmake/unit-tests/use-inference.mk
@@ -0,0 +1,35 @@
+# $NetBSD: use-inference.mk,v 1.1 2020/08/09 16:32:28 rillig Exp $
+#
+# Demonstrate that .USE rules do not have an effect on inference rules.
+# At least not in the special case where the inference rule does not
+# have any associated commands.
+
+.SUFFIXES:
+.SUFFIXES: .from .to
+
+all: use-inference.to
+
+verbose: .USE
+ @echo 'Verbosely making $@ out of $>'
+
+.from.to: verbose
+# Since this inference rule does not have any associated commands, it
+# is ignored.
+#
+# @echo 'Building $@ from $<'
+
+use-inference.from: # assume it exists
+ @echo 'Building $@ from nothing'
+
+# Possible but unproven explanation:
+#
+# The main target is "all", which depends on "use-inference.to".
+# The inference connects the .from to the .to file, otherwise make
+# would not know that the .from file would need to be built.
+#
+# The .from file is then built.
+#
+# After this, make stops since it doesn't know how to make the .to file.
+# This is strange since make definitely knows about the .from.to suffix
+# inference rule. But it seems to ignore it, maybe because it doesn't
+# have any associated commands.
diff --git a/contrib/bmake/unit-tests/var-class-cmdline.exp b/contrib/bmake/unit-tests/var-class-cmdline.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-class-cmdline.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/var-class-cmdline.mk b/contrib/bmake/unit-tests/var-class-cmdline.mk
new file mode 100644
index 000000000000..c43b5351c329
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-class-cmdline.mk
@@ -0,0 +1,8 @@
+# $NetBSD: var-class-cmdline.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for variables specified on the command line.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/var-class-env.exp b/contrib/bmake/unit-tests/var-class-env.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-class-env.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/var-class-env.mk b/contrib/bmake/unit-tests/var-class-env.mk
new file mode 100644
index 000000000000..6e6b4891d3fd
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-class-env.mk
@@ -0,0 +1,8 @@
+# $NetBSD: var-class-env.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for variables specified in the process environment.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/var-class-global.exp b/contrib/bmake/unit-tests/var-class-global.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-class-global.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/var-class-global.mk b/contrib/bmake/unit-tests/var-class-global.mk
new file mode 100644
index 000000000000..81345ffda463
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-class-global.mk
@@ -0,0 +1,8 @@
+# $NetBSD: var-class-global.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for global variables, which are the most common variables.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/var-class-local-legacy.exp b/contrib/bmake/unit-tests/var-class-local-legacy.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-class-local-legacy.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/var-class-local-legacy.mk b/contrib/bmake/unit-tests/var-class-local-legacy.mk
new file mode 100644
index 000000000000..bfd9733fd42b
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-class-local-legacy.mk
@@ -0,0 +1,8 @@
+# $NetBSD: var-class-local-legacy.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for legacy target-local variables, such as ${<F} or ${@D}.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/var-class-local.exp b/contrib/bmake/unit-tests/var-class-local.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-class-local.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/var-class-local.mk b/contrib/bmake/unit-tests/var-class-local.mk
new file mode 100644
index 000000000000..e75f08ba75a3
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-class-local.mk
@@ -0,0 +1,8 @@
+# $NetBSD: var-class-local.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for target-local variables, such as ${.TARGET} or $@.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/var-class.exp b/contrib/bmake/unit-tests/var-class.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-class.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/var-class.mk b/contrib/bmake/unit-tests/var-class.mk
new file mode 100644
index 000000000000..b20fca565e16
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-class.mk
@@ -0,0 +1,9 @@
+# $NetBSD: var-class.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the different variable classes (local, command-line, global,
+# environment), and which of them takes precedence over the others.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/var-op-append.exp b/contrib/bmake/unit-tests/var-op-append.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-op-append.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/var-op-append.mk b/contrib/bmake/unit-tests/var-op-append.mk
new file mode 100644
index 000000000000..b75880f95838
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-op-append.mk
@@ -0,0 +1,9 @@
+# $NetBSD: var-op-append.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the += variable assignment operator, which appends to a variable,
+# creating it if necessary.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/var-op-assign.exp b/contrib/bmake/unit-tests/var-op-assign.exp
new file mode 100644
index 000000000000..0e9e2d211a5f
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-op-assign.exp
@@ -0,0 +1,6 @@
+this will be evaluated later
+make: "var-op-assign.mk" line 52: Need an operator
+make: "var-op-assign.mk" line 86: Parsing still continues until here.
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/var-op-assign.mk b/contrib/bmake/unit-tests/var-op-assign.mk
new file mode 100644
index 000000000000..dadff6ed4b87
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-op-assign.mk
@@ -0,0 +1,89 @@
+# $NetBSD: var-op-assign.mk,v 1.4 2020/08/25 16:20:32 rillig Exp $
+#
+# Tests for the = variable assignment operator, which overwrites an existing
+# variable or creates it.
+
+# This is a simple variable assignment.
+# To the left of the assignment operator '=' there is the variable name,
+# and to the right is the variable value.
+#
+VAR= value
+
+# This condition demonstrates that whitespace around the assignment operator
+# is discarded. Otherwise the value would start with a single tab.
+#
+.if ${VAR} != "value"
+.error
+.endif
+
+# Whitespace to the left of the assignment operator is ignored as well.
+# The variable value can contain arbitrary characters.
+#
+# The '#' needs to be escaped with a backslash, this happens in a very
+# early stage of parsing and applies to all line types, except for the
+# commands, which are indented with a tab.
+#
+# The '$' needs to be escaped with another '$', otherwise it would refer to
+# another variable.
+#
+VAR =new value and \# some $$ special characters # comment
+
+# When a string literal appears in a condition, the escaping rules are
+# different. Run make with the -dc option to see the details.
+.if ${VAR} != "new value and \# some \$ special characters"
+.error ${VAR}
+.endif
+
+# The variable value may contain references to other variables.
+# In this example, the reference is to the variable with the empty name,
+# which always expands to an empty string. This alone would not produce
+# any side-effects, therefore the variable has a :!...! modifier that
+# executes a shell command.
+VAR= ${:! echo 'not yet evaluated' 1>&2 !}
+VAR= ${:! echo 'this will be evaluated later' 1>&2 !}
+
+# Now force the variable to be evaluated.
+# This outputs the line to stderr.
+.if ${VAR}
+.endif
+
+# In a variable assignment, the variable name must consist of a single word.
+#
+VARIABLE NAME= variable value
+
+# But if the whitespace appears inside parentheses or braces, everything is
+# fine.
+#
+# XXX: This was not an intentional decision, as variable names typically
+# neither contain parentheses nor braces. This is only a side-effect from
+# the implementation of the parser, which cheats when parsing a variable
+# name. It only counts parentheses and braces instead of properly parsing
+# nested variable expressions such as VAR.${param}.
+#
+VAR(spaces in parentheses)= ()
+VAR{spaces in braces}= {}
+
+# Be careful and use indirect variable names here, to prevent accidentally
+# accepting the test in case the parser just uses "VAR" as the variable name,
+# ignoring all the rest.
+#
+VARNAME_PAREN= VAR(spaces in parentheses)
+VARNAME_BRACES= VAR{spaces in braces}
+
+.if ${${VARNAME_PAREN}} != "()"
+.error
+.endif
+
+.if ${${VARNAME_BRACES}} != "{}"
+.error
+.endif
+
+# In safe mode, parsing would stop immediately after the "VARIABLE NAME="
+# line, since any commands run after that are probably working with
+# unexpected variable values.
+#
+# Therefore, just output an info message.
+.info Parsing still continues until here.
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/var-op-default.exp b/contrib/bmake/unit-tests/var-op-default.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-op-default.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/var-op-default.mk b/contrib/bmake/unit-tests/var-op-default.mk
new file mode 100644
index 000000000000..afb0c55f827c
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-op-default.mk
@@ -0,0 +1,9 @@
+# $NetBSD: var-op-default.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the ?= variable assignment operator, which only assigns
+# if the variable is still undefined.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/var-op-expand.exp b/contrib/bmake/unit-tests/var-op-expand.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-op-expand.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/var-op-expand.mk b/contrib/bmake/unit-tests/var-op-expand.mk
new file mode 100644
index 000000000000..07c5fb647759
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-op-expand.mk
@@ -0,0 +1,9 @@
+# $NetBSD: var-op-expand.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the := variable assignment operator, which expands its
+# right-hand side.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/var-op-shell.exp b/contrib/bmake/unit-tests/var-op-shell.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-op-shell.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/var-op-shell.mk b/contrib/bmake/unit-tests/var-op-shell.mk
new file mode 100644
index 000000000000..83580a89e6c2
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-op-shell.mk
@@ -0,0 +1,9 @@
+# $NetBSD: var-op-shell.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the != variable assignment operator, which runs its right-hand
+# side through the shell.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/var-op.exp b/contrib/bmake/unit-tests/var-op.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-op.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/var-op.mk b/contrib/bmake/unit-tests/var-op.mk
new file mode 100644
index 000000000000..9c9ffb8782f5
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-op.mk
@@ -0,0 +1,8 @@
+# $NetBSD: var-op.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the variable assignment operators.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/vardebug.exp b/contrib/bmake/unit-tests/vardebug.exp
new file mode 100644
index 000000000000..8a9eefad4b0c
--- /dev/null
+++ b/contrib/bmake/unit-tests/vardebug.exp
@@ -0,0 +1,80 @@
+Global:RELEVANT = yes
+Global:VAR = added
+Global:VAR = overwritten
+Global:delete VAR
+Global:delete VAR (not found)
+Var_Parse: ${:U} with VARE_WANTRES
+Applying ${:U} to "" (eflags = VARE_WANTRES, vflags = VAR_JUNK)
+Result of ${:U} is "" (eflags = VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP)
+Var_Set("${:U}", "empty name", ...) name expands to empty string - ignored
+Var_Parse: ${:U} with VARE_WANTRES
+Applying ${:U} to "" (eflags = VARE_WANTRES, vflags = VAR_JUNK)
+Result of ${:U} is "" (eflags = VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP)
+Var_Append("${:U}", "empty name", ...) name expands to empty string - ignored
+Global:FROM_CMDLINE = overwritten ignored!
+Global:VAR = 1
+Global:VAR = 1 2
+Global:VAR = 1 2 3
+Var_Parse: ${VAR:M[2]} with VARE_UNDEFERR|VARE_WANTRES
+Applying ${VAR:M...} to "1 2 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none)
+Pattern[VAR] for [1 2 3] is [[2]]
+ModifyWords: split "1 2 3" into 3 words
+VarMatch [1] [[2]]
+VarMatch [2] [[2]]
+VarMatch [3] [[2]]
+Result of ${VAR:M[2]} is "2" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none)
+Var_Parse: ${VAR:N[2]} with VARE_UNDEFERR|VARE_WANTRES
+Applying ${VAR:N...} to "1 2 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none)
+Pattern[VAR] for [1 2 3] is [[2]]
+ModifyWords: split "1 2 3" into 3 words
+Result of ${VAR:N[2]} is "1 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none)
+Var_Parse: ${VAR:S,2,two,} with VARE_UNDEFERR|VARE_WANTRES
+Applying ${VAR:S...} to "1 2 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none)
+Modifier part: "2"
+Modifier part: "two"
+ModifyWords: split "1 2 3" into 3 words
+Result of ${VAR:S,2,two,} is "1 two 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none)
+Var_Parse: ${VAR:Q} with VARE_UNDEFERR|VARE_WANTRES
+Applying ${VAR:Q} to "1 2 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none)
+QuoteMeta: [1\ 2\ 3]
+Result of ${VAR:Q} is "1\ 2\ 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none)
+Var_Parse: ${VAR:tu:tl:Q} with VARE_UNDEFERR|VARE_WANTRES
+Applying ${VAR:t...} to "1 2 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none)
+Result of ${VAR:tu} is "1 2 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none)
+Applying ${VAR:t...} to "1 2 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none)
+Result of ${VAR:tl} is "1 2 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none)
+Applying ${VAR:Q} to "1 2 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none)
+QuoteMeta: [1\ 2\ 3]
+Result of ${VAR:Q} is "1\ 2\ 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none)
+Var_Parse: ${:Uvalue:${:UM*e}:Mvalu[e]} with VARE_UNDEFERR|VARE_WANTRES
+Applying ${:U...} to "" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK)
+Result of ${:Uvalue} is "value" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP)
+Var_Parse: ${:UM*e}:Mvalu[e]} with VARE_UNDEFERR|VARE_WANTRES
+Applying ${:U...} to "" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK)
+Result of ${:UM*e} is "M*e" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP)
+Indirect modifier "M*e" from "${:UM*e}"
+Applying ${:M...} to "value" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP)
+Pattern[] for [value] is [*e]
+ModifyWords: split "value" into 1 words
+VarMatch [value] [*e]
+Result of ${:M*e} is "value" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP)
+Applying ${:M...} to "value" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP)
+Pattern[] for [value] is [valu[e]]
+ModifyWords: split "value" into 1 words
+VarMatch [value] [valu[e]]
+Result of ${:Mvalu[e]} is "value" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP)
+Var_Parse: ${:UVAR} with VARE_WANTRES
+Applying ${:U...} to "" (eflags = VARE_WANTRES, vflags = VAR_JUNK)
+Result of ${:UVAR} is "VAR" (eflags = VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP)
+Global:delete VAR
+Var_Parse: ${:Uvariable:unknown} with VARE_UNDEFERR|VARE_WANTRES
+Applying ${:U...} to "" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK)
+Result of ${:Uvariable} is "variable" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP)
+Applying ${:u...} to "variable" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP)
+make: Unknown modifier 'u'
+Result of ${:unknown} is error (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP)
+make: "vardebug.mk" line 44: Malformed conditional (${:Uvariable:unknown})
+Var_Parse: ${UNDEFINED} with VARE_UNDEFERR|VARE_WANTRES
+make: "vardebug.mk" line 53: Malformed conditional (${UNDEFINED})
+Global:RELEVANT = no
+exit status 1
diff --git a/contrib/bmake/unit-tests/vardebug.mk b/contrib/bmake/unit-tests/vardebug.mk
new file mode 100644
index 000000000000..f61df98d50de
--- /dev/null
+++ b/contrib/bmake/unit-tests/vardebug.mk
@@ -0,0 +1,59 @@
+# $NetBSD: vardebug.mk,v 1.3 2020/08/08 14:28:46 rillig Exp $
+#
+# Demonstrates the debugging output for var.c.
+
+RELEVANT= yes
+
+VAR= added # VarAdd
+VAR= overwritten # Var_Set
+.undef VAR # Var_Delete (found)
+.undef VAR # Var_Delete (not found)
+
+# The variable with the empty name cannot be set at all.
+${:U}= empty name # Var_Set
+${:U}+= empty name # Var_Append
+
+FROM_CMDLINE= overwritten # Var_Set (ignored)
+
+VAR= 1
+VAR+= 2
+VAR+= 3
+
+.if ${VAR:M[2]} # VarMatch
+.endif
+.if ${VAR:N[2]} # VarNoMatch (no debug output)
+.endif
+
+.if ${VAR:S,2,two,} # VarGetPattern
+.endif
+
+.if ${VAR:Q} # VarQuote
+.endif
+
+.if ${VAR:tu:tl:Q} # ApplyModifiers
+.endif
+
+# ApplyModifiers, "Got ..."
+.if ${:Uvalue:${:UM*e}:Mvalu[e]}
+.endif
+
+.undef ${:UVAR} # Var_Delete
+
+# When ApplyModifiers results in an error, this appears in the debug log
+# as "is error", without surrounding quotes.
+.if ${:Uvariable:unknown}
+.endif
+
+# XXX: The error message is "Malformed conditional", which is wrong.
+# The condition is syntactically fine, it just contains an undefined variable.
+#
+# There is a specialized error message for "Undefined variable", but as of
+# 2020-08-08, that is not covered by any unit tests. It might even be
+# unreachable.
+.if ${UNDEFINED}
+.endif
+
+RELEVANT= no
+
+all:
+ @:
diff --git a/contrib/bmake/unit-tests/varfind.exp b/contrib/bmake/unit-tests/varfind.exp
new file mode 100644
index 000000000000..13d8d60226a9
--- /dev/null
+++ b/contrib/bmake/unit-tests/varfind.exp
@@ -0,0 +1,15 @@
+VarFind-aliases.to: long explicit-dependency VarFind-aliases.from
+VarFind-aliases.to: abbr explicit-dependency VarFind-aliases.from
+VarFind-aliases.to: long
+VarFind-aliases.to: abbr
+VarFind-aliases.to: long VarFind-aliases.from
+VarFind-aliases.to: abbr VarFind-aliases.from
+VarFind-aliases.to: long
+VarFind-aliases.to: abbr
+VarFind-aliases.to: long explicit-dependency VarFind-aliases.from
+VarFind-aliases.to: abbr explicit-dependency VarFind-aliases.from
+VarFind-aliases.to: long VarFind-aliases
+VarFind-aliases.to: abbr VarFind-aliases
+VarFind-aliases.to: long VarFind-aliases.to
+VarFind-aliases.to: abbr VarFind-aliases.to
+exit status 0
diff --git a/contrib/bmake/unit-tests/varfind.mk b/contrib/bmake/unit-tests/varfind.mk
new file mode 100644
index 000000000000..6fb7bc630eaf
--- /dev/null
+++ b/contrib/bmake/unit-tests/varfind.mk
@@ -0,0 +1,31 @@
+# $NetBSD: varfind.mk,v 1.1 2020/07/25 21:19:29 rillig Exp $
+#
+# Demonstrates variable name aliases in VarFind.
+
+all: VarFind-aliases.to
+
+.SUFFIXES: .from .to
+
+VarFind-aliases.from:
+ @: do nothing
+
+VarFind-aliases.to: explicit-dependency
+
+explicit-dependency:
+ @: do nothing
+
+.from.to:
+ @echo $@: long ${.ALLSRC:Q}
+ @echo $@: abbr ${>:Q}
+ @echo $@: long ${.ARCHIVE:Q}
+ @echo $@: abbr ${!:Q}
+ @echo $@: long ${.IMPSRC:Q}
+ @echo $@: abbr ${<:Q}
+ @echo $@: long ${.MEMBER:Q}
+ @echo $@: abbr ${%:Q}
+ @echo $@: long ${.OODATE:Q}
+ @echo $@: abbr ${?:Q}
+ @echo $@: long ${.PREFIX:Q}
+ @echo $@: abbr ${*:Q}
+ @echo $@: long ${.TARGET:Q}
+ @echo $@: abbr ${@:Q}
diff --git a/contrib/bmake/unit-tests/varmisc.exp b/contrib/bmake/unit-tests/varmisc.exp
index b9a29141ce6b..e8f88d9ca51f 100644
--- a/contrib/bmake/unit-tests/varmisc.exp
+++ b/contrib/bmake/unit-tests/varmisc.exp
@@ -23,4 +23,52 @@ Version=123.456.789 == 123456789
Literal=3.4.5 == 3004005
We have target specific vars
MAN= make.1
+save-dollars: 0 = $
+save-dollars: 1 = $$
+save-dollars: 2 = $$
+save-dollars: False = $
+save-dollars: True = $$
+save-dollars: false = $
+save-dollars: true = $$
+save-dollars: Yes = $$
+save-dollars: No = $
+save-dollars: yes = $$
+save-dollars: no = $
+save-dollars: On = $$
+save-dollars: Off = $
+save-dollars: ON = $$
+save-dollars: OFF = $
+save-dollars: on = $$
+save-dollars: off = $
+export-appended: env
+export-appended: env
+export-appended: env mk
+parse-dynamic: parse-dynamic parse-dynamic before
+parse-dynamic: parse-dynamic parse-dynamic after
+parse-dynamic: parse-dynamic parse-dynamic after
+varerror-unclosed:begin
+make: Unclosed variable ""
+
+make: Unclosed variable "UNCLOSED"
+
+make: Unclosed variable "UNCLOSED"
+
+make: Unclosed variable "PATTERN"
+make: Unclosed variable specification (expecting '}') for "UNCLOSED" (value "") modifier M
+
+make: Unclosed variable "param"
+make: Unclosed variable "UNCLOSED."
+
+
+make: Unclosed variable "UNCLOSED.1"
+
+make: Unclosed variable "UNCLOSED.2"
+
+make: Unclosed variable "UNCLOSED.3"
+
+make: Unclosed variable "UNCLOSED_ORIG"
+
+varerror-unclosed:end
+target1-flags: we have: one two
+target2-flags: we have: one two three four
exit status 0
diff --git a/contrib/bmake/unit-tests/varmisc.mk b/contrib/bmake/unit-tests/varmisc.mk
index ab591db5c4fd..4c58b2a5dc77 100644
--- a/contrib/bmake/unit-tests/varmisc.mk
+++ b/contrib/bmake/unit-tests/varmisc.mk
@@ -1,9 +1,13 @@
-# $Id: varmisc.mk,v 1.11 2020/07/02 15:43:43 sjg Exp $
+# $Id: varmisc.mk,v 1.19 2020/08/31 16:28:10 sjg Exp $
#
# Miscellaneous variable tests.
all: unmatched_var_paren D_true U_true D_false U_false Q_lhs Q_rhs NQ_none \
strftime cmpv manok
+all: save-dollars
+all: export-appended
+all: parse-dynamic
+all: varerror-unclosed
unmatched_var_paren:
@echo ${foo::=foo-text}
@@ -74,7 +78,7 @@ manok:
@echo MAN=${MAN}
# This is an expanded variant of the above .for loop.
-# Between 2020-08-28 and 2020-07-02 this paragraph generated a wrong
+# Between 2020-06-28 and 2020-07-02 this paragraph generated a wrong
# error message "Variable VARNAME is recursive".
# When evaluating the !empty expression, the ${:U1} was not expanded and
# thus resulted in the seeming definition VARNAME=${VARNAME}, which is
@@ -82,3 +86,142 @@ manok:
VARNAME= ${VARNAME${:U1}}
.if defined(VARNAME${:U2}) && !empty(VARNAME${:U2})
.endif
+
+# begin .MAKE.SAVE_DOLLARS; see Var_Set_with_flags and s2Boolean.
+SD_VALUES= 0 1 2 False True false true Yes No yes no On Off ON OFF on off
+SD_4_DOLLARS= $$$$
+
+.for val in ${SD_VALUES}
+.MAKE.SAVE_DOLLARS:= ${val} # Must be := since a simple = has no effect.
+SD.${val}:= ${SD_4_DOLLARS}
+.endfor
+.MAKE.SAVE_DOLLARS:= yes
+
+save-dollars:
+.for val in ${SD_VALUES}
+ @printf '%s: %-8s = %s\n' $@ ${val} ${SD.${val}:Q}
+.endfor
+
+# Appending to an undefined variable does not add a space in front.
+.undef APPENDED
+APPENDED+= value
+.if ${APPENDED} != "value"
+.error "${APPENDED}"
+.endif
+
+# Appending to an empty variable adds a space between the old value
+# and the additional value.
+APPENDED= # empty
+APPENDED+= value
+.if ${APPENDED} != " value"
+.error "${APPENDED}"
+.endif
+
+# Appending to parameterized variables works as well.
+PARAM= param
+VAR.${PARAM}= 1
+VAR.${PARAM}+= 2
+.if ${VAR.param} != "1 2"
+.error "${VAR.param}"
+.endif
+
+# The variable name can contain arbitrary characters.
+# If the expanded variable name ends in a +, this still does not influence
+# the parser. The assignment operator is still a simple assignment.
+# Therefore, there is no need to add a space between the variable name
+# and the assignment operator.
+PARAM= +
+VAR.${PARAM}= 1
+VAR.${PARAM}+= 2
+.if ${VAR.+} != "1 2"
+.error "${VAR.+}"
+.endif
+.for param in + ! ?
+VAR.${param}= ${param}
+.endfor
+.if ${VAR.+} != "+" || ${VAR.!} != "!" || ${VAR.?} != "?"
+.error "${VAR.+}" "${VAR.!}" "${VAR.?}"
+.endif
+
+# Appending to a variable from the environment creates a copy of that variable
+# in the global context.
+# The appended value is not exported automatically.
+# When a variable is exported, the exported value is taken at the time of the
+# .export directive. Later changes to the variable have no effect.
+.export FROM_ENV_BEFORE
+FROM_ENV+= mk
+FROM_ENV_BEFORE+= mk
+FROM_ENV_AFTER+= mk
+.export FROM_ENV_AFTER
+
+export-appended:
+ @echo $@: "$$FROM_ENV"
+ @echo $@: "$$FROM_ENV_BEFORE"
+ @echo $@: "$$FROM_ENV_AFTER"
+
+# begin parse-dynamic
+#
+# Demonstrate that the target-specific variables are not evaluated in
+# the global context. They are preserved until there is a local context
+# in which resolving them makes sense.
+
+# There are different code paths for short names ...
+${:U>}= before
+GS_TARGET:= $@
+GS_MEMBER:= $%
+GS_PREFIX:= $*
+GS_ARCHIVE:= $!
+GS_ALLSRC:= $>
+${:U>}= after
+# ... and for braced short names ...
+GB_TARGET:= ${@}
+GB_MEMBER:= ${%}
+GB_PREFIX:= ${*}
+GB_ARCHIVE:= ${!}
+GB_ALLSRC:= ${>}
+# ... and for long names.
+GL_TARGET:= ${.TARGET}
+GL_MEMBER:= ${.MEMBER}
+GL_PREFIX:= ${.PREFIX}
+GL_ARCHIVE:= ${.ARCHIVE}
+GL_ALLSRC:= ${.ALLSRC}
+
+parse-dynamic:
+ @echo $@: ${GS_TARGET} ${GS_MEMBER} ${GS_PREFIX} ${GS_ARCHIVE} ${GS_ALLSRC}
+ @echo $@: ${GB_TARGET} ${GB_MEMBER} ${GB_PREFIX} ${GB_ARCHIVE} ${GB_ALLSRC}
+ @echo $@: ${GL_TARGET} ${GL_MEMBER} ${GL_PREFIX} ${GL_ARCHIVE} ${GL_ALLSRC}
+
+# Since 2020-07-28, make complains about unclosed variables.
+# Before that, it had complained about unclosed variables only when
+# parsing the modifiers, but not when parsing the variable name.
+
+UNCLOSED_INDIR_1= ${UNCLOSED_ORIG
+UNCLOSED_INDIR_2= ${UNCLOSED_INDIR_1}
+
+FLAGS= one two
+FLAGS+= ${FLAGS.${.ALLSRC:M*.c:T:u}}
+FLAGS.target2.c = three four
+
+target1.c:
+target2.c:
+
+all: target1-flags target2-flags
+target1-flags: target1.c
+ @echo $@: we have: ${FLAGS}
+
+target2-flags: target2.c
+ @echo $@: we have: ${FLAGS}
+
+varerror-unclosed:
+ @echo $@:begin
+ @echo $(
+ @echo $(UNCLOSED
+ @echo ${UNCLOSED
+ @echo ${UNCLOSED:M${PATTERN
+ @echo ${UNCLOSED.${param
+ @echo $
+.for i in 1 2 3
+ @echo ${UNCLOSED.${i}
+.endfor
+ @echo ${UNCLOSED_INDIR_2}
+ @echo $@:end
diff --git a/contrib/bmake/unit-tests/varmod-assign.exp b/contrib/bmake/unit-tests/varmod-assign.exp
new file mode 100644
index 000000000000..120c775d5f69
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-assign.exp
@@ -0,0 +1,26 @@
+mod-assign: first=1.
+mod-assign: last=3.
+mod-assign: appended=1 2 3.
+1
+2
+3
+mod-assign: ran:3.
+mod-assign: global: 1, 3, 1 2 3, 3.
+mod-assign-nested: then1t1
+mod-assign-nested: else2e2
+mod-assign-nested: then3t3
+mod-assign-nested: else4e4
+make: Bad modifier `:' for
+value}
+make: Bad modifier `:' for
+mod-assign-empty: overwritten}
+mod-assign-empty: VAR=overwritten
+make: Unknown modifier ':'
+
+sysv:y
+make: Unfinished modifier for ASSIGN ('}' missing)
+
+ok=word
+make: " echo word; false " returned non-zero status
+err=previous
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-assign.mk b/contrib/bmake/unit-tests/varmod-assign.mk
new file mode 100644
index 000000000000..82b7d947c484
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-assign.mk
@@ -0,0 +1,81 @@
+# $NetBSD: varmod-assign.mk,v 1.6 2020/08/25 21:16:53 rillig Exp $
+#
+# Tests for the obscure ::= variable modifiers, which perform variable
+# assignments during evaluation, just like the = operator in C.
+
+all: mod-assign
+all: mod-assign-nested
+all: mod-assign-empty
+all: mod-assign-parse
+all: mod-assign-shell-error
+
+mod-assign:
+ # The ::?= modifier applies the ?= assignment operator 3 times.
+ # The ?= operator only has an effect for the first time, therefore
+ # the variable FIRST ends up with the value 1.
+ @echo $@: ${1 2 3:L:@i@${FIRST::?=$i}@} first=${FIRST}.
+
+ # The ::= modifier applies the = assignment operator 3 times.
+ # The = operator overwrites the previous value, therefore the
+ # variable LAST ends up with the value 3.
+ @echo $@: ${1 2 3:L:@i@${LAST::=$i}@} last=${LAST}.
+
+ # The ::+= modifier applies the += assignment operator 3 times.
+ # The += operator appends 3 times to the variable, therefore
+ # the variable APPENDED ends up with the value "1 2 3".
+ @echo $@: ${1 2 3:L:@i@${APPENDED::+=$i}@} appended=${APPENDED}.
+
+ # The ::!= modifier applies the != assignment operator 3 times.
+ # The side effects of the shell commands are visible in the output.
+ # Just as with the ::= modifier, the last value is stored in the
+ # RAN variable.
+ @echo $@: ${echo.1 echo.2 echo.3:L:@i@${RAN::!=${i:C,.*,&; & 1>\&2,:S,., ,g}}@} ran:${RAN}.
+
+ # The assignments happen in the global scope and thus are
+ # preserved even after the shell command has been run.
+ @echo $@: global: ${FIRST:Q}, ${LAST:Q}, ${APPENDED:Q}, ${RAN:Q}.
+
+mod-assign-nested:
+ # The condition "1" is true, therefore THEN1 gets assigned a value,
+ # and IT1 as well. Nothing surprising here.
+ @echo $@: ${1:?${THEN1::=then1${IT1::=t1}}:${ELSE1::=else1${IE1::=e1}}}${THEN1}${ELSE1}${IT1}${IE1}
+
+ # The condition "0" is false, therefore ELSE1 gets assigned a value,
+ # and IE1 as well. Nothing surprising here as well.
+ @echo $@: ${0:?${THEN2::=then2${IT2::=t2}}:${ELSE2::=else2${IE2::=e2}}}${THEN2}${ELSE2}${IT2}${IE2}
+
+ # The same effects happen when the variables are defined elsewhere.
+ @echo $@: ${SINK3:Q}
+ @echo $@: ${SINK4:Q}
+SINK3:= ${1:?${THEN3::=then3${IT3::=t3}}:${ELSE3::=else3${IE3::=e3}}}${THEN3}${ELSE3}${IT3}${IE3}
+SINK4:= ${0:?${THEN4::=then4${IT4::=t4}}:${ELSE4::=else4${IE4::=e4}}}${THEN4}${ELSE4}${IT4}${IE4}
+
+mod-assign-empty:
+ # Assigning to the empty variable would obviously not work since that variable
+ # is write-protected. Therefore it is rejected early as a "bad modifier".
+ @echo ${::=value}
+ @echo $@: ${:Uvalue::=overwritten}
+
+ # The :L modifier sets the variable's value to its name.
+ # Since the name is still "VAR", assigning to that variable works.
+ @echo $@: ${VAR:L::=overwritten} VAR=${VAR}
+
+mod-assign-parse:
+ # The modifier for assignment operators starts with a ':'.
+ # An 'x' after that is an invalid modifier.
+ @echo ${ASSIGN::x} # 'x' is an unknown assignment operator
+
+ # When parsing an assignment operator fails because the operator is
+ # incomplete, make falls back to the SysV modifier.
+ @echo ${SYSV::=sysv\:x}${SYSV::x=:y}
+
+ @echo ${ASSIGN::=value # missing closing brace
+
+mod-assign-shell-error:
+ # If the command succeeds, the variable is assigned.
+ @${SH_OK::!= echo word; true } echo ok=${SH_OK}
+
+ # If the command fails, the variable keeps its previous value.
+ # FIXME: the error message says: "previous" returned non-zero status
+ @${SH_ERR::=previous}
+ @${SH_ERR::!= echo word; false } echo err=${SH_ERR}
diff --git a/contrib/bmake/unit-tests/varmod-defined.exp b/contrib/bmake/unit-tests/varmod-defined.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-defined.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-defined.mk b/contrib/bmake/unit-tests/varmod-defined.mk
new file mode 100644
index 000000000000..fa5bf5c2245c
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-defined.mk
@@ -0,0 +1,28 @@
+# $NetBSD: varmod-defined.mk,v 1.3 2020/08/25 21:58:08 rillig Exp $
+#
+# Tests for the :D variable modifier, which returns the given string
+# if the variable is defined. It is closely related to the :U modifier.
+
+DEF= defined
+.undef UNDEF
+
+# Since DEF is defined, the value of the expression is "value", not
+# "defined".
+#
+.if ${DEF:Dvalue} != "value"
+.error
+.endif
+
+# Since UNDEF is not defined, the "value" is ignored. Instead of leaving the
+# expression undefined, it is set to "", exactly to allow the expression to
+# be used in .if conditions. In this place, other undefined expressions
+# would generate an error message.
+# XXX: Ideally the error message would be "undefined variable", but as of
+# 2020-08-25 it is "Malformed conditional".
+#
+.if ${UNDEF:Dvalue} != ""
+.error
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-edge.exp b/contrib/bmake/unit-tests/varmod-edge.exp
index b3b2e3a92f95..94ba81e2e4f0 100644
--- a/contrib/bmake/unit-tests/varmod-edge.exp
+++ b/contrib/bmake/unit-tests/varmod-edge.exp
@@ -1,17 +1,22 @@
+make: "varmod-edge.mk" line omitted: ok M-paren
+make: "varmod-edge.mk" line omitted: ok M-mixed
+make: "varmod-edge.mk" line omitted: ok M-unescape
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
+make: "varmod-edge.mk" line omitted: ok M-nest-mix
+make: "varmod-edge.mk" line omitted: ok M-nest-brk
+make: "varmod-edge.mk" line omitted: ok M-pat-err
+make: "varmod-edge.mk" line omitted: ok M-bsbs
+make: "varmod-edge.mk" line omitted: ok M-bs1-par
+make: "varmod-edge.mk" line omitted: ok M-bs2-par
+make: "varmod-edge.mk" line omitted: ok M-128
+make: "varmod-edge.mk" line omitted: ok eq-ext
+make: "varmod-edge.mk" line omitted: ok eq-q
+make: "varmod-edge.mk" line omitted: ok eq-bs
+make: Unfinished modifier for INP.eq-esc ('=' missing)
+make: "varmod-edge.mk" line omitted: ok eq-esc
+make: "varmod-edge.mk" line omitted: ok colon
+make: Unknown modifier ':'
+make: Unknown modifier ':'
+make: "varmod-edge.mk" line omitted: ok colons
+ok
exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-edge.mk b/contrib/bmake/unit-tests/varmod-edge.mk
index 561eb6116891..e6f7f91c95c7 100644
--- a/contrib/bmake/unit-tests/varmod-edge.mk
+++ b/contrib/bmake/unit-tests/varmod-edge.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-edge.mk,v 1.7 2020/04/27 14:07:22 christos Exp $
+# $NetBSD: varmod-edge.mk,v 1.12 2020/08/08 13:29:09 rillig Exp $
#
# Tests for edge cases in variable modifiers.
#
@@ -143,20 +143,31 @@ 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.
+# Having only an escaped '=' results in a parse error.
+# The call to "pattern.lhs = ParseModifierPart" 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)
+# make: Unfinished modifier for INP.eq-esc ('=' missing)
+
+TESTS+= colon
+INP.colon= value
+MOD.colon= ${INP.colon:}
+EXP.colon= value
+
+TESTS+= colons
+INP.colons= value
+MOD.colons= ${INP.colons::::}
+EXP.colons= # empty
-all:
.for test in ${TESTS}
. if ${MOD.${test}} == ${EXP.${test}}
- @printf 'ok %s\n' ${test:Q}''
+.info ok ${test}
. else
- @printf 'error in %s: expected %s, got %s\n' \
- ${test:Q}'' ${EXP.${test}:Q}'' ${MOD.${test}:Q}''
+.warning error in ${test}: expected "${EXP.${test}}", got "${MOD.${test}}"
. endif
.endfor
+
+all:
+ @echo ok
diff --git a/contrib/bmake/unit-tests/varmod-exclam-shell.exp b/contrib/bmake/unit-tests/varmod-exclam-shell.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-exclam-shell.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-exclam-shell.mk b/contrib/bmake/unit-tests/varmod-exclam-shell.mk
new file mode 100644
index 000000000000..2e811ddb4f5c
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-exclam-shell.mk
@@ -0,0 +1,28 @@
+# $NetBSD: varmod-exclam-shell.mk,v 1.2 2020/08/16 12:48:55 rillig Exp $
+#
+# Tests for the :!cmd! variable modifier.
+
+.if ${:!echo hello | tr 'l' 'l'!} != "hello"
+.warning unexpected
+.endif
+
+# The output is truncated at the first null byte.
+# Cmd_Exec returns only a string pointer without length information.
+.if ${:!echo hello | tr 'l' '\0'!} != "he"
+.warning unexpected
+.endif
+
+.if ${:!echo!} != ""
+.warning A newline at the end of the output must be stripped.
+.endif
+
+.if ${:!echo;echo!} != " "
+.warning Only a single newline at the end of the output is stripped.
+.endif
+
+.if ${:!echo;echo;echo;echo!} != " "
+.warning Other newlines in the output are converted to spaces.
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-extension.exp b/contrib/bmake/unit-tests/varmod-extension.exp
new file mode 100644
index 000000000000..24f7403c7f3f
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-extension.exp
@@ -0,0 +1,10 @@
+extension of 'a/b/c' is ''
+extension of 'def' is ''
+extension of 'a.b.c' is 'c'
+extension of 'a.b/c' is 'b/c'
+extension of 'a' is ''
+extension of 'a.a' is 'a'
+extension of '.gitignore' is 'gitignore'
+extension of 'a' is ''
+extension of 'a.a' is 'a'
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-extension.mk b/contrib/bmake/unit-tests/varmod-extension.mk
new file mode 100644
index 000000000000..db501f7234c7
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-extension.mk
@@ -0,0 +1,9 @@
+# $NetBSD: varmod-extension.mk,v 1.3 2020/08/23 15:09:15 rillig Exp $
+#
+# Tests for the :E variable modifier, which returns the filename extension
+# of each word in the variable.
+
+all:
+.for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a
+ @echo "extension of '"${path:Q}"' is '"${path:E:Q}"'"
+.endfor
diff --git a/contrib/bmake/unit-tests/varmod-gmtime.exp b/contrib/bmake/unit-tests/varmod-gmtime.exp
new file mode 100644
index 000000000000..373e8de1a271
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-gmtime.exp
@@ -0,0 +1,9 @@
+mod-gmtime:
+%Y
+2020
+%Y
+%Y
+mod-gmtime-indirect:
+make: Unknown modifier '1'
+
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-gmtime.mk b/contrib/bmake/unit-tests/varmod-gmtime.mk
new file mode 100644
index 000000000000..0a05ad58d982
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-gmtime.mk
@@ -0,0 +1,35 @@
+# $NetBSD: varmod-gmtime.mk,v 1.2 2020/08/16 12:48:55 rillig Exp $
+#
+# Tests for the :gmtime variable modifier, which formats a timestamp
+# using strftime(3).
+
+all: mod-gmtime
+all: mod-gmtime-indirect
+
+mod-gmtime:
+ @echo $@:
+ @echo ${%Y:L:gmtim=1593536400} # modifier name too short
+ @echo ${%Y:L:gmtime=1593536400} # 2020-07-01T00:00:00Z
+ @echo ${%Y:L:gmtimer=1593536400} # modifier name too long
+ @echo ${%Y:L:gm=gm:M*}
+
+mod-gmtime-indirect:
+ @echo $@:
+
+ # As of 2020-08-16, it is not possible to pass the seconds via a
+ # variable expression. This is because parsing of the :gmtime
+ # modifier stops at the '$' and returns to ApplyModifiers.
+ #
+ # There, a colon would be skipped but not a dollar.
+ # Parsing therefore continues at the '$' of the ${:U159...}, looking
+ # for an ordinary variable modifier.
+ #
+ # At this point, the ${:U} is expanded and interpreted as a variable
+ # modifier, which results in the error message "Unknown modifier '1'".
+ #
+ # If ApplyModifier_Gmtime were to pass its argument through
+ # ParseModifierPart, this would work.
+ @echo ${%Y:L:gmtime=${:U1593536400}}
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-hash.exp b/contrib/bmake/unit-tests/varmod-hash.exp
new file mode 100644
index 000000000000..f16f30903539
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-hash.exp
@@ -0,0 +1,9 @@
+make: Unknown modifier 'h'
+
+26bb0f5f
+12345
+make: Unknown modifier 'h'
+
+make: Unknown modifier 'h'
+
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-hash.mk b/contrib/bmake/unit-tests/varmod-hash.mk
new file mode 100644
index 000000000000..ef59268cb82c
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-hash.mk
@@ -0,0 +1,10 @@
+# $NetBSD: varmod-hash.mk,v 1.3 2020/08/23 15:13:21 rillig Exp $
+#
+# Tests for the :hash variable modifier.
+
+all:
+ @echo ${12345:L:has} # modifier name too short
+ @echo ${12345:L:hash} # ok
+ @echo ${12345:L:hash=SHA-256} # :hash does not accept '='
+ @echo ${12345:L:hasX} # misspelled
+ @echo ${12345:L:hashed} # modifier name too long
diff --git a/contrib/bmake/unit-tests/varmod-head.exp b/contrib/bmake/unit-tests/varmod-head.exp
new file mode 100644
index 000000000000..f0bf87f03012
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-head.exp
@@ -0,0 +1,10 @@
+head (dirname) of 'a/b/c' is 'a/b'
+head (dirname) of 'def' is '.'
+head (dirname) of 'a.b.c' is '.'
+head (dirname) of 'a.b/c' is 'a.b'
+head (dirname) of 'a' is '.'
+head (dirname) of 'a.a' is '.'
+head (dirname) of '.gitignore' is '.'
+head (dirname) of 'a' is '.'
+head (dirname) of 'a.a' is '.'
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-head.mk b/contrib/bmake/unit-tests/varmod-head.mk
new file mode 100644
index 000000000000..eda4820c5f14
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-head.mk
@@ -0,0 +1,9 @@
+# $NetBSD: varmod-head.mk,v 1.3 2020/08/23 15:09:15 rillig Exp $
+#
+# Tests for the :H variable modifier, which returns the dirname of
+# each of the words in the variable value.
+
+all:
+.for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a
+ @echo "head (dirname) of '"${path:Q}"' is '"${path:H:Q}"'"
+.endfor
diff --git a/contrib/bmake/unit-tests/varmod-ifelse.exp b/contrib/bmake/unit-tests/varmod-ifelse.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-ifelse.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-ifelse.mk b/contrib/bmake/unit-tests/varmod-ifelse.mk
new file mode 100644
index 000000000000..e8e92b35d1c1
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-ifelse.mk
@@ -0,0 +1,9 @@
+# $NetBSD: varmod-ifelse.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the ${cond:?then:else} variable modifier, which evaluates either
+# the then-expression or the else-expression, depending on the condition.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-l-name-to-value.exp b/contrib/bmake/unit-tests/varmod-l-name-to-value.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-l-name-to-value.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-l-name-to-value.mk b/contrib/bmake/unit-tests/varmod-l-name-to-value.mk
new file mode 100644
index 000000000000..b8a5877e1100
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-l-name-to-value.mk
@@ -0,0 +1,31 @@
+# $NetBSD: varmod-l-name-to-value.mk,v 1.3 2020/08/25 22:25:05 rillig Exp $
+#
+# Tests for the :L modifier, which returns the variable name as the new value.
+
+# The empty variable name leads to an empty string.
+.if ${:L} != ""
+.error
+.endif
+
+# The variable name is converted into an expression with the variable name
+# "VARNAME" and the value "VARNAME".
+.if ${VARNAME:L} != "VARNAME"
+.error
+.endif
+
+# The value of the expression can be modified afterwards.
+.if ${VARNAME:L:S,VAR,,} != "NAME"
+.error
+.endif
+
+# The name of the expression is still the same as before. Using the :L
+# modifier, it can be restored.
+#
+# Hmmm, this can be used as a double storage or a backup mechanism.
+# Probably unintended, but maybe useful.
+.if ${VARNAME:L:S,VAR,,:L} != "VARNAME"
+.error
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-localtime.exp b/contrib/bmake/unit-tests/varmod-localtime.exp
new file mode 100644
index 000000000000..69e4335be187
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-localtime.exp
@@ -0,0 +1,4 @@
+%Y
+2020
+%Y
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-localtime.mk b/contrib/bmake/unit-tests/varmod-localtime.mk
new file mode 100644
index 000000000000..fa4fd4f9cfb1
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-localtime.mk
@@ -0,0 +1,9 @@
+# $NetBSD: varmod-localtime.mk,v 1.3 2020/08/23 15:13:21 rillig Exp $
+#
+# Tests for the :localtime variable modifier, which returns the given time,
+# formatted as a local timestamp.
+
+all:
+ @echo ${%Y:L:localtim=1593536400} # modifier name too short
+ @echo ${%Y:L:localtime=1593536400} # 2020-07-01T00:00:00Z
+ @echo ${%Y:L:localtimer=1593536400} # modifier name too long
diff --git a/contrib/bmake/unit-tests/varmod-loop.exp b/contrib/bmake/unit-tests/varmod-loop.exp
new file mode 100644
index 000000000000..ce93eb5bc0c8
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-loop.exp
@@ -0,0 +1,16 @@
+:+one+ +two+ +three+:
+:x1y x2y x3y:
+:x1y x2y x3y:
+:mod-loop-varname: :x1y x2y x3y: ::
+:x1y x2y x3y:
+mod-loop-resolve:w1d2d3w w2i3w w1i2d3 2i${RES3}w w1d2d3 2i${RES3} 1i${RES2}w:
+mod-loop-varname-dollar:(1) (2) (3).
+mod-loop-varname-dollar:() () ().
+mod-loop-varname-dollar:() () ().
+mod-loop-dollar:1:
+mod-loop-dollar:${word}$:
+mod-loop-dollar:$3$:
+mod-loop-dollar:$${word}$$:
+mod-loop-dollar:$$5$$:
+mod-loop-dollar:$$${word}$$$:
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-loop.mk b/contrib/bmake/unit-tests/varmod-loop.mk
new file mode 100644
index 000000000000..561f4b95baa0
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-loop.mk
@@ -0,0 +1,63 @@
+# $NetBSD: varmod-loop.mk,v 1.2 2020/08/16 12:30:45 rillig Exp $
+#
+# Tests for the :@var@...${var}...@ variable modifier.
+
+all: mod-loop-varname
+all: mod-loop-resolve
+all: mod-loop-varname-dollar
+all: mod-loop-dollar
+
+# In the :@ modifier, the name of the loop variable can even be generated
+# dynamically. There's no practical use-case for this, and hopefully nobody
+# will ever depend on this, but technically it's possible.
+# Therefore, in -dL mode, this is forbidden, see lint.mk.
+mod-loop-varname:
+ @echo :${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@:Q}:
+ # ":::" is a very creative variable name, unlikely in practice
+ # The expression ${\:\:\:} would not work since backslashes can only
+ # be escaped in the modifiers, but not in the variable name.
+ @echo :${:U1 2 3:@:::@x${${:U\:\:\:}}y@}:
+ # "@@" is another creative variable name.
+ @echo :${:U1 2 3:@\@\@@x${@@}y@}:
+ # Even "@" works as a variable name since the variable is installed
+ # in the "current" scope, which in this case is the one from the
+ # target.
+ @echo :$@: :${:U1 2 3:@\@@x${@}y@}: :$@:
+ # In extreme cases, even the backslash can be used as variable name.
+ # It needs to be doubled though.
+ @echo :${:U1 2 3:@\\@x${${:Ux:S,x,\\,}}y@}:
+
+# The :@ modifier resolves the variables a little more often than expected.
+# In particular, it resolves _all_ variables from the context, and not only
+# the loop variable (in this case v).
+#
+# The d means direct reference, the i means indirect reference.
+RESOLVE= ${RES1} $${RES1}
+RES1= 1d${RES2} 1i$${RES2}
+RES2= 2d${RES3} 2i$${RES3}
+RES3= 3
+
+mod-loop-resolve:
+ @echo $@:${RESOLVE:@v@w${v}w@:Q}:
+
+# Until 2020-07-20, the variable name of the :@ modifier could end with one
+# or two dollar signs, which were silently ignored.
+# There's no point in allowing a dollar sign in that position.
+mod-loop-varname-dollar:
+ @echo $@:${1 2 3:L:@v$@($v)@:Q}.
+ @echo $@:${1 2 3:L:@v$$@($v)@:Q}.
+ @echo $@:${1 2 3:L:@v$$$@($v)@:Q}.
+
+# Demonstrate that it is possible to generate dollar characters using the
+# :@ modifier.
+#
+# These are edge cases that could have resulted in a parse error as well
+# since the $@ at the end could have been interpreted as a variable, which
+# would mean a missing closing @ delimiter.
+mod-loop-dollar:
+ @echo $@:${:U1:@word@${word}$@:Q}:
+ @echo $@:${:U2:@word@$${word}$$@:Q}:
+ @echo $@:${:U3:@word@$$${word}$$$@:Q}:
+ @echo $@:${:U4:@word@$$$${word}$$$$@:Q}:
+ @echo $@:${:U5:@word@$$$$${word}$$$$$@:Q}:
+ @echo $@:${:U6:@word@$$$$$${word}$$$$$$@:Q}:
diff --git a/contrib/bmake/unit-tests/varmod-match-escape.exp b/contrib/bmake/unit-tests/varmod-match-escape.exp
new file mode 100755
index 000000000000..1e4c030c5b42
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-match-escape.exp
@@ -0,0 +1,3 @@
+Pattern[SPECIALS] for [\: : \\ * \*] is [\:]
+Pattern[SPECIALS] for [\: : \\ * \*] is [:]
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-match-escape.mk b/contrib/bmake/unit-tests/varmod-match-escape.mk
new file mode 100755
index 000000000000..7913bb476ae7
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-match-escape.mk
@@ -0,0 +1,20 @@
+# $NetBSD: varmod-match-escape.mk,v 1.1 2020/08/16 20:03:53 rillig Exp $
+#
+# As of 2020-08-01, the :M and :N modifiers interpret backslashes differently,
+# depending on whether there was a variable expression somewhere before the
+# first backslash or not. See ApplyModifier_Match, "copy = TRUE".
+#
+# Apart from the different and possibly confusing debug output, there is no
+# difference in behavior. When parsing the modifier text, only \{, \} and \:
+# are unescaped, and in the pattern matching these have the same meaning as
+# their plain variants '{', '}' and ':'. In the pattern matching from
+# Str_Match, only \*, \? or \[ would make a noticeable difference.
+SPECIALS= \: : \\ * \*
+RELEVANT= yes
+.if ${SPECIALS:M${:U}\:} != ${SPECIALS:M\:${:U}}
+.warning unexpected
+.endif
+RELEVANT= no
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-match.exp b/contrib/bmake/unit-tests/varmod-match.exp
new file mode 100644
index 000000000000..11777e086d4f
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-match.exp
@@ -0,0 +1,5 @@
+match-char-class:
+ uppercase numbers: One Two Three Four
+ all the others: five six seven
+ starts with non-s, ends with [ex]: One Three five
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-match.mk b/contrib/bmake/unit-tests/varmod-match.mk
new file mode 100644
index 000000000000..805426a0fda9
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-match.mk
@@ -0,0 +1,22 @@
+# $NetBSD: varmod-match.mk,v 1.3 2020/08/16 20:03:53 rillig Exp $
+#
+# Tests for the :M variable modifier, which filters words that match the
+# given pattern.
+
+all: match-char-class
+all: slow
+
+
+NUMBERS= One Two Three Four five six seven
+
+match-char-class:
+ @echo '$@:'
+ @echo ' uppercase numbers: ${NUMBERS:M[A-Z]*}'
+ @echo ' all the others: ${NUMBERS:M[^A-Z]*}'
+ @echo ' starts with non-s, ends with [ex]: ${NUMBERS:M[^s]*[ex]}'
+
+
+# Before 2020-06-13, this expression took quite a long time in Str_Match,
+# calling itself 601080390 times for 16 asterisks.
+slow:
+ @: ${:U****************:M****************b}
diff --git a/contrib/bmake/unit-tests/varmod-no-match.exp b/contrib/bmake/unit-tests/varmod-no-match.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-no-match.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-no-match.mk b/contrib/bmake/unit-tests/varmod-no-match.mk
new file mode 100644
index 000000000000..2acb27e2e727
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-no-match.mk
@@ -0,0 +1,9 @@
+# $NetBSD: varmod-no-match.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the :N variable modifier, which filters words that do not match
+# the given pattern.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-order-reverse.exp b/contrib/bmake/unit-tests/varmod-order-reverse.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-order-reverse.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-order-reverse.mk b/contrib/bmake/unit-tests/varmod-order-reverse.mk
new file mode 100644
index 000000000000..fa9d179560c8
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-order-reverse.mk
@@ -0,0 +1,13 @@
+# $NetBSD: varmod-order-reverse.mk,v 1.3 2020/08/16 20:13:10 rillig Exp $
+#
+# Tests for the :Or variable modifier, which returns the words, sorted in
+# descending order.
+
+NUMBERS= one two three four five six seven eight nine ten
+
+.if ${NUMBERS:Or} != "two three ten six seven one nine four five eight"
+.error ${NUMBERS:Or}
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-order-shuffle.exp b/contrib/bmake/unit-tests/varmod-order-shuffle.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-order-shuffle.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-order-shuffle.mk b/contrib/bmake/unit-tests/varmod-order-shuffle.mk
new file mode 100644
index 000000000000..b6eb6be4b1bc
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-order-shuffle.mk
@@ -0,0 +1,39 @@
+# $NetBSD: varmod-order-shuffle.mk,v 1.3 2020/08/16 20:43:01 rillig Exp $
+#
+# Tests for the :Ox variable modifier, which returns the words of the
+# variable, shuffled.
+#
+# As of 2020-08-16, make uses random(3) seeded by the current time in seconds.
+# This makes the random numbers completely predictable since there is no other
+# part of make that uses random numbers.
+
+NUMBERS= one two three four five six seven eight nine ten
+
+# 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 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.
+
+# Create two shuffles using the := assignment operator.
+shuffled1:= ${NUMBERS:Ox}
+shuffled2:= ${NUMBERS:Ox}
+.if ${shuffled1} == ${shuffled2}
+.error ${shuffled1} == ${shuffled2}
+.endif
+
+# Sorting the list before shuffling it has no effect.
+shuffled1:= ${NUMBERS:O:Ox}
+shuffled2:= ${NUMBERS:O:Ox}
+.if ${shuffled1} == ${shuffled2}
+.error ${shuffled1} == ${shuffled2}
+.endif
+
+# Sorting after shuffling must produce the original numbers.
+sorted:= ${NUMBERS:Ox:O}
+.if ${sorted} != ${NUMBERS:O}
+.error ${sorted} != ${NUMBERS:O}
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-order.exp b/contrib/bmake/unit-tests/varmod-order.exp
new file mode 100644
index 000000000000..99d1d6ef164c
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-order.exp
@@ -0,0 +1,7 @@
+make: Bad modifier `:OX' for NUMBERS
+make: "varmod-order.mk" line 13: Undefined variable "${NUMBERS:OX"
+make: Bad modifier `:OxXX' for NUMBERS
+make: "varmod-order.mk" line 16: Undefined variable "${NUMBERS:Ox"
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/varmod-order.mk b/contrib/bmake/unit-tests/varmod-order.mk
new file mode 100644
index 000000000000..b079bdc34217
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-order.mk
@@ -0,0 +1,19 @@
+# $NetBSD: varmod-order.mk,v 1.4 2020/08/16 20:43:01 rillig Exp $
+#
+# Tests for the :O variable modifier, which returns the words, sorted in
+# ascending order.
+
+NUMBERS= one two three four five six seven eight nine ten
+
+.if ${NUMBERS:O} != "eight five four nine one seven six ten three two"
+.error ${NUMBERS:O}
+.endif
+
+# Unknown modifier "OX"
+_:= ${NUMBERS:OX}
+
+# Unknown modifier "OxXX"
+_:= ${NUMBERS:OxXX}
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-path.exp b/contrib/bmake/unit-tests/varmod-path.exp
new file mode 100644
index 000000000000..ae7f6cc85748
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-path.exp
@@ -0,0 +1,4 @@
+varmod-path.subdir/varmod-path.phony
+varmod-path.subdir/varmod-path.real
+varmod-path.enoent
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-path.mk b/contrib/bmake/unit-tests/varmod-path.mk
new file mode 100644
index 000000000000..ebbf755ddbec
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-path.mk
@@ -0,0 +1,35 @@
+# $NetBSD: varmod-path.mk,v 1.3 2020/08/23 08:10:49 rillig Exp $
+#
+# Tests for the :P variable modifier, which looks up the path for a given
+# target.
+#
+# The phony target does not have a corresponding path, therefore ... oops,
+# as of 2020-08-23 it is nevertheless resolved to a path. This is probably
+# unintended.
+#
+# The real target is located in a subdirectory, and its full path is returned.
+# If it had been in the current directory, the difference between its path and
+# its name would not be visible.
+#
+# The enoent target does not exist, therefore the target name is returned.
+
+.MAIN: all
+
+_!= rm -rf varmod-path.subdir
+_!= mkdir varmod-path.subdir
+_!= > varmod-path.subdir/varmod-path.phony
+_!= > varmod-path.subdir/varmod-path.real
+
+# To have an effect, this .PATH declaration must be after the directory is created.
+.PATH: varmod-path.subdir
+
+varmod-path.phony: .PHONY
+varmod-path.real:
+
+all: varmod-path.phony varmod-path.real
+ @echo ${varmod-path.phony:P}
+ @echo ${varmod-path.real:P}
+ @echo ${varmod-path.enoent:P}
+
+.END:
+ @rm -rf varmod-path.subdir
diff --git a/contrib/bmake/unit-tests/varmod-quote-dollar.exp b/contrib/bmake/unit-tests/varmod-quote-dollar.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-quote-dollar.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-quote-dollar.mk b/contrib/bmake/unit-tests/varmod-quote-dollar.mk
new file mode 100644
index 000000000000..fedbe8a10f4b
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-quote-dollar.mk
@@ -0,0 +1,10 @@
+# $NetBSD: varmod-quote-dollar.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the :q variable modifier, which quotes the string for the shell
+# and doubles dollar signs, to prevent them from being interpreted by a
+# child process of make.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-quote.exp b/contrib/bmake/unit-tests/varmod-quote.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-quote.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-quote.mk b/contrib/bmake/unit-tests/varmod-quote.mk
new file mode 100644
index 000000000000..adf736048e76
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-quote.mk
@@ -0,0 +1,9 @@
+# $NetBSD: varmod-quote.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the :Q variable modifier, which quotes the variable value
+# to be used in a shell program.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-range.exp b/contrib/bmake/unit-tests/varmod-range.exp
new file mode 100644
index 000000000000..bdbd68edeb45
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-range.exp
@@ -0,0 +1,8 @@
+make: Unknown modifier 'r'
+
+1 2 3
+make: Unknown modifier 'r'
+
+make: Unknown modifier 'r'
+
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-range.mk b/contrib/bmake/unit-tests/varmod-range.mk
new file mode 100644
index 000000000000..9fd79cc97a81
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-range.mk
@@ -0,0 +1,10 @@
+# $NetBSD: varmod-range.mk,v 1.3 2020/08/23 15:13:21 rillig Exp $
+#
+# Tests for the :range variable modifier, which generates sequences
+# of integers from the given range.
+
+all:
+ @echo ${a b c:L:rang} # modifier name too short
+ @echo ${a b c:L:range} # ok
+ @echo ${a b c:L:rango} # misspelled
+ @echo ${a b c:L:ranger} # modifier name too long
diff --git a/contrib/bmake/unit-tests/varmod-remember.exp b/contrib/bmake/unit-tests/varmod-remember.exp
new file mode 100644
index 000000000000..448f817d8969
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-remember.exp
@@ -0,0 +1,3 @@
+1 2 3 1 2 3 1 2 3
+1 2 3, SAVED=3
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-remember.mk b/contrib/bmake/unit-tests/varmod-remember.mk
new file mode 100644
index 000000000000..68eb96a122c4
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-remember.mk
@@ -0,0 +1,12 @@
+# $NetBSD: varmod-remember.mk,v 1.3 2020/08/23 15:18:43 rillig Exp $
+#
+# Tests for the :_ modifier, which saves the current variable value
+# in the _ variable or another, to be used later again.
+
+# In the parameterized form, having the variable name on the right side of
+# the = assignment operator is confusing. In almost all other situations
+# the variable name is on the left-hand side of the = operator. Luckily
+# this modifier is only rarely needed.
+all:
+ @echo ${1 2 3:L:_:@var@${_}@}
+ @echo ${1 2 3:L:@var@${var:_=SAVED:}@}, SAVED=${SAVED}
diff --git a/contrib/bmake/unit-tests/varmod-root.exp b/contrib/bmake/unit-tests/varmod-root.exp
new file mode 100644
index 000000000000..24ecbb875f77
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-root.exp
@@ -0,0 +1,10 @@
+root of 'a/b/c' is 'a/b/c'
+root of 'def' is 'def'
+root of 'a.b.c' is 'a.b'
+root of 'a.b/c' is 'a'
+root of 'a' is 'a'
+root of 'a.a' is 'a'
+root of '.gitignore' is ''
+root of 'a' is 'a'
+root of 'a.a' is 'a'
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-root.mk b/contrib/bmake/unit-tests/varmod-root.mk
new file mode 100644
index 000000000000..88af42d82510
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-root.mk
@@ -0,0 +1,9 @@
+# $NetBSD: varmod-root.mk,v 1.3 2020/08/23 15:09:15 rillig Exp $
+#
+# Tests for the :R variable modifier, which returns the filename root
+# without the extension.
+
+all:
+.for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a
+ @echo "root of '"${path:Q}"' is '"${path:R:Q}"'"
+.endfor
diff --git a/contrib/bmake/unit-tests/varmod-select-words.exp b/contrib/bmake/unit-tests/varmod-select-words.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-select-words.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-select-words.mk b/contrib/bmake/unit-tests/varmod-select-words.mk
new file mode 100644
index 000000000000..a9df25f9ff32
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-select-words.mk
@@ -0,0 +1,9 @@
+# $NetBSD: varmod-select-words.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the :[...] variable modifier, which selects a single word
+# or a range of words from a variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-shell.exp b/contrib/bmake/unit-tests/varmod-shell.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-shell.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-shell.mk b/contrib/bmake/unit-tests/varmod-shell.mk
new file mode 100644
index 000000000000..052968004f1b
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-shell.mk
@@ -0,0 +1,9 @@
+# $NetBSD: varmod-shell.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the :sh variable modifier, which runs the shell command
+# given by the variable value and returns its output.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-subst-regex.exp b/contrib/bmake/unit-tests/varmod-subst-regex.exp
new file mode 100644
index 000000000000..eb9ae7f41fb9
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-subst-regex.exp
@@ -0,0 +1,23 @@
+make: Regex compilation error: (details omitted)
+mod-regex-compile-error: C,word,____,:Q}.
+make: No subexpression \1
+make: No subexpression \1
+make: No subexpression \1
+make: No subexpression \1
+mod-regex-limits:11-missing:1 6
+mod-regex-limits:11-ok:1 22 446
+make: No subexpression \2
+make: No subexpression \2
+make: No subexpression \2
+make: No subexpression \2
+mod-regex-limits:22-missing:1 6
+make: No subexpression \2
+make: No subexpression \2
+make: No subexpression \2
+make: No subexpression \2
+mod-regex-limits:22-missing:1 6
+mod-regex-limits:22-ok:1 33 556
+mod-regex-limits:capture:ihgfedcbaabcdefghijABCDEFGHIJa0a1a2rest
+make: Regex compilation error: (details omitted)
+mod-regex-errors:
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-subst-regex.mk b/contrib/bmake/unit-tests/varmod-subst-regex.mk
new file mode 100644
index 000000000000..fe2758e401ec
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-subst-regex.mk
@@ -0,0 +1,87 @@
+# $NetBSD: varmod-subst-regex.mk,v 1.3 2020/08/28 17:15:04 rillig Exp $
+#
+# Tests for the :C,from,to, variable modifier.
+
+all: mod-regex-compile-error
+all: mod-regex-limits
+all: mod-regex-errors
+
+# The variable expression expands to 4 words. Of these words, none matches
+# the regular expression "a b" since these words don't contain any
+# whitespace.
+.if ${:Ua b b c:C,a b,,} != "a b b c"
+.error
+.endif
+
+# Using the '1' modifier does not change anything. The '1' modifier just
+# means to apply at most 1 replacement in the whole variable expression.
+.if ${:Ua b b c:C,a b,,1} != "a b b c"
+.error
+.endif
+
+# The 'W' modifier treats the whole variable value as a single big word,
+# containing whitespace. This big word matches the regular expression,
+# therefore it gets replaced. Whitespace is preserved after replacing.
+.if ${:Ua b b c:C,a b,,W} != " b c"
+.error
+.endif
+
+# The 'g' modifier does not have any effect here since each of the words
+# contains the character 'b' a single time.
+.if ${:Ua b b c:C,b,,g} != "a c"
+.error
+.endif
+
+# The first :C modifier has the 'W' modifier, which makes the whole
+# expression a single word. The 'g' modifier then replaces all occurrences
+# of "1 2" with "___". The 'W' modifier only applies to this single :C
+# modifier. This is demonstrated by the :C modifier that follows. If the
+# 'W' modifier would be preserved, only a single underscore would have been
+# replaced with an 'x'.
+.if ${:U1 2 3 1 2 3:C,1 2,___,Wg:C,_,x,} != "x__ 3 x__ 3"
+.error
+.endif
+
+# The regular expression does not match in the first word.
+# It matches once in the second word, and the \0\0 doubles that word.
+# In the third word, the regular expression matches as early as possible,
+# and since the matches must not overlap, the next possible match would
+# start at the 6, but at that point, there is only one character left,
+# and that cannot match the regular expression "..". Therefore only the
+# "45" is doubled in the result.
+.if ${:U1 23 456:C,..,\0\0,} != "1 2323 45456"
+.error
+.endif
+
+# The modifier '1' applies the replacement at most once, across the whole
+# variable value, no matter whether it is a single big word or many small
+# words.
+#
+# Up to 2020-08-28, the manual page said that the modifiers '1' and 'g'
+# were orthogonal, which was wrong.
+.if ${:U12345 12345:C,.,\0\0,1} != "112345 12345"
+.error
+.endif
+
+# Multiple asterisks form an invalid regular expression. This produces an
+# error message and (as of 2020-08-28) stops parsing in the middle of the
+# variable expression. The unparsed part of the expression is then copied
+# verbatim to the output, which is unexpected and can lead to strange shell
+# commands being run.
+mod-regex-compile-error:
+ @echo $@: ${:Uword1 word2:C,****,____,g:C,word,____,:Q}.
+
+# These tests generate error messages but as of 2020-08-28 just continue
+# parsing and execution as if nothing bad had happened.
+mod-regex-limits:
+ @echo $@:11-missing:${:U1 23 456:C,..,\1\1,:Q}
+ @echo $@:11-ok:${:U1 23 456:C,(.).,\1\1,:Q}
+ @echo $@:22-missing:${:U1 23 456:C,..,\2\2,:Q}
+ @echo $@:22-missing:${:U1 23 456:C,(.).,\2\2,:Q}
+ @echo $@:22-ok:${:U1 23 456:C,(.)(.),\2\2,:Q}
+ # The :C modifier only handles single-digit capturing groups,
+ # which is more than enough for daily use.
+ @echo $@:capture:${:UabcdefghijABCDEFGHIJrest:C,(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.),\9\8\7\6\5\4\3\2\1\0\10\11\12,}
+
+mod-regex-errors:
+ @echo $@: ${UNDEF:Uvalue:C,[,,}
diff --git a/contrib/bmake/unit-tests/varmod-subst.exp b/contrib/bmake/unit-tests/varmod-subst.exp
new file mode 100644
index 000000000000..e752fb8058a8
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-subst.exp
@@ -0,0 +1,51 @@
+mod-subst:
+:a b b c:
+:a b b c:
+: b c:
+:a c:
+:x__ 3 x__ 3:
+12345
+mod-subst-delimiter:
+1 two 3 horizontal tabulator
+1 two 3 space
+1 two 3 exclamation mark
+1 two 3 double quotes
+1 two 3 hash
+1 two 3 dollar
+1 two 3 percent
+1 two 3 apostrophe
+1 two 3 opening parenthesis
+1 two 3 closing parenthesis
+1 two 3 digit
+1 two 3 colon
+1 two 3 less than sign
+1 two 3 equal sign
+1 two 3 greater than sign
+1 two 3 question mark
+1 two 3 at
+1 two 3 letter
+1 two 3 opening bracket
+1 two 3 backslash
+1 two 3 closing bracket
+1 two 3 caret
+1 two 3 opening brace
+1 two 3 vertical line
+1 two 3 closing brace
+1 two 3 tilde
+mod-subst-chain:
+A B c.
+make: Unknown modifier 'i'
+.
+mod-subst-dollar:$1:
+mod-subst-dollar:$2:
+mod-subst-dollar:$3:
+mod-subst-dollar:$4:
+mod-subst-dollar:$5:
+mod-subst-dollar:$6:
+mod-subst-dollar:$7:
+mod-subst-dollar:$8:
+mod-subst-dollar:$40:
+mod-subst-dollar:U8:
+mod-subst-dollar:$$$$:
+mod-subst-dollar:$$$good3
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-subst.mk b/contrib/bmake/unit-tests/varmod-subst.mk
new file mode 100644
index 000000000000..de8d54df8506
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-subst.mk
@@ -0,0 +1,153 @@
+# $NetBSD: varmod-subst.mk,v 1.3 2020/08/19 06:10:06 rillig Exp $
+#
+# Tests for the :S,from,to, variable modifier.
+
+all: mod-subst
+all: mod-subst-delimiter
+all: mod-subst-chain
+all: mod-subst-dollar
+
+WORDS= sequences of letters
+.if ${WORDS:S,,,} != ${WORDS}
+.warning The empty pattern matches something.
+.endif
+.if ${WORDS:S,e,*,1} != "s*quences of letters"
+.warning The :S modifier flag '1' is not applied exactly once.
+.endif
+.if ${WORDS:S,f,*,1} != "sequences o* letters"
+.warning The :S modifier flag '1' is only applied to the first word,\
+ not to the first occurrence.
+.endif
+.if ${WORDS:S,e,*,} != "s*quences of l*tters"
+.warning The :S modifier does not replace every first match per word.
+.endif
+.if ${WORDS:S,e,*,g} != "s*qu*nc*s of l*tt*rs"
+.warning The :S modifier flag 'g' does not replace every occurrence.
+.endif
+.if ${WORDS:S,^sequ,occurr,} != "occurrences of letters"
+.warning The :S modifier fails for a short match anchored at the start.
+.endif
+.if ${WORDS:S,^of,with,} != "sequences with letters"
+.warning The :S modifier fails for an exact match anchored at the start.
+.endif
+.if ${WORDS:S,^office,does not match,} != ${WORDS}
+.warning The :S modifier matches a too long pattern anchored at the start.
+.endif
+.if ${WORDS:S,f$,r,} != "sequences or letters"
+.warning The :S modifier fails for a short match anchored at the end.
+.endif
+.if ${WORDS:S,s$,,} != "sequence of letter"
+.warning The :S modifier fails to replace one occurrence per word.
+.endif
+.if ${WORDS:S,of$,,} != "sequences letters"
+.warning The :S modifier fails for an exact match anchored at the end.
+.endif
+.if ${WORDS:S,eof$,,} != ${WORDS}
+.warning The :S modifier matches a too long pattern anchored at the end.
+.endif
+.if ${WORDS:S,^of$,,} != "sequences letters"
+.warning The :S modifier does not match a word anchored at both ends.
+.endif
+.if ${WORDS:S,^o$,,} != ${WORDS}
+.warning The :S modifier matches a prefix anchored at both ends.
+.endif
+.if ${WORDS:S,^f$,,} != ${WORDS}
+.warning The :S modifier matches a suffix anchored at both ends.
+.endif
+.if ${WORDS:S,^eof$,,} != ${WORDS}
+.warning The :S modifier matches a too long prefix anchored at both ends.
+.endif
+.if ${WORDS:S,^office$,,} != ${WORDS}
+.warning The :S modifier matches a too long suffix anchored at both ends.
+.endif
+
+mod-subst:
+ @echo $@:
+ @echo :${:Ua b b c:S,a b,,:Q}:
+ @echo :${:Ua b b c:S,a b,,1:Q}:
+ @echo :${:Ua b b c:S,a b,,W:Q}:
+ @echo :${:Ua b b c:S,b,,g:Q}:
+ @echo :${:U1 2 3 1 2 3:S,1 2,___,Wg:S,_,x,:Q}:
+ @echo ${:U12345:S,,sep,g:Q}
+
+# The :S and :C modifiers accept an arbitrary character as the delimiter,
+# including characters that are otherwise used as escape characters or
+# interpreted in a special way. This can be used to confuse humans.
+mod-subst-delimiter:
+ @echo $@:
+ @echo ${:U1 2 3:S 2 two :Q} horizontal tabulator
+ @echo ${:U1 2 3:S 2 two :Q} space
+ @echo ${:U1 2 3:S!2!two!:Q} exclamation mark
+ @echo ${:U1 2 3:S"2"two":Q} double quotes
+ # In shell command lines, the hash does not need to be escaped.
+ # It needs to be escaped in variable assignment lines though.
+ @echo ${:U1 2 3:S#2#two#:Q} hash
+ @echo ${:U1 2 3:S$2$two$:Q} dollar
+ @echo ${:U1 2 3:S%2%two%:Q} percent
+ @echo ${:U1 2 3:S'2'two':Q} apostrophe
+ @echo ${:U1 2 3:S(2(two(:Q} opening parenthesis
+ @echo ${:U1 2 3:S)2)two):Q} closing parenthesis
+ @echo ${:U1 2 3:S121two1:Q} digit
+ @echo ${:U1 2 3:S:2:two::Q} colon
+ @echo ${:U1 2 3:S<2<two<:Q} less than sign
+ @echo ${:U1 2 3:S=2=two=:Q} equal sign
+ @echo ${:U1 2 3:S>2>two>:Q} greater than sign
+ @echo ${:U1 2 3:S?2?two?:Q} question mark
+ @echo ${:U1 2 3:S@2@two@:Q} at
+ @echo ${:U1 2 3:Sa2atwoa:Q} letter
+ @echo ${:U1 2 3:S[2[two[:Q} opening bracket
+ @echo ${:U1 2 3:S\2\two\:Q} backslash
+ @echo ${:U1 2 3:S]2]two]:Q} closing bracket
+ @echo ${:U1 2 3:S^2^two^:Q} caret
+ @echo ${:U1 2 3:S{2{two{:Q} opening brace
+ @echo ${:U1 2 3:S|2|two|:Q} vertical line
+ @echo ${:U1 2 3:S}2}two}:Q} closing brace
+ @echo ${:U1 2 3:S~2~two~:Q} tilde
+
+# The :S and :C modifiers can be chained without a separating ':'.
+# This is not documented in the manual page.
+# It works because ApplyModifier_Subst scans for the known modifiers g1W
+# and then just returns to ApplyModifiers. There, the colon is optionally
+# skipped (see the *st.next == ':' at the end of the loop).
+#
+# Most other modifiers cannot be chained since their parsers skip until
+# the next ':' or '}' or ')'.
+mod-subst-chain:
+ @echo $@:
+ @echo ${:Ua b c:S,a,A,S,b,B,}.
+ # There is no 'i' modifier for the :S or :C modifiers.
+ # The error message is "make: Unknown modifier 'i'", which is
+ # kind of correct, although it is mixing the terms for variable
+ # modifiers with the matching modifiers.
+ @echo ${:Uvalue:S,a,x,i}.
+
+# No matter how many dollar characters there are, they all get merged
+# into a single dollar by the :S modifier.
+#
+# As of 2020-08-09, this is because ParseModifierPart sees a '$' and
+# calls Var_Parse to expand the variable. In all other places, the "$$"
+# is handled outside of Var_Parse. Var_Parse therefore considers "$$"
+# one of the "really stupid names", skips the first dollar, and parsing
+# continues with the next character. This repeats for the other dollar
+# signs, except the one before the delimiter. That one is handled by
+# the code that optionally interprets the '$' as the end-anchor in the
+# first part of the :S modifier. That code doesn't call Var_Parse but
+# simply copies the dollar to the result.
+mod-subst-dollar:
+ @echo $@:${:U1:S,^,$,:Q}:
+ @echo $@:${:U2:S,^,$$,:Q}:
+ @echo $@:${:U3:S,^,$$$,:Q}:
+ @echo $@:${:U4:S,^,$$$$,:Q}:
+ @echo $@:${:U5:S,^,$$$$$,:Q}:
+ @echo $@:${:U6:S,^,$$$$$$,:Q}:
+ @echo $@:${:U7:S,^,$$$$$$$,:Q}:
+ @echo $@:${:U8:S,^,$$$$$$$$,:Q}:
+ @echo $@:${:U40:S,^,$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$,:Q}:
+# This generates no dollar at all:
+ @echo $@:${:UU8:S,^,${:U$$$$$$$$},:Q}:
+# Here is an alternative way to generate dollar characters.
+# It's unexpectedly complicated though.
+ @echo $@:${:U:range=5:ts\x24:C,[0-9],,g:Q}:
+# In modifiers, dollars are escaped using the backslash, not using another
+# dollar sign. Therefore, creating a dollar sign is pretty simple:
+ @echo $@:${:Ugood3:S,^,\$\$\$,:Q}
diff --git a/contrib/bmake/unit-tests/varmod-sysv.exp b/contrib/bmake/unit-tests/varmod-sysv.exp
new file mode 100644
index 000000000000..d9049c889823
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-sysv.exp
@@ -0,0 +1,8 @@
+ax:Q b c d eb
+bcd.e
+&
+anchor-dollar: value
+anchor-dollar: valux
+mismatch: file.cpp file.h
+mismatch: renamed.c other.c
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-sysv.mk b/contrib/bmake/unit-tests/varmod-sysv.mk
new file mode 100644
index 000000000000..c8410ed900d8
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-sysv.mk
@@ -0,0 +1,61 @@
+# $NetBSD: varmod-sysv.mk,v 1.3 2020/08/23 14:52:06 rillig Exp $
+#
+# Tests for the ${VAR:from=to} variable modifier, which replaces the suffix
+# "from" with "to". It can also use '%' as a wildcard.
+#
+# This modifier is applied when the other modifiers don't match exactly.
+
+all: words ampersand anchor-dollar mismatch
+
+# The :Q looks like a modifier but isn't.
+# It is part of the replacement string.
+words:
+ @echo a${a b c d e:L:%a=x:Q}b
+
+# Before 2020-07-19, an ampersand could be used in the replacement part
+# of a SysV substitution modifier. This was probably a copy-and-paste
+# mistake since the SysV modifier code looked a lot like the code for the
+# :S and :C modifiers. The ampersand is not mentioned in the manual page.
+ampersand:
+ @echo ${:U${a.bcd.e:L:a.%=%}:Q}
+ @echo ${:U${a.bcd.e:L:a.%=&}:Q}
+
+# Before 2020-07-20, when a SysV modifier was parsed, a single dollar
+# before the '=' was interpreted as an anchor, which doesn't make sense
+# since the anchor was discarded immediately.
+anchor-dollar:
+ @echo $@: ${:U${value:L:e$=x}:Q}
+ @echo $@: ${:U${value:L:e=x}:Q}
+
+# Words that don't match are copied unmodified.
+# The % placeholder can be anywhere in the string.
+mismatch:
+ @echo $@: ${:Ufile.c file.h:%.c=%.cpp}
+ @echo $@: ${:Ufile.c other.c:file.%=renamed.%}
+
+# Trying to cover all possible variants of the SysV modifier.
+LIST= one two
+EXPR.1= ${LIST:o=X}
+EXP.1= one twX
+EXPR.2= ${LIST:o=}
+EXP.2= one tw
+EXPR.3= ${LIST:o=%}
+EXP.3= one tw%
+EXPR.4= ${LIST:%o=X}
+EXP.4= one X
+EXPR.5= ${LIST:o%=X}
+EXP.5= X two
+EXPR.6= ${LIST:o%e=X}
+EXP.6= X two
+EXPR.7= ${LIST:o%%e=X} # Only the first '%' is the wildcard.
+EXP.7= one two # None of the words contains a literal '%'.
+EXPR.8= ${LIST:%=%%}
+EXP.8= one% two%
+EXPR.9= ${LIST:%nes=%xxx} # lhs is longer than the word "one"
+EXP.9= one two
+
+.for i in ${:U:range=9}
+.if ${EXPR.$i} != ${EXP.$i}
+.warning test case $i expected "${EXP.$i}", got "${EXPR.$i}
+.endif
+.endfor
diff --git a/contrib/bmake/unit-tests/varmod-tail.exp b/contrib/bmake/unit-tests/varmod-tail.exp
new file mode 100644
index 000000000000..e25c1cc4b914
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-tail.exp
@@ -0,0 +1,10 @@
+tail (basename) of 'a/b/c' is 'c'
+tail (basename) of 'def' is 'def'
+tail (basename) of 'a.b.c' is 'a.b.c'
+tail (basename) of 'a.b/c' is 'c'
+tail (basename) of 'a' is 'a'
+tail (basename) of 'a.a' is 'a.a'
+tail (basename) of '.gitignore' is '.gitignore'
+tail (basename) of 'a' is 'a'
+tail (basename) of 'a.a' is 'a.a'
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-tail.mk b/contrib/bmake/unit-tests/varmod-tail.mk
new file mode 100644
index 000000000000..a8078cc67335
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-tail.mk
@@ -0,0 +1,9 @@
+# $NetBSD: varmod-tail.mk,v 1.3 2020/08/23 15:09:15 rillig Exp $
+#
+# Tests for the :T variable modifier, which returns the basename of each of
+# the words in the variable value.
+
+all:
+.for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a
+ @echo "tail (basename) of '"${path:Q}"' is '"${path:T:Q}"'"
+.endfor
diff --git a/contrib/bmake/unit-tests/varmod-to-abs.exp b/contrib/bmake/unit-tests/varmod-to-abs.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-to-abs.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-to-abs.mk b/contrib/bmake/unit-tests/varmod-to-abs.mk
new file mode 100644
index 000000000000..7a74e89088e5
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-to-abs.mk
@@ -0,0 +1,9 @@
+# $NetBSD: varmod-to-abs.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the :tA variable modifier, which returns the absolute path for
+# each of the words in the variable value.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-to-lower.exp b/contrib/bmake/unit-tests/varmod-to-lower.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-to-lower.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-to-lower.mk b/contrib/bmake/unit-tests/varmod-to-lower.mk
new file mode 100644
index 000000000000..38a4add4cfe5
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-to-lower.mk
@@ -0,0 +1,19 @@
+# $NetBSD: varmod-to-lower.mk,v 1.3 2020/08/28 17:21:02 rillig Exp $
+#
+# Tests for the :tl variable modifier, which returns the words in the
+# variable value, converted to lowercase.
+
+.if ${:UUPPER:tl} != "upper"
+.error
+.endif
+
+.if ${:Ulower:tl} != "lower"
+.error
+.endif
+
+.if ${:UMixeD case.:tl} != "mixed case."
+.error
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-to-many-words.exp b/contrib/bmake/unit-tests/varmod-to-many-words.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-to-many-words.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-to-many-words.mk b/contrib/bmake/unit-tests/varmod-to-many-words.mk
new file mode 100644
index 000000000000..10cddb00c5e4
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-to-many-words.mk
@@ -0,0 +1,9 @@
+# $NetBSD: varmod-to-many-words.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the :tw modifier, which treats the variable as many words,
+# to undo a previous :tW modifier.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-to-one-word.exp b/contrib/bmake/unit-tests/varmod-to-one-word.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-to-one-word.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-to-one-word.mk b/contrib/bmake/unit-tests/varmod-to-one-word.mk
new file mode 100644
index 000000000000..0865ce8fb41f
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-to-one-word.mk
@@ -0,0 +1,9 @@
+# $NetBSD: varmod-to-one-word.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the :tW variable modifier, which treats the variable value
+# as a single word, for all following modifiers.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-to-separator.exp b/contrib/bmake/unit-tests/varmod-to-separator.exp
new file mode 100644
index 000000000000..07dd5b5e7c5b
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-to-separator.exp
@@ -0,0 +1,9 @@
+make: Bad modifier `:tx' for WORDS
+make: "varmod-to-separator.mk" line 104: Malformed conditional (${WORDS:tx} != "anything")
+make: "varmod-to-separator.mk" line 108: Parsing continues here.
+make: Bad modifier `:t\X' for WORDS
+make: "varmod-to-separator.mk" line 112: Malformed conditional (${WORDS:t\X} != "anything")
+make: "varmod-to-separator.mk" line 115: Parsing continues here.
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/varmod-to-separator.mk b/contrib/bmake/unit-tests/varmod-to-separator.mk
new file mode 100644
index 000000000000..68e728c276dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-to-separator.mk
@@ -0,0 +1,118 @@
+# $NetBSD: varmod-to-separator.mk,v 1.3 2020/08/31 19:58:21 rillig Exp $
+#
+# Tests for the :ts variable modifier, which joins the words of the variable
+# using an arbitrary character as word separator.
+
+WORDS= one two three four five six
+
+# The words are separated by a single space, just as usual.
+.if ${WORDS:ts } != "one two three four five six"
+. warning Space as separator does not work.
+.endif
+
+# The separator can be an arbitrary character, for example a comma.
+.if ${WORDS:ts,} != "one,two,three,four,five,six"
+. warning Comma as separator does not work.
+.endif
+
+# After the :ts modifier, other modifiers can follow.
+.if ${WORDS:ts/:tu} != "ONE/TWO/THREE/FOUR/FIVE/SIX"
+. warning Chaining modifiers does not work.
+.endif
+
+# To use the ':' as the separator, just write it normally.
+# The first colon is the separator, the second ends the modifier.
+.if ${WORDS:ts::tu} != "ONE:TWO:THREE:FOUR:FIVE:SIX"
+. warning Colon as separator does not work.
+.endif
+
+# When there is just a colon but no other character, the words are
+# "separated" by an empty string, that is, they are all squashed
+# together.
+.if ${WORDS:ts:tu} != "ONETWOTHREEFOURFIVESIX"
+. warning Colon as separator does not work.
+.endif
+
+# Applying the :tu modifier first and then the :ts modifier does not change
+# anything since neither of these modifiers is related to how the string is
+# split into words. Beware of separating the words using a single or double
+# quote though, or other special characters like dollar or backslash.
+#
+# This example also demonstrates that the closing brace is not interpreted
+# as a separator, but as the closing delimiter of the whole variable
+# expression.
+.if ${WORDS:tu:ts} != "ONETWOTHREEFOURFIVESIX"
+. warning Colon as separator does not work.
+.endif
+
+# The '}' plays the same role as the ':' in the preceding examples.
+# Since there is a single character before it, that character is taken as
+# the separator.
+.if ${WORDS:tu:ts/} != "ONE/TWO/THREE/FOUR/FIVE/SIX"
+. warning Colon as separator does not work.
+.endif
+
+# Now it gets interesting and ambiguous: The separator could either be empty
+# since it is followed by a colon. Or it could be the colon since that
+# colon is followed by the closing brace. It's the latter case.
+.if ${WORDS:ts:} != "one:two:three:four:five:six"
+. warning Colon followed by closing brace does not work.
+.endif
+
+# As in the ${WORDS:tu:ts} example above, the separator is empty.
+.if ${WORDS:ts} != "onetwothreefourfivesix"
+. warning Empty separator before closing brace does not work.
+.endif
+
+# The :ts modifier can be followed by other modifiers.
+.if ${WORDS:ts:S/two/2/} != "one2threefourfivesix"
+. warning Separator followed by :S modifier does not work.
+.endif
+
+# The :ts modifier can follow other modifiers.
+.if ${WORDS:S/two/2/:ts} != "one2threefourfivesix"
+. warning :S modifier followed by :ts modifier does not work.
+.endif
+
+# The :ts modifier with an actual separator can be followed by other
+# modifiers.
+.if ${WORDS:ts/:S/two/2/} != "one/2/three/four/five/six"
+. warning The :ts modifier followed by an :S modifier does not work.
+.endif
+
+# The separator can be \n, which is a newline.
+.if ${WORDS:[1..3]:ts\n} != "one${.newline}two${.newline}three"
+. warning The separator \n does not produce a newline.
+.endif
+
+# The separator can be \t, which is a tab.
+.if ${WORDS:[1..3]:ts\t} != "one two three"
+. warning The separator \t does not produce a tab.
+.endif
+
+# The separator can be given as octal number.
+.if ${WORDS:[1..3]:ts\012:tu} != "ONE${.newline}TWO${.newline}THREE"
+. warning The separator \012 is not interpreted in octal ASCII.
+.endif
+
+# The separator can be given as hexadecimal number.
+.if ${WORDS:[1..3]:ts\xa:tu} != "ONE${.newline}TWO${.newline}THREE"
+. warning The separator \xa is not interpreted in hexadecimal ASCII.
+.endif
+
+# In the :t modifier, the :t must be followed by any of A, l, s, u.
+.if ${WORDS:tx} != "anything"
+. info This line is not reached because of the malformed condition.
+. info If this line were reached, it would be visible in the -dcpv log.
+.endif
+.info Parsing continues here.
+
+# After the backslash, only n, t, an octal number, or x and a hexadecimal
+# number are allowed.
+.if ${WORDS:t\X} != "anything"
+. info This line is not reached.
+.endif
+.info Parsing continues here.
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-to-upper.exp b/contrib/bmake/unit-tests/varmod-to-upper.exp
new file mode 100644
index 000000000000..8453da538d3b
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-to-upper.exp
@@ -0,0 +1,2 @@
+mod-tu-space: A B
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-to-upper.mk b/contrib/bmake/unit-tests/varmod-to-upper.mk
new file mode 100644
index 000000000000..26c0b25e5142
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-to-upper.mk
@@ -0,0 +1,21 @@
+# $NetBSD: varmod-to-upper.mk,v 1.4 2020/08/28 17:21:02 rillig Exp $
+#
+# Tests for the :tu variable modifier, which returns the words in the
+# variable value, converted to uppercase.
+
+.if ${:UUPPER:tu} != "UPPER"
+.error
+.endif
+
+.if ${:Ulower:tu} != "LOWER"
+.error
+.endif
+
+.if ${:UMixeD case.:tu} != "MIXED CASE."
+.error
+.endif
+
+# The :tu and :tl modifiers operate on the variable value as a single string,
+# not as a list of words. Therefore, the adjacent spaces are preserved.
+mod-tu-space:
+ @echo $@: ${a b:L:tu:Q}
diff --git a/contrib/bmake/unit-tests/varmod-undefined.exp b/contrib/bmake/unit-tests/varmod-undefined.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-undefined.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-undefined.mk b/contrib/bmake/unit-tests/varmod-undefined.mk
new file mode 100644
index 000000000000..9ed35a8406fe
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-undefined.mk
@@ -0,0 +1,55 @@
+# $NetBSD: varmod-undefined.mk,v 1.3 2020/08/23 20:49:33 rillig Exp $
+#
+# Tests for the :U variable modifier, which returns the given string
+# if the variable is undefined.
+#
+# The pattern ${:Uword} is heavily used when expanding .for loops.
+
+# This is how an expanded .for loop looks like.
+# .for word in one
+# . if ${word} != one
+# . error ${word}
+# . endif
+# .endfor
+
+.if ${:Uone} != one
+. error ${:Uone}
+.endif
+
+# The variable expressions in the text of the :U modifier may be arbitrarily
+# nested.
+
+.if ${:U${:Unested}${${${:Udeeply}}}} != nested
+.error
+.endif
+
+# The nested variable expressions may contain braces, and these braces don't
+# need to match pairwise. In the following example, the :S modifier uses '{'
+# as delimiter, which confuses both editors and humans because the opening
+# and # closing braces don't match anymore. It's syntactically valid though.
+# For more similar examples, see varmod-subst.mk, mod-subst-delimiter.
+
+.if ${:U${:Uvalue:S{a{X{}} != vXlue
+.error
+.endif
+
+# The escaping rules for the :U modifier (left-hand side) and condition
+# string literals (right-hand side) are completely different.
+#
+# In the :U modifier, the backslash only escapes very few characters, all
+# other backslashes are retained.
+#
+# In condition string literals, the backslash always escapes the following
+# character, no matter whether it would be necessary or not.
+#
+# In both contexts, \n is an escaped letter n, not a newline; that's what
+# the .newline variable is for.
+#
+# Whitespace at the edges is preserved, on both sides of the comparison.
+
+.if ${:U \: \} \$ \\ \a \b \n } != " : } \$ \\ \\a \\b \\n "
+.error
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-unique.exp b/contrib/bmake/unit-tests/varmod-unique.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-unique.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-unique.mk b/contrib/bmake/unit-tests/varmod-unique.mk
new file mode 100644
index 000000000000..ea4698764947
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-unique.mk
@@ -0,0 +1,39 @@
+# $NetBSD: varmod-unique.mk,v 1.4 2020/08/31 17:41:38 rillig Exp $
+#
+# Tests for the :u variable modifier, which discards adjacent duplicate
+# words.
+
+.if ${:U1 2 1:u} != "1 2 1"
+. warning The :u modifier only merges _adjacent_ duplicate words.
+.endif
+
+.if ${:U1 2 2 3:u} != "1 2 3"
+. warning The :u modifier must merge adjacent duplicate words.
+.endif
+
+.if ${:U:u} != ""
+. warning The :u modifier must do nothing with an empty word list.
+.endif
+
+.if ${:U1:u} != "1"
+. warning The :u modifier must do nothing with a single-element word list.
+.endif
+
+.if ${:U1 1 1 1 1 1 1 1:u} != "1"
+. warning The :u modifier must merge _all_ adjacent duplicate words.
+.endif
+
+.if ${:U 1 2 1 1 :u} != "1 2 1"
+. warning The :u modifier must normalize whitespace between the words.
+.endif
+
+.if ${:U1 1 1 1 2:u} != "1 2"
+. warning Duplicate words at the beginning must be merged.
+.endif
+
+.if ${:U1 2 2 2 2:u} != "1 2"
+. warning Duplicate words at the end must be merged.
+.endif
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod.exp b/contrib/bmake/unit-tests/varmod.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod.mk b/contrib/bmake/unit-tests/varmod.mk
new file mode 100644
index 000000000000..68bf165bf72b
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varmod.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for variable modifiers, such as :Q, :S,from,to or :Ufallback.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dollar.exp b/contrib/bmake/unit-tests/varname-dollar.exp
new file mode 100644
index 000000000000..c880e82f0170
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dollar.exp
@@ -0,0 +1,5 @@
+make: "varname-dollar.mk" line 16: dollar is $.
+make: "varname-dollar.mk" line 17: dollar in braces is .
+make: "varname-dollar.mk" line 25: dollar is $.
+make: "varname-dollar.mk" line 26: dollar in braces is dollar.
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dollar.mk b/contrib/bmake/unit-tests/varname-dollar.mk
new file mode 100644
index 000000000000..d1db9f833306
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dollar.mk
@@ -0,0 +1,29 @@
+# $NetBSD: varname-dollar.mk,v 1.3 2020/08/19 05:40:06 rillig Exp $
+#
+# Tests for the expression "$$", which looks as if it referred to a variable,
+# but simply expands to a single '$' sign.
+#
+# If there really were a special variable named '$', the expressions ${${DOLLAR}}
+# and $$ would always expand to the same value.
+
+# Using the dollar sign in variable names is tricky and not recommended.
+# To see that using this variable indeed affects the variable '$', run the
+# test individually with the -dv option.
+DOLLAR= $$
+
+# At this point, the variable '$' is not defined. Therefore the second line
+# returns an empty string.
+.info dollar is $$.
+.info dollar in braces is ${${DOLLAR}}.
+
+# Now overwrite the '$' variable to see whether '$$' really expands to that
+# variable, or whether '$$' is handled by the parser.
+${DOLLAR}= dollar
+
+# At this point, the variable '$' is defined, therefore its value is printed
+# in the second .info directive.
+.info dollar is $$.
+.info dollar in braces is ${${DOLLAR}}.
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-alltargets.exp b/contrib/bmake/unit-tests/varname-dot-alltargets.exp
new file mode 100644
index 000000000000..992c2573b519
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-alltargets.exp
@@ -0,0 +1,4 @@
+
+first second source
+first second source all .END
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-alltargets.mk b/contrib/bmake/unit-tests/varname-dot-alltargets.mk
new file mode 100644
index 000000000000..0d16c8e6fef0
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-alltargets.mk
@@ -0,0 +1,25 @@
+# $NetBSD: varname-dot-alltargets.mk,v 1.3 2020/08/25 22:51:54 rillig Exp $
+#
+# Tests for the special .ALLTARGETS variable.
+
+.MAIN: all
+
+TARGETS_1:= ${.ALLTARGETS}
+
+first second: source
+
+TARGETS_2:= ${.ALLTARGETS}
+
+all:
+ # Since the tests are run with the -r option, no targets are
+ # defined at the beginning.
+ @echo ${TARGETS_1}
+
+ # Only first and second are "real" targets.
+ # The .ALLTARGETS variable is not about targets though, but
+ # about all nodes, therefore source is also included.
+ @echo ${TARGETS_2}
+
+ # Interestingly, the .END target is also implicitly defined at
+ # this point.
+ @echo ${.ALLTARGETS}
diff --git a/contrib/bmake/unit-tests/varname-dot-curdir.exp b/contrib/bmake/unit-tests/varname-dot-curdir.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-curdir.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-curdir.mk b/contrib/bmake/unit-tests/varname-dot-curdir.mk
new file mode 100644
index 000000000000..3795d87b030f
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-curdir.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-curdir.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .CURDIR variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-includedfromdir.exp b/contrib/bmake/unit-tests/varname-dot-includedfromdir.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-includedfromdir.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-includedfromdir.mk b/contrib/bmake/unit-tests/varname-dot-includedfromdir.mk
new file mode 100644
index 000000000000..e5a61793baaf
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-includedfromdir.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-includedfromdir.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .INCLUDEDFROMDIR variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-includedfromfile.exp b/contrib/bmake/unit-tests/varname-dot-includedfromfile.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-includedfromfile.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-includedfromfile.mk b/contrib/bmake/unit-tests/varname-dot-includedfromfile.mk
new file mode 100644
index 000000000000..403f562c27fb
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-includedfromfile.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-includedfromfile.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .INCLUDEDFROMFILE variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-includes.exp b/contrib/bmake/unit-tests/varname-dot-includes.exp
new file mode 100755
index 000000000000..edc90f068899
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-includes.exp
@@ -0,0 +1,2 @@
+.INCLUDES= -I. -I..
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-includes.mk b/contrib/bmake/unit-tests/varname-dot-includes.mk
new file mode 100755
index 000000000000..33e8669c27be
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-includes.mk
@@ -0,0 +1,20 @@
+# $NetBSD: varname-dot-includes.mk,v 1.1 2020/08/28 03:51:06 rillig Exp $
+#
+# Tests for the special .INCLUDES variable, which is not documented in the
+# manual page.
+#
+# It is yet unclear in which situations this feature is useful.
+
+.SUFFIXES: .h
+
+.PATH.h: . ..
+
+.INCLUDES: .h
+
+# The .INCLUDES variable is not yet available.
+.if defined(${.INCLUDES:Q})
+.error
+.endif
+
+all:
+ @echo .INCLUDES=${.INCLUDES:Q}
diff --git a/contrib/bmake/unit-tests/varname-dot-libs.exp b/contrib/bmake/unit-tests/varname-dot-libs.exp
new file mode 100755
index 000000000000..97b7e720bdd4
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-libs.exp
@@ -0,0 +1,2 @@
+.LIBS= -L. -L..
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-libs.mk b/contrib/bmake/unit-tests/varname-dot-libs.mk
new file mode 100755
index 000000000000..80abeb50b48c
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-libs.mk
@@ -0,0 +1,20 @@
+# $NetBSD: varname-dot-libs.mk,v 1.1 2020/08/28 03:51:06 rillig Exp $
+#
+# Tests for the special .LIBS variable, which is not documented in the
+# manual page.
+#
+# It is yet unclear in which situations this feature is useful.
+
+.SUFFIXES: .a
+
+.PATH.a: . ..
+
+.LIBS: .a
+
+# The .LIBS variable is not yet available.
+.if defined(${.LIBS:Q})
+.error
+.endif
+
+all:
+ @echo .LIBS=${.LIBS:Q}
diff --git a/contrib/bmake/unit-tests/varname-dot-make-dependfile.exp b/contrib/bmake/unit-tests/varname-dot-make-dependfile.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-dependfile.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-dependfile.mk b/contrib/bmake/unit-tests/varname-dot-make-dependfile.mk
new file mode 100644
index 000000000000..882c5344d9be
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-dependfile.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-dependfile.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.DEPENDFILE variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-expand_variables.exp b/contrib/bmake/unit-tests/varname-dot-make-expand_variables.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-expand_variables.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-expand_variables.mk b/contrib/bmake/unit-tests/varname-dot-make-expand_variables.mk
new file mode 100644
index 000000000000..cff2db4b9a21
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-expand_variables.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-expand_variables.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.EXPAND_VARIABLES variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-exported.exp b/contrib/bmake/unit-tests/varname-dot-make-exported.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-exported.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-exported.mk b/contrib/bmake/unit-tests/varname-dot-make-exported.mk
new file mode 100644
index 000000000000..123762dea9d0
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-exported.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-exported.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.EXPORTED variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-jobs-prefix.exp b/contrib/bmake/unit-tests/varname-dot-make-jobs-prefix.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-jobs-prefix.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-jobs-prefix.mk b/contrib/bmake/unit-tests/varname-dot-make-jobs-prefix.mk
new file mode 100644
index 000000000000..bf445ab2d349
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-jobs-prefix.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-jobs-prefix.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.JOBS.PREFIX variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-jobs.exp b/contrib/bmake/unit-tests/varname-dot-make-jobs.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-jobs.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-jobs.mk b/contrib/bmake/unit-tests/varname-dot-make-jobs.mk
new file mode 100644
index 000000000000..1e99b3d28ea8
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-jobs.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-jobs.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.JOBS variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-level.exp b/contrib/bmake/unit-tests/varname-dot-make-level.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-level.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-level.mk b/contrib/bmake/unit-tests/varname-dot-make-level.mk
new file mode 100644
index 000000000000..c4f2c0db7da6
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-level.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-level.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.LEVEL variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-makefile_preference.exp b/contrib/bmake/unit-tests/varname-dot-make-makefile_preference.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-makefile_preference.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-makefile_preference.mk b/contrib/bmake/unit-tests/varname-dot-make-makefile_preference.mk
new file mode 100644
index 000000000000..58cc8a2bf516
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-makefile_preference.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-makefile_preference.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.MAKEFILE_PREFERENCE variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-makefiles.exp b/contrib/bmake/unit-tests/varname-dot-make-makefiles.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-makefiles.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-makefiles.mk b/contrib/bmake/unit-tests/varname-dot-make-makefiles.mk
new file mode 100644
index 000000000000..f7449bbfc729
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-makefiles.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-makefiles.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.MAKEFILES variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-bailiwick.exp b/contrib/bmake/unit-tests/varname-dot-make-meta-bailiwick.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-bailiwick.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-bailiwick.mk b/contrib/bmake/unit-tests/varname-dot-make-meta-bailiwick.mk
new file mode 100644
index 000000000000..130a7b483291
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-bailiwick.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-meta-bailiwick.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.META.BAILIWICK variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-created.exp b/contrib/bmake/unit-tests/varname-dot-make-meta-created.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-created.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-created.mk b/contrib/bmake/unit-tests/varname-dot-make-meta-created.mk
new file mode 100644
index 000000000000..bc149b60ddf4
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-created.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-meta-created.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.META.CREATED variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-files.exp b/contrib/bmake/unit-tests/varname-dot-make-meta-files.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-files.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-files.mk b/contrib/bmake/unit-tests/varname-dot-make-meta-files.mk
new file mode 100644
index 000000000000..e6fe9e92bfca
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-files.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-meta-files.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.META.FILES variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.exp b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.mk b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.mk
new file mode 100644
index 000000000000..c41aec4acdf8
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-meta-ignore_filter.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.META.IGNORE_FILTER variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.exp b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.mk b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.mk
new file mode 100644
index 000000000000..4ae34f51608b
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-meta-ignore_paths.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.META.IGNORE_PATHS variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.exp b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.mk b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.mk
new file mode 100644
index 000000000000..ea9fc49f1718
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-meta-ignore_patterns.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.META.IGNORE_PATTERNS variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-prefix.exp b/contrib/bmake/unit-tests/varname-dot-make-meta-prefix.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-prefix.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-prefix.mk b/contrib/bmake/unit-tests/varname-dot-make-meta-prefix.mk
new file mode 100644
index 000000000000..d4fdf64e6db3
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-prefix.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-meta-prefix.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.META.PREFIX variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-mode.exp b/contrib/bmake/unit-tests/varname-dot-make-mode.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-mode.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-mode.mk b/contrib/bmake/unit-tests/varname-dot-make-mode.mk
new file mode 100644
index 000000000000..ee75a54ebd74
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-mode.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-mode.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.MODE variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-path_filemon.exp b/contrib/bmake/unit-tests/varname-dot-make-path_filemon.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-path_filemon.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-path_filemon.mk b/contrib/bmake/unit-tests/varname-dot-make-path_filemon.mk
new file mode 100644
index 000000000000..8c07529f1b4c
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-path_filemon.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-path_filemon.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.PATH_FILEMON variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-pid.exp b/contrib/bmake/unit-tests/varname-dot-make-pid.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-pid.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-pid.mk b/contrib/bmake/unit-tests/varname-dot-make-pid.mk
new file mode 100644
index 000000000000..bea114d33547
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-pid.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-pid.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.PID variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-ppid.exp b/contrib/bmake/unit-tests/varname-dot-make-ppid.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-ppid.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-ppid.mk b/contrib/bmake/unit-tests/varname-dot-make-ppid.mk
new file mode 100644
index 000000000000..c9471542ca35
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-ppid.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-ppid.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.PPID variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-make-save_dollars.exp b/contrib/bmake/unit-tests/varname-dot-make-save_dollars.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-save_dollars.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-save_dollars.mk b/contrib/bmake/unit-tests/varname-dot-make-save_dollars.mk
new file mode 100644
index 000000000000..97f37a646d2d
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-make-save_dollars.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-make-save_dollars.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.SAVE_DOLLARS variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-makeoverrides.exp b/contrib/bmake/unit-tests/varname-dot-makeoverrides.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-makeoverrides.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-makeoverrides.mk b/contrib/bmake/unit-tests/varname-dot-makeoverrides.mk
new file mode 100644
index 000000000000..a897f4667175
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-makeoverrides.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-makeoverrides.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.MAKEOVERRIDES variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-newline.exp b/contrib/bmake/unit-tests/varname-dot-newline.exp
new file mode 100644
index 000000000000..13943539bf3b
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-newline.exp
@@ -0,0 +1,4 @@
+make: "varname-dot-newline.mk" line 16: The .newline variable can be overwritten. Just don't do that.
+first
+second
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-newline.mk b/contrib/bmake/unit-tests/varname-dot-newline.mk
new file mode 100644
index 000000000000..fa9ac0759649
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-newline.mk
@@ -0,0 +1,23 @@
+# $NetBSD: varname-dot-newline.mk,v 1.3 2020/08/19 05:51:18 rillig Exp $
+#
+# Tests for the special .newline variable.
+#
+# Contrary to the special variable named "" that is used in expressions like
+# ${:Usome-value}, the variable ".newline" is not protected against
+# modification. Nobody exploits that though.
+
+NEWLINE:= ${.newline}
+
+.newline= overwritten
+
+.if ${.newline} == ${NEWLINE}
+.info The .newline variable cannot be overwritten. Good.
+.else
+.info The .newline variable can be overwritten. Just don't do that.
+.endif
+
+# Restore the original value.
+.newline= ${NEWLINE}
+
+all:
+ @echo 'first${.newline}second'
diff --git a/contrib/bmake/unit-tests/varname-dot-objdir.exp b/contrib/bmake/unit-tests/varname-dot-objdir.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-objdir.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-objdir.mk b/contrib/bmake/unit-tests/varname-dot-objdir.mk
new file mode 100644
index 000000000000..e662e8ac56fa
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-objdir.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-objdir.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .OBJDIR variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-parsedir.exp b/contrib/bmake/unit-tests/varname-dot-parsedir.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-parsedir.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-parsedir.mk b/contrib/bmake/unit-tests/varname-dot-parsedir.mk
new file mode 100644
index 000000000000..6aeb5878a457
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-parsedir.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-parsedir.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .PARSEDIR variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-parsefile.exp b/contrib/bmake/unit-tests/varname-dot-parsefile.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-parsefile.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-parsefile.mk b/contrib/bmake/unit-tests/varname-dot-parsefile.mk
new file mode 100644
index 000000000000..9deff34a70d0
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-parsefile.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-parsefile.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .PARSEFILE variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-path.exp b/contrib/bmake/unit-tests/varname-dot-path.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-path.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-path.mk b/contrib/bmake/unit-tests/varname-dot-path.mk
new file mode 100644
index 000000000000..8f4e3a5dcfa4
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-path.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-path.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .PATH variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-dot-shell.exp b/contrib/bmake/unit-tests/varname-dot-shell.exp
new file mode 100755
index 000000000000..af4baaf44c0b
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-shell.exp
@@ -0,0 +1,19 @@
+ParseReadLine (8): 'ORIG_SHELL:= ${.SHELL}'
+Var_Parse: ${.SHELL} with VARE_WANTRES|VARE_ASSIGN
+Global:delete .SHELL (not found)
+Command:.SHELL = (details omitted)
+ParseReadLine (10): '.SHELL= overwritten'
+Global:.SHELL = overwritten
+Var_Parse: ${.SHELL} != ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES
+ParseReadLine (18): '.undef .SHELL'
+Global:delete .SHELL
+ParseReadLine (19): '.SHELL= newly overwritten'
+Global:.SHELL = newly overwritten
+Var_Parse: ${.SHELL} != ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES
+ParseReadLine (24): 'all:'
+ParseReadLine (25): ' @echo ${.SHELL:M*}'
+Var_Parse: ${.SHELL:M*} with VARE_WANTRES
+Applying ${.SHELL:M...} to "(details omitted)" (eflags = VARE_WANTRES, vflags = VAR_READONLY)
+Pattern[.SHELL] for [(details omitted)] is [*]
+Result of ${.SHELL:M*} is "(details omitted)" (eflags = VARE_WANTRES, vflags = VAR_READONLY)
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-shell.mk b/contrib/bmake/unit-tests/varname-dot-shell.mk
new file mode 100755
index 000000000000..4a2c52b39cbc
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-shell.mk
@@ -0,0 +1,25 @@
+# $NetBSD: varname-dot-shell.mk,v 1.2 2020/08/23 09:28:52 rillig Exp $
+#
+# Tests for the special .SHELL variable, which contains the shell used for
+# running the commands.
+#
+# This variable is read-only.
+
+ORIG_SHELL:= ${.SHELL}
+
+.SHELL= overwritten
+.if ${.SHELL} != ${ORIG_SHELL}
+.error
+.endif
+
+# Trying to delete the variable.
+# This has no effect since the variable is not defined in the global context,
+# but in the command-line context.
+.undef .SHELL
+.SHELL= newly overwritten
+.if ${.SHELL} != ${ORIG_SHELL}
+.error
+.endif
+
+all:
+ @echo ${.SHELL:M*}
diff --git a/contrib/bmake/unit-tests/varname-dot-targets.exp b/contrib/bmake/unit-tests/varname-dot-targets.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-targets.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-targets.mk b/contrib/bmake/unit-tests/varname-dot-targets.mk
new file mode 100644
index 000000000000..2d1665744040
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-dot-targets.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-dot-targets.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .TARGETS variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-empty.exp b/contrib/bmake/unit-tests/varname-empty.exp
new file mode 100644
index 000000000000..77cb7c517e9d
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-empty.exp
@@ -0,0 +1,11 @@
+Var_Set("${:U}", "cmdline-u", ...) name expands to empty string - ignored
+Var_Set("", "cmline-plain", ...) name expands to empty string - ignored
+Var_Set("", "default", ...) name expands to empty string - ignored
+Var_Set("", "assigned", ...) name expands to empty string - ignored
+Var_Set("", "appended", ...) name expands to empty string - ignored
+Var_Set("", "", ...) name expands to empty string - ignored
+Var_Set("", "subst", ...) name expands to empty string - ignored
+Var_Set("", "shell-output", ...) name expands to empty string - ignored
+out: fallback
+out: 1 2 3
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-empty.mk b/contrib/bmake/unit-tests/varname-empty.mk
new file mode 100755
index 000000000000..b4ce05c3017b
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-empty.mk
@@ -0,0 +1,26 @@
+# $NetBSD: varname-empty.mk,v 1.5 2020/08/22 21:22:24 rillig Exp $
+#
+# Tests for the special variable with the empty name.
+#
+# The variable "" is not supposed to be assigned any value.
+# This is because it is heavily used in the .for loop expansion,
+# as well as to generate arbitrary strings, as in ${:Ufallback}.
+
+# Until 2020-08-22 it was possible to assign a value to the variable with
+# the empty name, leading to all kinds of unexpected effects.
+?= default
+= assigned # undefined behavior until 2020-08-22
++= appended
+:= subst
+!= echo 'shell-output'
+
+# The .for loop expands the expression ${i} to ${:U1}, ${:U2} and so on.
+# This only works if the variable with the empty name is guaranteed to
+# be undefined.
+.for i in 1 2 3
+NUMBERS+= ${i}
+.endfor
+
+all:
+ @echo out: ${:Ufallback}
+ @echo out: ${NUMBERS}
diff --git a/contrib/bmake/unit-tests/varname-make.exp b/contrib/bmake/unit-tests/varname-make.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-make.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-make.mk b/contrib/bmake/unit-tests/varname-make.mk
new file mode 100644
index 000000000000..049f00ef741b
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-make.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-make.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special MAKE variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-make_print_var_on_error.exp b/contrib/bmake/unit-tests/varname-make_print_var_on_error.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-make_print_var_on_error.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-make_print_var_on_error.mk b/contrib/bmake/unit-tests/varname-make_print_var_on_error.mk
new file mode 100644
index 000000000000..757d55780be1
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-make_print_var_on_error.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-make_print_var_on_error.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special .MAKE.PRINT_VAR_ON_ERROR variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-makeflags.exp b/contrib/bmake/unit-tests/varname-makeflags.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-makeflags.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-makeflags.mk b/contrib/bmake/unit-tests/varname-makeflags.mk
new file mode 100644
index 000000000000..b2e5f68b4e08
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-makeflags.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-makeflags.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special MAKEFLAGS variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-pwd.exp b/contrib/bmake/unit-tests/varname-pwd.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-pwd.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-pwd.mk b/contrib/bmake/unit-tests/varname-pwd.mk
new file mode 100644
index 000000000000..e11ee828117b
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-pwd.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-pwd.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special PWD variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname-vpath.exp b/contrib/bmake/unit-tests/varname-vpath.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-vpath.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-vpath.mk b/contrib/bmake/unit-tests/varname-vpath.mk
new file mode 100644
index 000000000000..8924647a5072
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-vpath.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname-vpath.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for the special VPATH variable.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varname.exp b/contrib/bmake/unit-tests/varname.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname.mk b/contrib/bmake/unit-tests/varname.mk
new file mode 100644
index 000000000000..9dd965083f3f
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname.mk
@@ -0,0 +1,8 @@
+# $NetBSD: varname.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+#
+# Tests for special variables, such as .MAKE or .PARSEDIR.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varparse-dynamic.exp b/contrib/bmake/unit-tests/varparse-dynamic.exp
new file mode 100644
index 000000000000..a2ff29413167
--- /dev/null
+++ b/contrib/bmake/unit-tests/varparse-dynamic.exp
@@ -0,0 +1,5 @@
+make: "varparse-dynamic.mk" line 8: Malformed conditional (${.TARGEX})
+make: "varparse-dynamic.mk" line 10: Malformed conditional (${.TARGXX})
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/varparse-dynamic.mk b/contrib/bmake/unit-tests/varparse-dynamic.mk
new file mode 100644
index 000000000000..724cca3a5035
--- /dev/null
+++ b/contrib/bmake/unit-tests/varparse-dynamic.mk
@@ -0,0 +1,14 @@
+# $NetBSD: varparse-dynamic.mk,v 1.1 2020/07/26 22:15:36 rillig Exp $
+
+# Before 2020-07-27, there was an off-by-one error in Var_Parse that skipped
+# the last character in the variable name.
+# To trigger the bug, the variable must not be defined.
+.if ${.TARGET} # exact match, may be undefined
+.endif
+.if ${.TARGEX} # 1 character difference, must be defined
+.endif
+.if ${.TARGXX} # 2 characters difference, must be defined
+.endif
+
+all:
+ @: