pkgsrc/regress/infra-unittests/subst.sh

1795 lines
49 KiB
Bash

#! /bin/sh
# $NetBSD: subst.sh,v 1.49 2020/06/11 20:10:53 rillig Exp $
#
# Tests for mk/subst.mk.
#
set -eu
. './test.subr'
test_case_set_up() {
create_file 'prepare-subst.mk' <<-EOF
# The tools that are used by subst.mk
AWK= awk
CHMOD= chmod
CMP= cmp
DIFF= diff
ECHO= echo
MKDIR= mkdir -p
MV= mv
RM= rm
RMDIR= rmdir
SED= sed
TEST= test
TOUCH= touch
TOUCH_FLAGS= # none
TR= tr
TRUE= true
# Commands that are specific to pkgsrc
RUN= @set -e;
STEP_MSG= echo '=>'
DO_NADA= : do-nada
INFO_MSG= echo 'info:'
WARNING_MSG= echo 'warning:'
FAIL_MSG= sh $pkgsrcdir/mk/scripts/fail echo 'fail:' 1>&2
WRKDIR= $PWD
WRKSRC= $PWD
EOF
create_file_lines "$mocked_pkgsrcdir/show-pkg-fail-reason.mk" \
'show-pkg-fail-reasons: .PHONY' \
'.if ${PKG_FAIL_REASON:M*}' \
' @printf "fail reason: %s\n" ${PKG_FAIL_REASON} 1>&2 && exit 1' \
'.endif'
}
if test_case_begin 'single file'; then
# A single file is patched successfully.
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= subst-single.txt
SUBST_SED.class= -e 's,before,after,'
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
create_file_lines 'subst-single.txt' \
'before'
run_bmake 'testcase.mk' 'subst-class' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in subst-single.txt'
assert_that 'subst-single.txt' --file-is-lines \
'after'
test_case_end
fi
if test_case_begin 'several individual files'; then
# Several individual files are patched successfully.
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= first second third
SUBST_SED.class= -e 's,file,example,'
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
create_file_lines 'first' 'the first file'
create_file_lines 'second' 'the second file'
create_file_lines 'third' 'the third file'
run_bmake 'testcase.mk' 'subst-class' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in first second third'
assert_that 'first' --file-is-lines 'the first example'
assert_that 'second' --file-is-lines 'the second example'
assert_that 'third' --file-is-lines 'the third example'
test_case_end
fi
if test_case_begin 'several files by pattern'; then
# Several files are patched successfully.
# The filenames are given by a pattern.
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= pattern-*
SUBST_SED.class= -e 's,file,example,'
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
create_file_lines 'pattern-first' 'the first file'
create_file_lines 'pattern-second' 'the second file'
create_file_lines 'pattern-third' 'the third file'
run_bmake 'testcase.mk' 'subst-class' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in pattern-*'
assert_that 'pattern-first' --file-is-lines 'the first example'
assert_that 'pattern-second' --file-is-lines 'the second example'
assert_that 'pattern-third' --file-is-lines 'the third example'
test_case_end
fi
if test_case_begin 'pattern with 1 noop, no-op ok'; then
# Several files are given via a pattern.
# Most of the files are patched, but one stays the same.
# Since it is easier to give a too broad pattern like *.py
# than to exclude a few files from such a pattern,
# only an info is logged.
# This is not an error.
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= pattern-*
SUBST_SED.class= -e 's,file,example,'
SUBST_NOOP_OK.class= yes
.include "prepare-subst.mk"
.include "mk/subst.mk"
all: subst-class
EOF
create_file_lines 'pattern-first' 'the first file'
create_file_lines 'pattern-second' 'the second is already an example'
create_file_lines 'pattern-third' 'the third file'
run_bmake 'testcase.mk' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in pattern-*' \
'info: [subst.mk:class] Nothing changed in "pattern-second".'
assert_that 'pattern-first' --file-is-lines 'the first example'
assert_that 'pattern-second' --file-is-lines 'the second is already an example'
assert_that 'pattern-third' --file-is-lines 'the third example'
test_case_end
fi
if test_case_begin 'pattern with 1 noop, no-op not ok'; then
# Several files are given via a pattern.
# Most of the files are patched, but one stays the same.
# Since it is easier to give a too broad pattern like *.py
# than to exclude a few files from such a pattern,
# only a warning is logged.
# This is not an error.
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= pattern-*
SUBST_SED.class= -e 's,file,example,'
SUBST_NOOP_OK.class= no
.include "prepare-subst.mk"
.include "mk/subst.mk"
all: subst-class
EOF
create_file_lines 'pattern-first' 'the first file'
create_file_lines 'pattern-second' 'the second is already an example'
create_file_lines 'pattern-third' 'the third file'
run_bmake 'testcase.mk' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in pattern-*' \
'warning: [subst.mk:class] Nothing changed in "pattern-second".'
assert_that 'pattern-first' --file-is-lines 'the first example'
assert_that 'pattern-second' --file-is-lines 'the second is already an example'
assert_that 'pattern-third' --file-is-lines 'the third example'
test_case_end
fi
if test_case_begin 'single file noop, noop_ok=yes'; then
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= single
SUBST_SED.class= -e 's,file,example,'
SUBST_NOOP_OK.class= yes
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
create_file_lines 'single' 'already an example'
run_bmake 'testcase.mk' 'subst-class' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in single' \
'info: [subst.mk:class] Nothing changed in "single".'
assert_that 'single' --file-is-lines 'already an example'
test_case_end
fi
if test_case_begin 'single file noop, noop_ok=no'; then
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= single
SUBST_SED.class= -e 's,file,example,'
SUBST_NOOP_OK.class= no
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
create_file_lines 'single' 'already an example'
run_bmake 'testcase.mk' 'subst-class' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in single' \
'warning: [subst.mk:class] Nothing changed in "single".' \
'fail: [subst.mk:class] The filename pattern "single" has no effect.' \
'*** Error code 1' \
'' \
'Stop.' \
"$make: stopped in $PWD"
assert_that 'single' --file-is-lines 'already an example'
assert_that "$exitcode" --equals '1'
test_case_end
fi
if test_case_begin 'single file nonexistent'; then
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= nonexistent
SUBST_SED.class= -e 's,file,example,'
SUBST_NOOP_OK.class= no
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
run_bmake 'testcase.mk' 'subst-class' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in nonexistent' \
'warning: [subst.mk:class] Ignoring nonexistent file "nonexistent".' \
'fail: [subst.mk:class] The filename pattern "nonexistent" has no effect.' \
'*** Error code 1' \
'' \
'Stop.' \
"$make: stopped in $PWD"
assert_that "$exitcode" --equals '1'
test_case_end
fi
if test_case_begin 'single file nonexistent ok'; then
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= nonexistent
SUBST_SED.class= -e 's,file,example,'
SUBST_NOOP_OK.class= yes
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
run_bmake 'testcase.mk' 'subst-class' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in nonexistent' \
'info: [subst.mk:class] Ignoring nonexistent file "nonexistent".'
assert_that "$exitcode" --equals '0'
test_case_end
fi
if test_case_begin 'several filename patterns, 1 nonexistent, no-op ok'; then
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= *exist* *not-found*
SUBST_SED.class= -e 's,file,example,'
SUBST_NOOP_OK.class= yes
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
create_file_lines 'exists' 'this file exists'
run_bmake 'testcase.mk' 'subst-class' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in *exist* *not-found*' \
'info: [subst.mk:class] Ignoring nonexistent file "./*not-found*".'
assert_that 'exists' --file-is-lines 'this example exists'
assert_that "$exitcode" --equals '0'
test_case_end
fi
if test_case_begin 'several filename patterns, 1 nonexistent, no-op not ok'; then
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= *exist* *not-found*
SUBST_SED.class= -e 's,file,example,'
SUBST_NOOP_OK.class= no
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
create_file_lines 'exists' 'this file exists'
run_bmake 'testcase.mk' 'subst-class' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in *exist* *not-found*' \
'warning: [subst.mk:class] Ignoring nonexistent file "./*not-found*".' \
'fail: [subst.mk:class] The filename pattern "*not-found*" has no effect.' \
'*** Error code 1' \
'' \
'Stop.' \
"$make: stopped in $PWD"
assert_that 'exists' --file-is-lines 'this example exists'
assert_that "$exitcode" --equals '1'
test_case_end
fi
if test_case_begin 'multiple missing files, all are reported at once, no-op not ok'; then
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= does not exist
SUBST_SED.class= -e 'sahara'
SUBST_NOOP_OK.class= no
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
run_bmake 'testcase.mk' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in does not exist' \
'warning: [subst.mk:class] Ignoring nonexistent file "does".' \
'warning: [subst.mk:class] Ignoring nonexistent file "not".' \
'warning: [subst.mk:class] Ignoring nonexistent file "exist".' \
'fail: [subst.mk:class] The filename patterns "does not exist" have no effect.' \
'*** Error code 1' \
'' \
'Stop.' \
"$make: stopped in $PWD"
assert_that "$exitcode" --equals '1'
test_case_end
fi
if test_case_begin 'multiple missing files, all are reported at once, no-op ok'; then
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= does not exist
SUBST_SED.class= -e 'sahara'
SUBST_NOOP_OK.class= yes
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
run_bmake 'testcase.mk' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in does not exist' \
'info: [subst.mk:class] Ignoring nonexistent file "does".' \
'info: [subst.mk:class] Ignoring nonexistent file "not".' \
'info: [subst.mk:class] Ignoring nonexistent file "exist".'
assert_that "$exitcode" --equals '0'
test_case_end
fi
if test_case_begin 'multiple no-op files, all are reported at once, no-op not ok'; then
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= first second third
SUBST_SED.class= -e 's,from,to,'
SUBST_NOOP_OK.class= no
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
create_file_lines 'first' 'text'
create_file_lines 'second' 'second'
create_file_lines 'third' 'third'
run_bmake 'testcase.mk' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in first second third' \
'warning: [subst.mk:class] Nothing changed in "first".' \
'warning: [subst.mk:class] Nothing changed in "second".' \
'warning: [subst.mk:class] Nothing changed in "third".' \
'fail: [subst.mk:class] The filename patterns "first second third" have no effect.' \
'*** Error code 1' \
'' \
'Stop.' \
"$make: stopped in $PWD"
assert_that "$exitcode" --equals '1'
test_case_end
fi
if test_case_begin 'multiple no-op files, all are reported at once, no-op ok'; then
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= first second third
SUBST_SED.class= -e 's,from,to,'
SUBST_NOOP_OK.class= yes
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
create_file_lines 'first' 'text'
create_file_lines 'second' 'second'
create_file_lines 'third' 'third'
run_bmake 'testcase.mk' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in first second third' \
'info: [subst.mk:class] Nothing changed in "first".' \
'info: [subst.mk:class] Nothing changed in "second".' \
'info: [subst.mk:class] Nothing changed in "third".'
assert_that "$exitcode" --equals '0'
test_case_end
fi
if test_case_begin 'late evaluation of SUBST_FILES'; then
# Ensure that SUBST_FILES is evaluated as late as possible.
# Evaluating it early breaks cases like pkglocaledir where the
# list of files is generated by a shell command.
# See mk/configure/replace-localedir.mk.
# If SUBST_FILES were to be evaluated early, it would expand to
# "too-early", since that file exists when testcase.mk is parsed,
# as opposed to "first" "second" "third", which only exist when
# the subst-class target is run.
create_file 'testcase.mk' <<EOF
REPLACE_FILES_CMD.class= \
cd \${WRKSRC} && echo *r*
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= first third # \${REPLACE_FILES_CMD.class:sh}
SUBST_SED.class= -e 's,from,to,'
.include "prepare-subst.mk"
RUN= @set -e;
WRKSRC= \${WRKDIR}/package-1.0
.include "mk/subst.mk"
# It is tricky that the dependency must use an internal implementation
# detail, but that's the only way to guarantee the correct ordering.
\${_SUBST_COOKIE.class}: prepare-subst-class
prepare-subst-class:
\${RUN} \${MKDIR} \${WRKSRC}
\${RUN} \${RM} '\${WRKSRC}/too-early'
\${RUN} \${ECHO} 'from' > '\${WRKSRC}/first'
\${RUN} \${ECHO} 'from' > '\${WRKSRC}/second'
\${RUN} \${ECHO} 'from' > '\${WRKSRC}/third'
EOF
create_file_lines 'package-1.0/too-early' \
'from'
run_bmake 'testcase.mk' 'subst-class' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in first third'
assert_that "package-1.0/first" --file-is-lines 'to'
assert_that "package-1.0/second" --file-is-lines 'from'
assert_that "package-1.0/third" --file-is-lines 'to'
assert_that "$exitcode" --equals '0'
test_case_end
fi
if test_case_begin 'special characters in filenames'; then
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= *
SUBST_SED.class= -e s,before,after,
SUBST_NOOP_OK.class= yes
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
create_file_lines ' !"#$%&'\''()*+,-.' 'before'
create_file_lines '0123456789:;<=>?' 'before'
create_file_lines '@ABCDEFGHIJKLMNO' 'before'
create_file_lines 'PQRSTUVWXYZ[\]^_' 'before'
create_file_lines '`abcdefghijklmno' 'before'
create_file_lines 'pqrstuvwxyz{|}~' 'before'
create_file_lines '--no-option' 'before'
create_file_lines '.hidden' 'before'
run_bmake 'testcase.mk' 'subst-class' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in *' \
'info: [subst.mk:class] Nothing changed in "prepare-subst.mk".'
assert_that ' !"#$%&'\''()*+,-.' --file-is-lines 'after'
assert_that '0123456789:;<=>?' --file-is-lines 'after'
assert_that '@ABCDEFGHIJKLMNO' --file-is-lines 'after'
assert_that 'PQRSTUVWXYZ[\]^_' --file-is-lines 'after'
assert_that '`abcdefghijklmno' --file-is-lines 'after'
assert_that 'pqrstuvwxyz{|}~' --file-is-lines 'after'
assert_that '--no-option' --file-is-lines 'after'
assert_that '.hidden' --file-is-lines 'before'
test_case_end
fi
if test_case_begin 'brackets in filename patterns'; then
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= class
SUBST_STAGE.class= pre-configure
SUBST_FILES.class= [*]
SUBST_SED.class= -e s,before,after,
SUBST_NOOP_OK.class= yes
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
create_file_lines 'any' 'before'
create_file_lines 'x' 'before'
create_file_lines '*' 'before'
create_file_lines '[*]' 'before'
run_bmake 'testcase.mk' 'subst-class' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "class" in [*]'
assert_that "$exitcode" --equals '0'
assert_that 'any' --file-is-lines 'before'
assert_that 'x' --file-is-lines 'before'
assert_that '*' --file-is-lines 'after'
assert_that '[*]' --file-is-lines 'before'
test_case_end
fi
if test_case_begin 'duplicate SUBST class'; then
create_file 'testcase.mk' <<EOF
SUBST_CLASSES+= one
SUBST_CLASSES+= two
SUBST_CLASSES+= one
all:
@printf 'fail reason: %s\n' \${PKG_FAIL_REASON} 1>&2
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
run_bmake 'testcase.mk' 'all' \
1> "$tmpdir/stdout" \
2> "$tmpdir/stderr" \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/stdout" --file-is-empty
assert_that "$tmpdir/stderr" --file-is-lines \
'fail reason: [subst.mk] duplicate SUBST class in: one one two'
assert_that "$exitcode" --equals '0'
test_case_end
fi
if test_case_begin 'several SUBST classes'; then
# It's ok to have several SUBST classes that apply to the same file.
# The order of execution is not guaranteed though.
create_file_lines 'file' 'zero one two three four'
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= one
SUBST_STAGE.one= pre-configure
SUBST_FILES.one= file
SUBST_SED.one= -e 's,one,I,'
SUBST_CLASSES+= two
SUBST_STAGE.two= pre-configure
SUBST_FILES.two= file
SUBST_SED.two= -e 's,two,II,'
SUBST_CLASSES+= three
SUBST_STAGE.three= pre-configure
SUBST_FILES.three= file
SUBST_SED.three= -e 's,three,III,'
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
run_bmake 'testcase.mk' 'pre-configure' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
# The order of the above output is not guaranteed.
LC_ALL=C sort < "$tmpdir/output" > "$tmpdir/output-sorted"
assert_that 'file' --file-is-lines 'zero I II III four'
assert_that "$tmpdir/output-sorted" --file-is-lines \
'=> Substituting "one" in file' \
'=> Substituting "three" in file' \
'=> Substituting "two" in file'
test_case_end
fi
if test_case_begin 'show diff'; then
create_file_lines 'file' 'one' 'two' 'three'
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= two
SUBST_STAGE.two= pre-configure
SUBST_FILES.two= file
SUBST_SED.two= -e 's,two,II,'
SUBST_SHOW_DIFF.two= yes
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
LC_ALL=C \
run_bmake 'testcase.mk' 'pre-configure' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
awk '{ if (/^(---|\+\+\+) /) { print $1 " " $2 " (filtered timestamp)" } else { print $0 } }' \
< "$tmpdir/output" > "$tmpdir/output-filtered"
assert_that 'file' --file-is-lines 'one' 'II' 'three'
assert_that "$tmpdir/output-filtered" --file-is-lines \
'=> Substituting "two" in file' \
'--- file (filtered timestamp)' \
'+++ file.subst.sav (filtered timestamp)' \
'@@ -1,3 +1,3 @@' \
' one' \
'-two' \
'+II' \
' three'
test_case_end
fi
if test_case_begin 'global show diff'; then
create_file_lines 'file' \
'one' \
'two' \
'three'
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= two
SUBST_STAGE.two= pre-configure
SUBST_FILES.two= file
SUBST_SED.two= -e 's,two,II,'
SUBST_SHOW_DIFF= yes
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
run_bmake 'testcase.mk' 'pre-configure' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
awk '{ if (/^(---|\+\+\+) /) { print $1 " " $2 " (filtered timestamp)" } else { print $0 } }' \
< "$tmpdir/output" > "$tmpdir/output-filtered"
assert_that 'file' --file-is-lines \
'one' \
'II' \
'three'
assert_that "$tmpdir/output-filtered" --file-is-lines \
'=> Substituting "two" in file' \
'--- file (filtered timestamp)' \
'+++ file.subst.sav (filtered timestamp)' \
'@@ -1,3 +1,3 @@' \
' one' \
'-two' \
'+II' \
' three'
test_case_end
fi
if test_case_begin 'SUBST_VARS'; then
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= vars' \
'SUBST_STAGE.vars= pre-configure' \
'SUBST_FILES.vars= vars.txt' \
'SUBST_VARS.vars= PLAIN DQUOT SQUOT DELIM PRINTABLE' \
'SUBST_VARS.vars+= UNDEFINED' \
'' \
'PLAIN= plain' \
'DQUOT= "double quoted"' \
'SQUOT= '\''single quoted'\''' \
'DELIM= hello, world' \
'PRINTABLE= !"\#$$%&'\''()*+,-./09:;<=>?@AZ[\]^_`az{|}' \
'#UNDEFINED= # undefined' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'vars.txt' \
'@PLAIN@' \
'@DQUOT@' \
'@SQUOT@' \
'@DELIM@' \
'@PRINTABLE@' \
'@UNDEFINED@'
run_bmake 'testcase.mk' 'pre-configure' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
# The double quotes and single quotes are kept since the variables
# are treated as simple string variables, not as lists of shell
# words. In these string variables, the quotes are part of the value.
assert_that 'vars.txt' --file-is-lines \
'plain' \
'"double quoted"' \
"'single quoted'" \
'hello, world' \
'!"#$%&'\''()*+,-./09:;<=>?@AZ[\]^_`az{|}' \
''
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "vars" in vars.txt'
test_case_end
fi
if test_case_begin 'SUBST_VARS with surrounding whitespace'; then
# Ensure that leading and trailing whitespace is preserved
# in the variable values.
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= vars' \
'SUBST_STAGE.vars= pre-configure' \
'SUBST_FILES.vars= vars.txt' \
'SUBST_VARS.vars= SPACE TAB NEWLINE' \
'' \
'SPACE= ${:U }between spaces${:U }' \
'TAB= ${:U }between tabs${:U }' \
'NEWLINE= ${.newline}between newlines${.newline}' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'vars.txt' \
'@SPACE@' \
'@TAB@' \
'@NEWLINE@'
run_bmake 'testcase.mk' 'pre-configure' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
space=' '
tab=' '
newline='
'
assert_that 'vars.txt' --file-is-lines \
"$space"'between spaces'"$space" \
"$tab"'between tabs'"$tab" \
"$newline"'between newlines'"$newline"
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "vars" in vars.txt'
test_case_end
fi
if test_case_begin 'SUBST_VARS with backslashes'; then
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= bs' \
'SUBST_STAGE.bs= pre-configure' \
'SUBST_FILES.bs= backslash.txt' \
'SUBST_VARS.bs= BACKSLASHES' \
'' \
'BACKSLASHES= \" \, \\, \" \'\'' \0\000 \x40 \089 \a \$$' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'backslash.txt' '@BACKSLASHES@'
run_bmake 'testcase.mk' 'pre-configure' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that 'backslash.txt' --file-is-lines \
'\" \, \\, \" \'\'' \0\000 \x40 \089 \a \$'
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "bs" in backslash.txt'
test_case_end
fi
if test_case_begin 'SUBST_VARS for variables with regex characters'; then
# Ensure that special regex characters like dots and parentheses
# may appear in variable names and are properly escaped.
# Variable names containing a dollar are not supported.
# Bmake behaves very surprisingly when a $ is expanded inside a :C
# modifier. Nobody needs this feature anyway, it was just an
# experiment to see whether this would be theoretically possible.
# Variable names ending with a backslash are not supported.
# The backslash may only occur in the middle of the variable name.
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= vars' \
'SUBST_STAGE.vars= pre-configure' \
'SUBST_FILES.vars= vars.txt' \
'SUBST_VARS.vars= VAR...... VAR.abcde' \
'SUBST_VARS.vars+= VAR.() VAR.<> VAR.[]' \
'SUBST_VARS.vars+= VAR.$$x VAR.^ VAR.\x' \
'' \
'VAR......= dots' \
'VAR.abcde= letters' \
'VAR.()= parentheses' \
'VAR.<>= angle brackets' \
'VAR.[]= square brackets' \
'VAR.$$x= dollar' \
'VAR.^= circumflex' \
'VAR.\x= backslash' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'vars.txt' \
'@VAR......@' \
'@VAR.abcde@' \
'@VAR.()@' \
'@VAR.<>@' \
'@VAR.[]@' \
'@VAR.$x@' \
'@VAR.^@' \
'@VAR.\x@'
run_bmake 'testcase.mk' 'pre-configure' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that 'vars.txt' --file-is-lines \
'dots' \
'letters' \
'parentheses' \
'angle brackets' \
'square brackets' \
'@VAR.$x@' \
'circumflex' \
'backslash'
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "vars" in vars.txt'
test_case_end
fi
if test_case_begin 'pattern matches directory'; then
# When a pattern matches a directory, that directory is silently
# skipped.
#
# In this test case, the pattern also matches a regular file that
# is actually modified. Therefore the pattern has an effect, and
# there is no error message.
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= dir' \
'SUBST_STAGE.dir= pre-configure' \
'SUBST_FILES.dir= sub*' \
'SUBST_VARS.dir= VAR' \
'SUBST_NOOP_OK.dir= no' \
'' \
'VAR= value' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'subdir/subfile' \
'@VAR@'
create_file_lines 'subst-file' \
'@VAR@'
run_bmake 'testcase.mk' 'pre-configure' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that 'subst-file' --file-is-lines 'value'
assert_that 'subdir/subfile' --file-is-lines '@VAR@' # unchanged
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "dir" in sub*'
test_case_end
fi
if test_case_begin 'pattern matches only directory'; then
# When a pattern matches a directory, that directory is silently
# skipped.
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= dir' \
'SUBST_STAGE.dir= pre-configure' \
'SUBST_FILES.dir= sub*' \
'SUBST_VARS.dir= VAR' \
'SUBST_NOOP_OK.dir= no' \
'' \
'VAR= value' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'subdir/subfile' \
'@VAR@'
run_bmake 'testcase.mk' 'pre-configure' \
1> "$tmpdir/stdout" \
2> "$tmpdir/stderr" \
&& exitcode=0 || exitcode=$?
assert_that 'subdir/subfile' --file-is-lines '@VAR@' # unchanged
assert_that "$tmpdir/stdout" --file-is-lines \
'=> Substituting "dir" in sub*' \
'*** Error code 1' \
'' \
'Stop.' \
"$make: stopped in $PWD"
assert_that "$tmpdir/stderr" --file-is-lines \
'fail: [subst.mk:dir] The filename pattern "sub*" has no effect.'
assert_that "$exitcode" --equals 1
test_case_end
fi
if test_case_begin 'two filename patterns have no effect'; then
# All patterns of SUBST_FILES should be applied before erroring out,
# to give a complete picture of the situation.
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= id' \
'SUBST_STAGE.id= pre-configure' \
'SUBST_FILES.id= file1 file2' \
'SUBST_VARS.id= A B' \
'SUBST_NOOP_OK.id= no' \
'A= a-value' \
'B= b-value' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'file1' 'nothing to replace'
create_file_lines 'file2' 'nothing to replace'
run_bmake 'testcase.mk' 'pre-configure' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "id" in file1 file2' \
'warning: [subst.mk:id] Nothing changed in "file1".' \
'warning: [subst.mk:id] Nothing changed in "file2".' \
'fail: [subst.mk:id] The filename patterns "file1 file2" have no effect.' \
'*** Error code 1' \
'' \
'Stop.' \
"$make: stopped in $PWD"
test_case_end
fi
if test_case_begin 'empty SUBST_FILES'; then
# An empty SUBST_FILES section is ok.
# It may have been produced by a shell command like find(1).
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= id' \
'SUBST_STAGE.id= pre-configure' \
'SUBST_FILES.id= # none' \
'SUBST_SED.id= -e s,from,to,' \
'SUBST_NOOP_OK.id= no' \
'' \
'all:' \
' @printf "%s\n" ${PKG_FAIL_REASON:Uok}' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
run_bmake 'testcase.mk' 'pre-configure' 'all' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "id" in ' \
'ok'
test_case_end
fi
if test_case_begin 'empty SUBST_SED'; then
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= id' \
'SUBST_STAGE.id= pre-configure' \
'SUBST_FILES.id= file' \
'SUBST_SED.id= # none' \
'SUBST_NOOP_OK.id= no' \
'' \
'all:' \
' @printf "%s\n" ${PKG_FAIL_REASON:Uok}' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
run_bmake 'testcase.mk' 'subst-id' 'all' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "id" in file' \
'warning: [subst.mk:id] Ignoring nonexistent file "file".' \
'fail: [subst.mk:id] The filename pattern "file" has no effect.' \
'*** Error code 1' \
'' \
'Stop.' \
"$make: stopped in $PWD"
test_case_end
fi
if test_case_begin 'typo in SUBST_CLASSES'; then
# Look closely. The SUBST_CLASSES line contains a typo.
# subst.mk does not catch this, but pkglint does.
create_file_lines 'testcase.mk' \
'SUBST_CLASSES=+ id' \
'SUBST_STAGE.id= pre-configure' \
'SUBST_FILES.id= file' \
'SUBST_SED.id= # none' \
'SUBST_NOOP_OK.id= no' \
'' \
'all:' \
' @printf "%s\n" ${PKG_FAIL_REASON:Uok}' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
run_bmake 'testcase.mk' 'pre-configure' 'all' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "id" in file' \
'warning: [subst.mk:id] Ignoring nonexistent file "file".' \
'fail: [subst.mk:id] The filename pattern "file" has no effect.' \
'*** Error code 1' \
'' \
'Stop.' \
"$make: stopped in $PWD"
test_case_end
fi
if test_case_begin 'executable bit is preserved'; then
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= id' \
'SUBST_STAGE.id= pre-configure' \
'SUBST_FILES.id= cmd data' \
'SUBST_VARS.id= VAR' \
'VAR= replaced' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'cmd' \
'@VAR@'
create_file_lines 'data' \
'@VAR@'
chmod +x 'cmd'
run_bmake 'testcase.mk' 'pre-configure' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "id" in cmd data'
assert_that 'cmd' --file-is-lines 'replaced'
assert_that 'data' --file-is-lines 'replaced'
[ -x 'cmd' ] \
|| assert_fail 'cmd must still be executable\n'
[ -x 'data' ] \
&& assert_fail 'data must not be executable\n'
test_case_end
fi
if test_case_begin 'unreadable file'; then
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= id' \
'SUBST_STAGE.id= pre-configure' \
'SUBST_FILES.id= unreadable-file' \
'SUBST_SED.id= -e s,before,after,' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'unreadable-file' \
'before'
chmod 0000 'unreadable-file'
run_bmake 'testcase.mk' 'pre-configure' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "id" in unreadable-file' \
'sh: cannot open unreadable-file: permission denied' \
'sh: cannot open unreadable-file: permission denied' \
'*** Error code 1' \
'' \
'Stop.' \
"$make: stopped in $PWD"
test_case_end
fi
if test_case_begin 'identity substitution implementation'; then
assert_identity() {
_ai_expected="$1"; shift
awk -f "$pkgsrcdir/mk/scripts/subst-identity.awk" -- ${1+"$@"} \
&& _ai_actual='yes' || _ai_actual='no'
[ "$_ai_actual" = "$_ai_expected" ] \
|| assert_fail 'expected "%s", got "%s" for %s\n' "$_ai_expected" "$_ai_actual" "$*"
}
# If there is no SUBST_SED at all, this is not the situation
# that is targeted by this test for identity substitution.
assert_identity 'no' # no substitutions at all
# Even though this is an identity substitution, it is missing
# the -e option and thus does not follow the usual format.
# Therefore it is considered just a normal substitution.
assert_identity 'no' 's,from,from,'
# The following are typical identity substitutions.
# It does not matter whether the g modifier is there or not.
# Unknown modifiers are not allowed though.
assert_identity 'yes' -e 's,from,from,'
assert_identity 'yes' -e 's;from;from;'
assert_identity 'yes' -e 's,from,from,g'
assert_identity 'no' -e 's,from,from,gunknown'
# The identity substitution may include characters other than
# A-Za-z0-9, but no characters that have a special meaning in
# basic regular expressions.
assert_identity 'yes' -e 's,/dev/audio,/dev/audio,'
assert_identity 'yes' -e 's!/dev/audio!/dev/audio!'
# There may be several identity substitutions in the same
# SUBST_SED. As long as any of these substitutions is an
# identity substitution, it can happen that a file stays the
# same. It still might be modified in a different pkgsrc
# configuration.
assert_identity 'yes' -e 's;from;from;' -e 's!second!second!'
assert_identity 'yes' -e 's,changing,x,' -e 's,id,id,'
assert_identity 'yes' -e 's,id,id,' -e 's,changing,x,'
assert_identity 'no' -e 's,changing,x,' -e 's,changing,x,'
# A demonstration of all ASCII characters that may appear in an
# identity substitution.
#
# The # and $ are excluded since they are interpreted specially
# in Makefiles and would thus be confusing to the human reader.
#
# The characters *.?[\]^ have a special meaning in the pattern of the
# substitution.
# The & has a special meaning in the replacement of the
# substitution.
specials='!"%'\''()+,-/:;<=>@_`{|}~'
assert_identity 'yes' -e "sX${specials}X${specials}X"
# Regular expression meta-characters may be escaped using a
# backslash or be enclosed in square brackets.
assert_identity 'yes' -e 's,library\.so,library.so,g'
assert_identity 'yes' -e 's,library[.]so,library.so,g'
assert_identity 'yes' -e 's,[*],*,'
assert_identity 'yes' -e 's,[$],$,'
# When this happens, it is probably a mistake.
assert_identity 'no' -e 's,,,'
# Backslashes are not considered identity substitutions since
# there might be tricky corner cases.
assert_identity 'no' -e 's,\\,\\,'
# Back-references are not considered identity substitutions.
assert_identity 'no' -e 's,\1,\1,'
# The & is interpreted specially in the replacement string.
assert_identity 'no' -e 's,&&&,&&&,'
assert_identity 'no' -e 's,\&,&,'
assert_identity 'no' -e 's,[&],&,'
assert_identity 'no' -e 's,&,\&,' # this would be an identity
# References to Makefile variables can be identical too.
# See converters/help2man for an example.
assert_identity 'yes' -e 's,\$(var),$(var),'
# POSIX 2004 and 2018 both define in section "9.3.8 BRE Expression
# Anchoring" that a dollar-sign at the end of the string means
# end-of-string.
#
# A dollar-sign followed by \) may or may not be an anchor.
# In all other cases the dollar is an ordinary character.
assert_identity 'yes' -e 's,$(var),$(var),'
# Since this dollar-sign may or may not be an anchor, treat the
# whole regular expression as not-an-identity.
#
# Since a regular expression with a subexpression must contain
# \( and \), it does not count as an identity substitution anyway,
# which makes the implementation simple.
assert_identity 'no' -e 's,aaa\(aaa$\),aaa\(aaa$\),'
assert_identity 'yes' -e 's,$a,$a,'
assert_identity 'no' -e 's,a$,a$,'
# Same for the circumflex.
assert_identity 'yes' -e 's,a^,a^,'
assert_identity 'no' -e 's,^a,^a,'
assert_identity 'no' -e 's,\(^aaa\)aaa,\(^aaa\)aaa,'
# Seen in games/bastet before 2020-05-16.
assert_identity 'yes' -e 's,a,a,1'
test_case_end
fi
if test_case_begin 'identity substitution, found in file'; then
# There are many situations in which a fixed text is replaced
# with a dynamic value that may or may not be equal to the
# original text.
#
# Typical examples are s|man|${PKGMANDIR}|, s|/usr/pkg|${PREFIX}|,
# s|/dev/audio|${DEVOSSAUDIO}|.
#
# It is not an error if these substitutions result in a no-op,
# provided that the text is actually found in the file.
#
# Alternatives for this special exception would be:
#
# 1. Mark these blocks as SUBST_NOOP_OK. This would not detect
# outdated definitions. Since this detection is the main goal
# of SUBST_NOOP_OK, this is out of the question.
#
# 2. Surround these blocks with a condition like ".if ${VAR} !=
# fixed-value ... .endif". This pattern only works if VAR is
# definitely assigned, which often requires a corresponding
# .include line, leading to code bloat. It would also mean that
# variables defined in bsd.pkg.mk could not be used in SUBST
# blocks like these.
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= id' \
'SUBST_FILES.id= file' \
'SUBST_SED.id= -e s,before,before,' \
'SUBST_SED.id+= -e "s,before,before,"' \
"SUBST_SED.id+= -e 's,before,before,'" \
'SUBST_NOOP_OK.id= no' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'file' \
'before'
run_bmake 'testcase.mk' 'subst-id' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "id" in file'
if [ -f "file.subst.sav" ]; then
assert_fail 'temporary file still exists\n'
fi
test_case_end
fi
if test_case_begin 'identity substitution, not found in file'; then
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= id' \
'SUBST_FILES.id= file' \
'SUBST_SED.id= s,before,before,' \
'SUBST_NOOP_OK.id= no' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'file' \
'other'
run_bmake 'testcase.mk' 'subst-id' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "id" in file' \
'warning: [subst.mk:id] Nothing changed in "file".' \
'fail: [subst.mk:id] The filename pattern "file" has no effect.' \
'*** Error code 1' \
'' \
'Stop.' \
"$make: stopped in $PWD"
test_case_end
fi
if test_case_begin 'identity + effective substitution'; then
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= id' \
'SUBST_FILES.id= file' \
'SUBST_SED.id= -e s,no-op,no-op,g' \
'SUBST_SED.id+= -e s,from,to,' \
'SUBST_NOOP_OK.id= no' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'file' \
'from'
run_bmake 'testcase.mk' 'subst-id' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "id" in file'
assert_that 'file' --file-is-lines \
'to'
test_case_end
fi
if test_case_begin 'identity + no-op substitution'; then
# If there were only an identity substitution, it wouldn't be an
# error. But since there is a regular substitution as well,
# that substitution is an unexpected no-op and is therefore
# flagged as an error.
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= id' \
'SUBST_FILES.id= file' \
'SUBST_SED.id= -e s,no-op,no-op,g' \
'SUBST_SED.id+= -e s,from,to,' \
'SUBST_NOOP_OK.id= no' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'file' \
'other'
run_bmake 'testcase.mk' 'subst-id' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "id" in file' \
'warning: [subst.mk:id] Nothing changed in "file".' \
'fail: [subst.mk:id] The filename pattern "file" has no effect.' \
'*** Error code 1' \
'' \
'Stop.' \
"$make: stopped in $PWD"
assert_that 'file' --file-is-lines \
'other'
test_case_end
fi
if test_case_begin 'SUBST_FILTER_CMD + SUBST_SED in NOOP_OK=no mode'; then
# If SUBST_FILTER_CMD is defined for a SUBST class, the
# corresponding SUBST_SED and SUBST_VARS are ignored. To avoid
# redundant variable definitions, this case fails fast.
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= id' \
'SUBST_FILES.id= file' \
'SUBST_FILTER_CMD.id= tr -d "0-9"' \
'SUBST_SED.id= -e s,x,x,' \
'SUBST_NOOP_OK.id= no' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'file' \
'letters 123 letters'
create_file_lines "$tmpdir/main.mk" \
"PKGSRCDIR= $pkgsrcdir" \
".PATH: $mocked_pkgsrcdir" \
".PATH: $pkgsrcdir" \
'.include "testcase.mk"' \
'' \
'all: subst-id' \
' @printf '\''fail reason: %s\n'\'' ${PKG_FAIL_REASON} 1>&2'
"$make" -f "$tmpdir/main.mk" 'all' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "id" in file' \
'fail reason: [subst.mk:id] SUBST_FILTER_CMD and SUBST_SED/SUBST_VARS cannot be combined.'
assert_that 'file' --file-is-lines \
'letters letters'
test_case_end
fi
if test_case_begin 'SUBST_FILTER_CMD + empty SUBST_SED'; then
# If SUBST_FILTER_CMD is defined for a SUBST class,
# the corresponding SUBST_SED and SUBST_VARS are ignored.
# To avoid redundant variable definitions, this case fails fast.
#
# This happens even if SUBST_SED or SUBST_VARS are empty since
# there is no point in setting these to empty values.
# In most cases they are constant and non-empty anyway.
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= id' \
'SUBST_FILES.id= file' \
'SUBST_FILTER_CMD.id= tr -d "0-9"' \
'SUBST_SED.id= # empty' \
'SUBST_VARS.id= # empty' \
'SUBST_NOOP_OK.id= no' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"' \
'.include "show-pkg-fail-reason.mk"'
run_bmake 'testcase.mk' 'show-pkg-fail-reasons' 'subst-id' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'fail reason: [subst.mk:id] SUBST_FILTER_CMD and SUBST_SED/SUBST_VARS cannot be combined.' \
'*** Error code 1' \
'' \
'Stop.' \
"$make: stopped in $PWD"
test_case_end
fi
if test_case_begin 'effective SUBST_FILTER_CMD in NOOP_OK=no mode'; then
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= id' \
'SUBST_FILES.id= file' \
'SUBST_FILTER_CMD.id= tr -d "0-9"' \
'SUBST_NOOP_OK.id= no' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'file' \
'letters 123 letters'
run_bmake 'testcase.mk' 'subst-id' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "id" in file'
assert_that 'file' --file-is-lines \
'letters letters'
test_case_end
fi
if test_case_begin 'no-op SUBST_FILTER_CMD in NOOP_OK=no mode'; then
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= id' \
'SUBST_FILES.id= file' \
'SUBST_FILTER_CMD.id= tr -d "0-9"' \
'SUBST_NOOP_OK.id= no' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'file' \
'only letters'
run_bmake 'testcase.mk' 'subst-id' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "id" in file' \
'warning: [subst.mk:id] Nothing changed in "file".' \
'fail: [subst.mk:id] The filename pattern "file" has no effect.' \
'*** Error code 1' \
'' \
'Stop.' \
"$make: stopped in $PWD"
assert_that 'file' --file-is-lines \
'only letters'
test_case_end
fi
if test_case_begin 'backtick in SUBST_SED'; then
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= id' \
'SUBST_FILES.id= file' \
"SUBST_SED.id= -e 's,\"\\\\\`,\"\\\\\`,'" \
'SUBST_NOOP_OK.id= yes' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'file' \
'from`'
run_bmake 'testcase.mk' 'subst-id' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "id" in file' \
'info: [subst.mk:id] Nothing changed in "file".'
test_case_end
fi
if test_case_begin 'multiple sed commands with semicolon'; then
# From PR pkg/55226:
# ===> Configuring for perl-5.30.2
# sh: 1: Syntax error: Word "/d"p" unexpected (expecting ")")
create_file_lines 'testcase.mk' \
'SUBST_CLASSES+= id' \
'SUBST_FILES.id= file' \
'SUBST_SED.id= -e "s/755/755/g;/umask(/d"' \
'SUBST_NOOP_OK.id= no' \
'' \
'.include "prepare-subst.mk"' \
'.include "mk/subst.mk"'
create_file_lines 'file' \
'$mode = 755;'
run_bmake 'testcase.mk' 'subst-id' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "id" in file' \
'warning: [subst.mk:id] Nothing changed in "file".' \
'fail: [subst.mk:id] The filename pattern "file" has no effect.' \
'*** Error code 1' \
'' \
'Stop.' \
"$make: stopped in $PWD"
test_case_end
fi
if test_case_begin 'several substitution, only one applies to file'; then
# https://mail-index.netbsd.org/pkgsrc-changes/2020/06/06/msg215480.html
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= id
SUBST_FILES.id= first second
SUBST_SED.id= -e 's,first,first-modified,'
SUBST_SED.id+= -e 's,second,second,'
SUBST_NOOP_OK.id= no
.include "prepare-subst.mk"
.include "mk/subst.mk"
EOF
create_file_lines 'first' 'first'
create_file_lines 'second' 'second'
run_bmake 'testcase.mk' 'subst-id' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
# Each of the files contains at least one of the sed patterns,
# therefore the substitutions _could_ have an effect, depending
# on the pkgsrc configuration.
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "id" in first second'
assert_that 'first' --file-contains-exactly 'first-modified'
assert_that 'second' --file-contains-exactly 'second'
test_case_end
fi
if test_case_begin 'identity substitution with newline'; then
# Ensures that the adjusted sed command line in the "found=" line
# in mk/subst.mk does not create shell syntax errors.
# This is the sed command after the "found=" line in mk/subst.mk.
# It tests whether any of the patterns is found.
# It only outputs the actually found lines (-n) by appending a "p"
# to the usual "s,from,to," commands.
mock_sed=$(
newline='
'
args='-n'
# In this "usual" sed command, the "p" is added.
args="$args -e s,identity,identity,p"
# This is considered an "unusual" sed command because of
# the leading 1, therefore no "p" is added.
#
# Ideally this should be considered a "usual" sed command,
# even though it only applies to some of the lines.
# To do this, mk/scripts/subst-identity.awk has to parse
# sed addresses, in addition to substitutions.
args="$args -e 1s,first line,first line,"
# In the Makefile, the additional quotes at the beginning
# make this an "unusual" sed command, and the :C modifier
# in subst.mk doesn't see that after unquoting the word,
# the sed command is quite usual. This is an edge case
# that doesn't occur in practice.
args="$args -e s,unusual,unusual,g"
# No "p" is added since this is not a "usual" substitution.
# If it had been found, the file would have changed anyway,
# and this sed command line would not be executed.
args="$args -e /not found/d"
# Same here. Just make sure that the generated sed command
# line does not lead to a syntax error in the shell.
args="$args -e /not found/a\\${newline}added${newline}"
args="$args file"
mock_cmd 'mock-sed' \
--when-args "$args" --then-output 'identity'
)
create_file 'testcase.mk' <<-EOF
SUBST_CLASSES+= id
SUBST_FILES.id= file
SUBST_SED.id= -e 's,identity,identity,'
SUBST_SED.id+= -e '1s,first line,first line,'
SUBST_SED.id+= -e '''''s,unusual,unusual,g'
SUBST_SED.id+= -e '/not found/d'
SUBST_SED.id+= -e '/not found/a\\\${.newline}added\${.newline}'
# Use the standard sed for the main part.
SUBST_FILTER_CMD.id= LC_ALL=C sed \${SUBST_SED.id}
.include "prepare-subst.mk"
# Use the mocked sed for the "found=" part.
SED= $mock_sed
.include "mk/subst.mk"
# ignore PKG_FAIL_REASON (SUBST_SED + SUBST_FILTER_CMD)
EOF
create_file 'file' <<-EOF
identity
EOF
run_bmake 'testcase.mk' 'subst-id' 1> "$tmpdir/output" 2>&1 \
&& exitcode=0 || exitcode=$?
assert_that "$tmpdir/output" --file-is-lines \
'=> Substituting "id" in file'
assert_that 'file' --file-is-lines \
'identity'
test_case_end
fi