Bookwyrm has tests for the same trusted-header SSO bridge and settings
patcher; postmarks and baserow ship theirs untested, and the composer
require-dev stripper is a pure transformation nobody exercised.
Each test was checked by breaking its source: the admin gate failing
open, the group matcher loosened to a prefix, require-dev left in place
and the SSO gate forced open all turn the new tests red.
The unit suites had to install their toolchain on every run. The image
now carries the interpreters, so the make prerequisite finds them present.
The Composer vendor tree deliberately stays out of the image build: the
package cache is reached through the compose extra_hosts mapping, which
only exists for a running container, so a build-time composer install
would bypass the cache and talk to packagist directly. Fetching it from
the test target keeps it on the cached path.
test-unit-php and test-unit-ruby exited 0 whenever the interpreter or the
vendor tree was missing, so both suites were silently absent from every
gate. They now provision their toolchain through install-php/install-ruby
and fail loudly when that is impossible.
The three ensure_*_present helpers collapse into ensure_command_present
in system_pkg, and the ruby runner rejects an empty suite because
minitest reports success when nothing was required.
The two ca_* handlers sat flat in utils/env/handlers/infinito, which the
prefix-grouping lint rejects, and the bundle-candidates comment exceeded
the .env comment length limit.
Run 32118850138 lost the :53 listener, so the sampler counts listeners.
Run 32538854067 lost DNS with the listener still bound: sockets-udp.txt
showed dnsmasq holding 0.0.0.0:53 while every forwarded query timed out,
and the sampler reported that hour as healthy.
Each tick now also records what a glibc caller gets back — for the zone
this resolver owns, derived from its own address= line, and for one name
it must forward — with the latency that caller paid. The rescue collector
probes the in-zone name too: probing external names alone cannot separate
"forwarding is gone" from "dnsmasq is gone".
The sampler now also runs on the swarm lab DNS nodes; it only ever ran in
the compose path, which is why the Magento artifact carried no sampler log
at all. Its location comes from group_vars FILE_DNS53_SAMPLER_LOG and is
required at every call site, so a run cannot quietly collect nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
patch.py located files/ruby/ relative to its own __file__. That holds in
the repo, where the script sits next to the fragments, but the image
copies the script to /tmp/patch.py and the fragments to /tmp/ruby/, so
parent.parent resolved to / and the build died with
FileNotFoundError: /ruby/omniauth_provider.rb on every retry.
DECIDIM_PATCH_RUBY_DOCKER already decides where the COPY puts the
fragments. Reading it in the script makes that variable the only place
the location is stated, so the layout can change without a second
place silently disagreeing. Missing variable raises instead of
guessing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
test-unit-javascript ran `node --test` without declaring where node
comes from, while its python sibling declares `install`. On a bare
distro image the target died with `exec: node: not found` (127), which
took down the pre-commit hook and all five test-workspace jobs.
The installer stack already knows how to pull a command from the system
package manager; node was simply missing from the table. Wiring the
target to install-node provisions it the same way php, ruby and npm are
provisioned, and fails loudly when the runtime cannot be installed
rather than skipping the suite.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`roles/<role>/files/<lang>/<rel>` is tested by
`tests/unit/<lang>/roles/<role>/files/<rel>`, renamed to that language's
convention. The language appears once, in `tests/unit/`, because the suite
already says what the file is. Without a fixed mapping a shipped script is
tested wherever its author happened to look, and nobody can tell an untested
file from one whose test lives somewhere unexpected.
Most of what a role ships is a script against a live console -- it boots a
Laravel kernel, imports frappe, or writes through an ActiveRecord model -- so
the escape is a `nocheck: mirrored-unit-test` comment in the file, with a
rationale of its own. A file carrying both a test and an exemption fails: the
exemption outlived its reason and reads as "untested" to everyone after you.
Five scripts turned out to have real units behind the console wrapper, so they
get tests rather than markers (62 cases):
* svc-net-tor/onion_self_reach.py -- pins that an HTTP error status, a closed
connection and a refused connection all count as reached, because each
proves the loopback fast path resolved; only a timeout or an unresolved
name is a failure.
* sys-lock/sys-lock.py -- `parse_time_to_seconds` unit precedence, and that a
service still activating holds the lock (otherwise the gate races it).
* svc-opt-keyboard-color/script.py -- colour interpolation and the
zero-length transition window that would otherwise divide by zero.
* sys-svc-compose/swarm/secret_rotate.py -- including the binary fallback a
TLS key needs, and that a missing source leaves the name alone rather than
pinning the hash of a file that is not there.
* sys-front-inj-logout/logout.js -- pins that `href="/logout"` alone does not
match and a non-interactive tag is never patched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every script a role ships now lives under `files/<language>/`, and the unit
suite is split the same way: `tests/unit/python/` beside new sibling trees for
the other languages. `tests/lint/filesystem/test_role_files_layout.py` keeps it
that way; directories whose shape is dictated from outside carry a `.nocheck`
naming the constraint (Docker build contexts, WordPress mu-plugins, the Joomla
extension, Odoo addons, Playwright suites, the `files/test/test.sh` harness
that `test-e2e-cli` resolves generically).
This cannot be applied in halves. Moving a file and repointing what names it
are the same change: 14 references resolve only at deploy time, so the tree is
green either before or after, never between.
Those 14 were found by static resolution over every reference class, not by the
test suite -- a full `make quality-high` was green while all of them were
broken:
* 7 `src:` values built from a role variable holding a bare filename
(svc-bkp-remote-2-local, decidim, espocrm, friendica, joomla, pixelfed x2).
The variable doubles as the destination name inside the container, so the
language segment belongs at the call site, not in the variable.
* `sys-service/tasks/04_base.yml` resolved every consumer's systemd script
through `first_found(..., errors='strict')` over `files/script.{sh,py}`.
18 roles ship exactly that and have no template fallback: the play would
have died on each.
* `sys-front-inj-javascript` fell back to `<role>/files/javascript.js`; the
interface now reads `files/javascript/javascript.js`.
* `sys-front-inj-logout/templates/head_sub.j2` built the cache-busting mtime
path through `path_absolute(... 'files', ...)`.
* 4 `ansible.builtin.script:` invocations naming a bare command
(pull.py, prune.sh, set_postgres_superuser_password.sh, ganesha-serve.sh).
* `dev-python/files/shell/install.sh` pointed at a sibling role's script.
Depth-dependent paths inside the moved trees were bumped accordingly: 59
`parents[N]` computations, 12 relative climbs, and the per-role recover CLI,
whose path `cli/administration/recover/recoverers.py` still assembled from the
pre-move location.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>