Files
werkator/docs/prs/2026-10-04-PR#110-nginx-cleanup-cpu-time-disks.md
miandmhoennig 978f1e80d8
werkator build succeeded after 02:01
werkator/deploy build succeeded after 00:07
Tests for stale nginx container removal, processor-time display and System page disks; no endless-wait memory errors in the mutation build (v1.34.7) (#110)
- cleanupStaleContainers 45 -> 2, UiFormats.cpuTime 43.8 -> 7, monitoredDisks 42 -> 2: tests reaching every branch; `ContainerInfo.isWerkatorLeftover`, `monitoredDisksOf`, shared `InstancePath.resolve`.
- Fix for the failed gated mutation build (2 INCONCLUSIVE from MEMORY_ERROR): `ConsoleBuildRunner.awaitTerminal` decides the end of the wait once; the nginx test sleeper fails after 100 rounds. Local PIT: 0 MEMORY_ERROR, 0 INCONCLUSIVE.
- Nightly gate 46 -> 36 (highest left: SystemMetricsCollector.sample 35.36).

PR-doc: `docs/prs/2026-10-04-PR#110-nginx-cleanup-cpu-time-disks.md`

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #110
2026-10-04 17:28:48 +02:00

107 lines
6.4 KiB
Markdown

> **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#109 the three highest Mutation-CRAP scores of Werkator were unrelated, each small:
| Method | Mutation-CRAP | Killed / eligible | Why |
| ------------------------------------------ | ------------- | ----------------- | ----------------------------------------------------------------------------------------------------- |
| `NginxProxyManager.cleanupStaleContainers` | 45.04 | 1 / 6 | tested, but the tests passed whatever it did: the start removes the container by name once more later |
| `UiFormats.cpuTime` | 43.81 | 1 / 11 | no test of its own |
| `MetricsConfiguration.monitoredDisks` | 42 | 0 / 9 | read the configuration through Spring beans, no test reached it |
The first mutation build with an active gate (PR#104) failed on an analysis error instead: two methods were `INCONCLUSIVE`, because a mutant had made a bounded wait endless and filled the memory of PIT's minion (`MEMORY_ERROR`), which werkcrappit refuses to count as killed (its ADR 0006).
In `NginxProxyManager.waitForPortsFree` the mutant turned `repeat(4)` into an endless loop, and the test's sleeper did not sleep; in `ConsoleBuildRunner.buildAndStream` the end of the wait was asked twice, and a mutant of the first question alone made it endless and without a pause.
Tonight's mutation build would have failed the same way.
The rule for a path in the instance file — `~` is the home directory, anything relative is relative to it — was written twice, for the registry's repositories and for the System page's disks.
## Non-Goals
- No change in behaviour.
## The Scenarios
### Feature: Three small decisions, held by tests
#### Scenario#110.01: Processor time reads with its factor against the wall clock
So that `4:07 (2.4x)` stays what the artifact page shows.
- **Given** processor times with and without a duration, a zero duration, nothing measured, and a negative time
- **When** they are formatted
- **Then** the factor appears only with a duration above zero, and nothing measured shows nothing
##### Verified by
- [UiViewsUnitTest](../../src/test/kotlin/de/hoennig/werkator/server/UiViewsUnitTest.kt) — Scenario#110.01
#### Scenario#110.02: The System page shows the configured disks, their paths read like every path of the instance file
So that `~`, `~/…` and relative paths mean the same for a disk as for a repository.
- **Given** no `metrics.disks`, and disks with `~`, an absolute path, `~/…` with a blank label, and a relative path
- **When** the monitored disks are resolved
- **Then** without the section the checkout's filesystem is `Disk`; otherwise the disks keep their order, paths resolve against the home directory, and a blank label falls back to the key
- **And** only `~` and `~/` expand, `~other` is a name like any other
##### Verified by
- [MonitoredDisksUnitTest](../../src/test/kotlin/de/hoennig/werkator/metrics/MonitoredDisksUnitTest.kt) — Scenario#110.02
#### Scenario#110.03: Only Werkator's own leftovers are removed before the managed nginx starts
So that a stale container of Werkator never blocks a port, and someone else's container is never touched.
- **Given** a container of the configured name, labelled nginx containers of this repository, and containers on and off the nginx ports
- **When** the managed nginx starts
- **Then** the container of the name goes first, then the labelled ones; a failed or empty listing removes nothing by label
- **And** on an nginx port only a container labelled as Werkator's, or named `werkator-…` or `git-watch-origin-and-test-nginx-…`, is removed
##### Verified by
- [NginxProxyManagerUnitTest](../../src/test/kotlin/de/hoennig/werkator/server/NginxProxyManagerUnitTest.kt) — Scenario#110.03
#### Scenario#110.04: A wait that a mutant makes endless fails a test instead of filling the memory
So that the mutation gate judges test quality, not memory, and the nightly build can pass.
- **Given** the port wait of the managed nginx and the console's wait for a build
- **When** PIT runs on both classes
- **Then** no mutant ends with `MEMORY_ERROR`, and no method is `INCONCLUSIVE`
##### Verified by
- [NginxProxyManagerUnitTest](../../src/test/kotlin/de/hoennig/werkator/server/NginxProxyManagerUnitTest.kt): its sleeper fails after 100 rounds
- [ConsoleBuildRunnerUnitTest](../../src/test/kotlin/de/hoennig/werkator/commands/ConsoleBuildRunnerUnitTest.kt)
- A local PIT run on both classes: 0 `MEMORY_ERROR`, 0 `INCONCLUSIVE`
## The Solution
- `NginxProxyManager.ContainerInfo.isWerkatorLeftover` names the rule for a stale Werkator container; `removeLabelledContainers` the removal by label; `cleanupStaleContainers` reads as the three steps.
- `MetricsConfiguration.monitoredDisksOf` resolves the disks without Spring; `InstancePath.resolve` is the one rule for a path in the instance file, used by `RepositoryEntry.directory` as well.
- `UiFormats.cpuTime` gets the test it lacked.
- `ConsoleBuildRunner.awaitTerminal` decides the end of the wait at one place; the test sleeper of `NginxProxyManagerUnitTest` fails after 100 rounds.
Measured with PIT as the nightly build runs it:
| Method | Before | After |
| ------------------------------------------ | ------ | ----- |
| `NginxProxyManager.cleanupStaleContainers` | 45.04 | 2 |
| `UiFormats.cpuTime` | 43.81 | 7.04 |
| `MetricsConfiguration.monitoredDisks` | 42 | 2 |
| `MetricsConfiguration.monitoredDisksOf` | — | 4 |
| `ContainerInfo.isWerkatorLeftover` | — | 3.03 |
The gate `maxMutationCrap` falls from 46 to 36: the highest score left is `SystemMetricsCollector.sample` at 35.36.
## Additional Changes
- Version 1.34.7 with its release note.
## Follow-up PRs
- The methods above 15 by complexity alone: `SystemMetricsCollector.sample` (35), `BuildDefinition.applyTo` (34), `ForgejoWorkflowExporter.settingNotes` (27), `SystemMetricsView.from` (24), and the `ConfigLoader` checks.