The shell translator rewrites test bodies and loses Bats reporting.
The unit lane needs complete TAP results and JUnit reports without
changing how live-cluster suites run.
Sourced production helpers still need a dash pass, and unit test bodies
need nounset without exporting shell options into subprocesses.
Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
`.PHONY=a b c` and `.PHONY: a b c` differ in the `=` against the
`:`, and only the second declares anything. The first defines an
ordinary variable named `.PHONY` that nothing reads, leaving every
listed target file-backed. A file or directory of that name beside a
package Makefile then stands in as the product of the rule: for a
target with no prerequisites make reports it up to date, skips the
recipe and exits 0, and a target whose prerequisite is itself
file-less keeps rebuilding until that prerequisite is shadowed too.
The neighbouring `.DEFAULT_GOAL=help` is a real variable and is
correct with `=`, so the two lines read alike while only one of them
works, which is what let the inert form sit there unnoticed.
Add hack/package-mk-phony.bats, six tests, picked up automatically
by the hack/*.bats glob in `bats-unit-tests`.
Three cover the declaration itself. The first reads the names off
the `.PHONY:` line rather than repeating them, plants a file named
after each beside a stub Makefile, and requires `make --dry-run` to
print the recipe instead of "is up to date". A negative control
mutates a copy back to the assignment form and requires the probe to
report up-to-date, over the targets package.mk defines rather than
the names it declares, because "is up to date" is only a meaningful
answer for a name that has a rule. The third keeps those two sets
equal: it requires the declared list and the defined targets to be
the same set. Defined but undeclared
is the direction the list rots in: check, clean, suspend and resume
sat undeclared for as long as the declaration itself was inert.
Declared but undefined is the empty-target trap in its general
form, so a future name cannot slip in the way update and image
once did.
Two more pin what the list deliberately leaves out. package.mk
defines neither `update` nor `image`; the including package Makefile
does, and most packages define neither. Naming them here would
create them as empty targets everywhere, turning "No rule to make
target" into "Nothing to be done". Since the CI build matrix is
parsed from the root Makefile `build:` target and runs
`make -C <pkg> image` per unit, a package reaching that list without
an `image:` rule would go from a red job to a green one that builds
no image. One test requires that loud failure to survive, pinning
both of the observables it is made of: make's exit status 2 and its
diagnostic. They are independent, and a wrapper that kept the
wording while returning something else would satisfy a check on the
message alone. Its control extends the declaration with the two
names in a copy and requires the failure to disappear, so the pin
tracks the list rather than some property of the harness.
Every decoy carries one fixed timestamp. Six of the nine targets take
check as a prerequisite, and apply and delete reach it through suspend
as well, so touching the decoys in list order can leave check newer
than its own dependents. Make then rebuilds them for that reason and
prints a recipe whatever .PHONY says, and the probe reports success
against a declaration that does nothing. Equal mtimes remove the
question in any list order. Measured on GNU Make 4.3: list order left
the per-target assertion inert for up to six of the nine, moving with
where the clock tick fell, while 3.81 fits the loop inside a single
timestamp and shows none of it. The control test plants its decoys
the same way for the same reason, and goes red if that is undone in
its own loop. The two loops are independent, so neither guards the
other, and the header is what keeps the arrangement from being
unpicked.
A sixth test leaves package.mk and covers the root Makefile, where this
class had already bitten: `test` is the one target in the tree with a
path of its own name beside it. It probes with --dry-run because the
recipe wants a live cluster, and fails loudly if that path ever goes
away rather than passing on a question with no subject.
`%-update` is not coverable: .PHONY matches target names literally
and does not expand patterns.
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>