Summary
In image mode (package manager: bootc), a test/plan requirement specified as a file path (e.g. require: /usr/bin/go) is silently dropped when the path is not already present on the booted image. tmt neither bakes the providing package into the derived container image nor reports an error — the prepare step reports success, and the test only fails later at runtime when it tries to use the missing dependency.
Requirements given as a package name or a capability work fine; only file-path requirements are affected, and only in image mode.
Reproducer
A plan that requires a file path which is not part of the base image, run against a bootc image-mode guest:
prepare:
- how: install
package:
- /usr/bin/go # provided by the (not-installed) golang package
Provisioned against Fedora-44-image-mode-x86_64 (is image mode: yes, package manager: bootc, bootc builder: dnf5), tmt 1.75.0.
Observed (from tmt run -dddvvv)
prepare task #2: requires on default-0
package: 1 package requested
/usr/bin/go
cmd: rpm -q --whatprovides /usr/bin/go
stderr: error: file /usr/bin/go: No such file or directory
Command returned '1' (failure).
No Containerfile directives to build container image, skipping build.
...
summary: 2 preparations applied # <-- reported as success
The package is never installed. The test then fails:
inner wrapper: set -eo pipefail; rpm -qf /usr/bin/go
cmd: rpm -qf /usr/bin/go
stdout: error: file /usr/bin/go: No such file or directory
Command returned '1' (failure).
Compare with /usr/bin/flock in the same run, which is present — rpm -q --whatprovides /usr/bin/flock returns util-linux-core-..., so it is correctly treated as already installed.
Expected
The package providing /usr/bin/go (golang) should be installed into the derived image (added as a RUN dnf install … Containerfile directive), exactly as it would be for require: golang. If it genuinely cannot be provided, prepare should fail loudly, not silently succeed.
Root cause
Two compounding defects, both in tmt/package_managers/bootc.py.
1. Bootc.check_presence does not classify an absent file path as "absent"
Bootc.check_presence runs rpm -q --whatprovides <path> and parses the result. For an absent file path, rpm writes
error: file /usr/bin/go: No such file or directory
to stderr (with empty stdout) and exits 1. The parser only inspects stdout and only matches package … is not installed / no package provides …:
https://github.com/teemtee/tmt/blob/HEAD/tmt/package_managers/bootc.py#L212-L240
So the installable gets no entry in the returned presence dict.
Note this differs from Dnf.check_presence, which gained a third matcher for file paths in e99abfd (#3791):
https://github.com/teemtee/tmt/blob/HEAD/tmt/package_managers/dnf.py#L330-L336
…but even that matcher inspects stdout, while rpm writes the error: file … line to stderr — so it does not actually fire for absent paths either. (For the classic dnf path this is harmless: the install is a shell rpm -q --whatprovides X || dnf install -y X, so the || short-circuit installs X regardless of the Python-side presence parsing. In image mode the install is gated by the Python check_presence result, so the parsing bug becomes load-bearing.)
2. Bootc.install derives "missing" from the presence dict, masking #1
presence = self.check_presence(*installables)
missing_installables = {
installable for installable, present in presence.items() if not present
}
https://github.com/teemtee/tmt/blob/HEAD/tmt/package_managers/bootc.py#L327-L341
Because it iterates presence.items() rather than the requested installables, any installable absent from the dict (which is exactly what #1 produces for a missing file path) is silently treated as not missing → nothing is installed → "No Containerfile directives to build container image, skipping build."
This regressed in #4131 (518e15b), which changed
installable for installable in installables if not presence[installable]
to
installable for installable, present in presence.items() if not present
The original form would have raised KeyError (a loud failure) for a missing file path; the new form silently drops it.
Proposed fix
Make Bootc.check_presence robust to the stderr/empty-stdout case, and make Bootc.install fail-safe by treating "unknown" as "missing":
-
check_presence — search the combined stdout + stderr for per-installable absence markers, and default to present only when no absence marker is found, iterating the requested installables rather than relying on positional zip() alignment (which breaks as soon as one item errors to stderr while another prints to stdout):
def check_presence(self, *installables):
try:
output = self.guest.execute(self.engine.check_presence(*installables))
combined = f"{output.stdout or ''}\n{output.stderr or ''}"
except RunError as exc:
combined = f"{exc.stdout or ''}\n{exc.stderr or ''}"
results: dict[Installable, bool] = {}
for installable in installables:
name = re.escape(str(installable))
absent = (
re.search(rf'package {name} is not installed', combined)
or re.search(rf'no package provides {name}', combined)
or re.search(rf'error: file {name}: No such file or directory', combined)
)
results[installable] = absent is None
return results
-
install — never silently drop a requested installable:
missing_installables = {i for i in installables if not presence.get(i, False)}
-
Add regression coverage for an absent file-path requirement in image mode (the existing check_presence/install_filesystempath tests only exercise file paths that are already present, e.g. /usr/bin/arch, /usr/bin/flock, so this case is currently untested). The same stdout-vs-stderr point should be considered for the Dnf.check_presence file-path matcher.
Environment
- tmt: 1.75.0
- guest:
Fedora-44-image-mode-x86_64.qcow2 (quay.io/testing-farm/fedora-bootc:44), provisioned with how: virtual
package manager: bootc, bootc builder: dnf5, is image mode: yes
Generated-by: Claude Code
Summary
In image mode (
package manager: bootc), a test/plan requirement specified as a file path (e.g.require: /usr/bin/go) is silently dropped when the path is not already present on the booted image. tmt neither bakes the providing package into the derived container image nor reports an error — thepreparestep reports success, and the test only fails later at runtime when it tries to use the missing dependency.Requirements given as a package name or a capability work fine; only file-path requirements are affected, and only in image mode.
Reproducer
A plan that requires a file path which is not part of the base image, run against a bootc image-mode guest:
Provisioned against
Fedora-44-image-mode-x86_64(is image mode: yes,package manager: bootc,bootc builder: dnf5), tmt 1.75.0.Observed (from
tmt run -dddvvv)The package is never installed. The test then fails:
Compare with
/usr/bin/flockin the same run, which is present —rpm -q --whatprovides /usr/bin/flockreturnsutil-linux-core-..., so it is correctly treated as already installed.Expected
The package providing
/usr/bin/go(golang) should be installed into the derived image (added as aRUN dnf install …Containerfile directive), exactly as it would be forrequire: golang. If it genuinely cannot be provided,prepareshould fail loudly, not silently succeed.Root cause
Two compounding defects, both in
tmt/package_managers/bootc.py.1.
Bootc.check_presencedoes not classify an absent file path as "absent"Bootc.check_presencerunsrpm -q --whatprovides <path>and parses the result. For an absent file path,rpmwritesto stderr (with empty stdout) and exits
1. The parser only inspectsstdoutand only matchespackage … is not installed/no package provides …:https://github.com/teemtee/tmt/blob/HEAD/tmt/package_managers/bootc.py#L212-L240
So the installable gets no entry in the returned
presencedict.Note this differs from
Dnf.check_presence, which gained a third matcher for file paths in e99abfd (#3791):https://github.com/teemtee/tmt/blob/HEAD/tmt/package_managers/dnf.py#L330-L336
…but even that matcher inspects
stdout, whilerpmwrites theerror: file …line to stderr — so it does not actually fire for absent paths either. (For the classicdnfpath this is harmless: the install is a shellrpm -q --whatprovides X || dnf install -y X, so the||short-circuit installsXregardless of the Python-side presence parsing. In image mode the install is gated by the Pythoncheck_presenceresult, so the parsing bug becomes load-bearing.)2.
Bootc.installderives "missing" from the presence dict, masking #1https://github.com/teemtee/tmt/blob/HEAD/tmt/package_managers/bootc.py#L327-L341
Because it iterates
presence.items()rather than the requestedinstallables, any installable absent from the dict (which is exactly what #1 produces for a missing file path) is silently treated as not missing → nothing is installed → "No Containerfile directives to build container image, skipping build."This regressed in #4131 (518e15b), which changed
to
The original form would have raised
KeyError(a loud failure) for a missing file path; the new form silently drops it.Proposed fix
Make
Bootc.check_presencerobust to the stderr/empty-stdout case, and makeBootc.installfail-safe by treating "unknown" as "missing":check_presence— search the combined stdout + stderr for per-installable absence markers, and default to present only when no absence marker is found, iterating the requested installables rather than relying on positionalzip()alignment (which breaks as soon as one item errors to stderr while another prints to stdout):install— never silently drop a requested installable:Add regression coverage for an absent file-path requirement in image mode (the existing
check_presence/install_filesystempathtests only exercise file paths that are already present, e.g./usr/bin/arch,/usr/bin/flock, so this case is currently untested). The same stdout-vs-stderr point should be considered for theDnf.check_presencefile-path matcher.Environment
Fedora-44-image-mode-x86_64.qcow2(quay.io/testing-farm/fedora-bootc:44), provisioned withhow: virtualpackage manager: bootc,bootc builder: dnf5,is image mode: yesGenerated-by: Claude Code