Third Mutation-CRAP refactoring: the environment probes. - `DockerSocketLocator`: seams for env, path existence, socket group; `candidate` holds the rule order (56 -> 5.1). - `InitCommand.jarPathOf` (runningJarPath 56 -> 6), `HostingEnvironments.hostnameOf` (readHostname 42 -> 2). - `StateHome.of`: the one XDG_STATE_HOME rule, used by metrics, nginx default, artifact store. - Nightly gate `maxMutationCrap` 57 -> 47 (highest left: BuildExecutor.runBuildCommands 46.55). PR-doc: `docs/prs/2026-10-04-PR#107-environment-probes.md` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: mhoennig <michael@hoennig.de> Reviewed-on: #107
5.4 KiB
WARNING: This document describes only the change applied in this PR. It may already be outdated once the next PR is merged. Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.
The Problem
After PR#106 the highest Mutation-CRAP scores of Werkator were methods that ask the host something, and no test reached them:
| Method | Mutation-CRAP | Killed / eligible |
|---|---|---|
DockerSocketLocator.locate |
56 | 0 / 9 |
InitCommand.runningJarPath |
56 | 0 / 7 |
HostingEnvironments.readHostname |
42 | 0 / 7 |
MetricsConfiguration.stateHome |
42 | 0 / 6 |
NginxProxyManager.defaultStateDir |
26.83 | 1 / 6 |
Each read the environment, the file system or the JVM directly, so its rules — which socket wins, where the jar is, which name counts — could only be tested on a host that happens to look a certain way.
The rule for Werkator's state directory, XDG_STATE_HOME or ~/.local/state, was written out three times: for the metrics, the managed nginx and the artifact store.
Non-Goals
- No change in behaviour: every socket, path and name is found as before.
MetricsConfiguration.monitoredDisksandNginxProxyManager.cleanupStaleContainersstay for a later PR.
The Scenarios
Feature: What Werkator asks the host is decided by rules with tests
Scenario#107.01: A unix DOCKER_HOST wins, then the rootless socket, then the system socket
So that a build container gets the socket the legacy script would have given it.
- Given an environment, a set of existing sockets and their groups
- When the socket for a uid is located
- Then a unix
DOCKER_HOSTwins, then/run/user/<uid>/docker.sock(rootless), then/var/run/docker.sockwith its group - And a tcp
DOCKER_HOSTor a socket that does not exist gives none
Verified by
- DockerSocketLocatorUnitTest — Scenario#107.01
Scenario#107.02: The running jar is the one java -jar names, else the one in the loader's URL
So that init --systemd writes the jar the service really runs.
- Given a launch command and a code source URL
- When the running jar is found
- Then a
java -jarcommand names it, a Spring Boot loader URL holds it, and running from classes gives none
Verified by
- InitCommandUnitTest — Scenario#107.02
Scenario#107.03: The machine's name comes from /etc/hostname, the resolver only when the file says nothing
So that a resolver alias never names a Hostsharing machine.
- Given a hostname file and a resolver, each answering, blank, or failing
- When the machine's name is read
- Then the file's name wins, the resolver answers when the file is blank or missing, and nothing answers when both fail
Verified by
- HostingEnvironmentUnitTest — Scenario#107.03
Scenario#107.04: The state home is XDG_STATE_HOME, else ~/.local/state
So that the artifact store, the managed nginx and the metrics agree where Werkator's state lives.
- Given
XDG_STATE_HOMEset, unset, and blank - When the state home is asked for
- Then it is that directory, or
~/.local/state
Verified by
- StateHomeUnitTest — Scenario#107.04
The Solution
DockerSocketLocatorasks the host through three seams — the environment, whether a path exists, the group of a socket — given by its Spring constructor;candidateholds the order of the rules.InitCommand.jarPathOfdecides from the launch command and the code source URL;runningJarPathonly reads the two.HostingEnvironments.hostnameOfdecides between the hostname file and the resolver;readHostnameonly names the two.StateHome.ofis the one rule for the state directory; the metrics, the nginx default and the artifact store use it.- The gate
maxMutationCrapfalls from 57 to 47: the highest score left isBuildExecutor.runBuildCommandsat 46.55.
Measured with PIT as the nightly build runs it:
| Method | Before | After |
|---|---|---|
DockerSocketLocator.locate |
56 | 5.12 |
DockerSocketLocator.candidate |
— | 5 (6 / 6 killed) |
InitCommand.runningJarPath |
56 | 6 |
InitCommand.jarPathOf |
— | 6 (6 / 6 killed) |
HostingEnvironments.readHostname |
42 | 2 |
HostingEnvironments.hostnameOf |
— | 8 (8 / 8 killed) |
MetricsConfiguration.stateHome |
42 | no mutant left |
StateHome.of |
— | 6.17 |
NginxProxyManager.defaultStateDir |
26.83 | 2 |
Additional Changes
- Version 1.34.4 with its release note.
Follow-up PRs
- The rest of
BuildExecutor(runBuildCommands46.55,shutdown42,cancel30,startBuild28.67), with a decision on how the nightly run measures it.