Files
werkator/docs/prs/2026-10-04-PR#107-environment-probes.md
miandmhoennig fadbcd305f
werkator build succeeded after 02:19
werkator/deploy build succeeded after 00:12
Unit tests for detecting the Docker socket, the running jar, the hostname and the state directory (v1.34.4) (#107)
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
2026-10-04 12:23:50 +02:00

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.monitoredDisks and NginxProxyManager.cleanupStaleContainers stay 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_HOST wins, then /run/user/<uid>/docker.sock (rootless), then /var/run/docker.sock with its group
  • And a tcp DOCKER_HOST or a socket that does not exist gives none
Verified by

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 -jar command names it, a Spring Boot loader URL holds it, and running from classes gives none
Verified by

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

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_HOME set, unset, and blank
  • When the state home is asked for
  • Then it is that directory, or ~/.local/state
Verified by

The Solution

  • DockerSocketLocator asks the host through three seams — the environment, whether a path exists, the group of a socket — given by its Spring constructor; candidate holds the order of the rules.
  • InitCommand.jarPathOf decides from the launch command and the code source URL; runningJarPath only reads the two.
  • HostingEnvironments.hostnameOf decides between the hostname file and the resolver; readHostname only names the two.
  • StateHome.of is the one rule for the state directory; the metrics, the nginx default and the artifact store use it.
  • The gate maxMutationCrap falls from 57 to 47: the highest score left is BuildExecutor.runBuildCommands at 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 (runBuildCommands 46.55, shutdown 42, cancel 30, startBuild 28.67), with a decision on how the nightly run measures it.