> **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 The nightly mutation build leaves out `BuildExecutorIntegrationTest` (PR#94), the only test that runs `BuildExecutor`. The executor's own decisions therefore looked untested: `runBuildCommands` 46.55, `shutdown` 42, `cancel` 30, `startBuild` 28.67. Taking the test back into the run is no way out: measured locally on `BuildExecutor` alone, PIT needed 12 minutes without it and had not finished after more than 100 minutes with it, 72 mutants having run into PIT's time limit. Every one of those tests starts real processes and waits for them. The register of active builds, a map of artifact keys, was asked in nine places, each with its own loop. ## Non-Goals - `BuildExecutorIntegrationTest` stays, and stays out of the nightly run; it holds the executor together with real processes. - The process handling (`destroyProcessTree`, `waitForBounded`, the processor-time sampling) follows in its own PR. - No change in behaviour. ## The Scenarios ### Feature: The register of active builds #### Scenario#108.01: A build of the same pool and commit is a duplicate while it is active and not cancelled So that a double-triggered restart does not queue a commit twice, and a cancelled build does not block it. - **Given** a queued build of `main` at `abc` - **When** the register is asked for a duplicate - **Then** the same pool and commit in the same repository is one; another pool, commit or repository is not; a cancelled build is not ##### Verified by - [ActiveBuildsUnitTest](../../src/test/kotlin/de/hoennig/werkator/build/ActiveBuildsUnitTest.kt) — Scenario#108.01 #### Scenario#108.02: A key is active in its own repository only, and until it is removed So that a route of one repository never reaches a build of another. - **Given** a listed build, one queued and one executing - **When** the register is asked - **Then** the key is active in its repository only, until removed, and only the executing build counts as executing ##### Verified by - [ActiveBuildsUnitTest](../../src/test/kotlin/de/hoennig/werkator/build/ActiveBuildsUnitTest.kt) — Scenario#108.02 #### Scenario#108.03: A cancellation says whether the build was still queued or already executing So that the executor records a queued build at once and terminates an executing one. - **Given** a queued and an executing build - **When** each is cancelled, and an unknown key - **Then** the answers are queued, executing, and none, and both builds are marked cancelled ##### Verified by - [ActiveBuildsUnitTest](../../src/test/kotlin/de/hoennig/werkator/build/ActiveBuildsUnitTest.kt) — Scenario#108.03 ### Feature: The executor's decisions, without a process #### Scenario#108.04: A build runs its clean command, then its build command, and the exit codes decide its status So that the order of the commands and the logs they write are held where the nightly run sees them. - **Given** a runner whose commands end at once with chosen exit codes - **When** builds run with and without a clean command, and with their own log names - **Then** clean runs before build, a failed clean ends the build, the exit codes decide success or failure, and the live log and the named logs hold what they should ##### Verified by - [BuildExecutorUnitTest](../../src/test/kotlin/de/hoennig/werkator/build/BuildExecutorUnitTest.kt) — Scenario#108.04 #### Scenario#108.05: Cancelling a queued build records it at once and never starts it; an executing one is terminated So that a cancellation is final either way. - **Given** an executing build that ends only when destroyed, and a build queued behind it - **When** both are cancelled - **Then** the queued one is CANCELLED at once and never started, the executing one is destroyed and CANCELLED ##### Verified by - [BuildExecutorUnitTest](../../src/test/kotlin/de/hoennig/werkator/build/BuildExecutorUnitTest.kt) — Scenario#108.05 #### Scenario#108.06: A second start of the same commit is the same build while it is active So that a double click does not build twice, and a cancelled build can be started again. ##### Verified by - [BuildExecutorUnitTest](../../src/test/kotlin/de/hoennig/werkator/build/BuildExecutorUnitTest.kt) — Scenario#108.06 #### Scenario#108.07: A shutdown interrupts the executing build and leaves the queued one pending So that the startup recovery finds both. ##### Verified by - [BuildExecutorUnitTest](../../src/test/kotlin/de/hoennig/werkator/build/BuildExecutorUnitTest.kt) — Scenario#108.07 #### Scenario#108.08: A build that outlives its timeout is stopped and recorded as timed out ##### Verified by - [BuildExecutorUnitTest](../../src/test/kotlin/de/hoennig/werkator/build/BuildExecutorUnitTest.kt) — Scenario#108.08 #### Scenario#108.09: A build cancelled after its clean command never starts its build command So that a cancellation between the two commands is not overrun by the second one. ##### Verified by - [BuildExecutorUnitTest](../../src/test/kotlin/de/hoennig/werkator/build/BuildExecutorUnitTest.kt) — Scenario#108.09 #### Scenario#108.10: After a deployment the instance restarts only once every other build has ended So that a deployment never stops another repository's build. - **Given** a build of another branch executing - **When** a deployment with `restartsInstance` succeeds - **Then** the instance drains, starts no further build, and asks for the restart only after the other build has ended ##### Verified by - [BuildExecutorUnitTest](../../src/test/kotlin/de/hoennig/werkator/build/BuildExecutorUnitTest.kt) — Scenario#108.10 ## The Solution - `ActiveBuilds` is the register of accepted, unfinished builds: duplicates, active keys, executing builds, and what a cancellation found; `BuildExecutor` asks it instead of a map. - `BuildExecutorUnitTest` runs the real `BuildExecutor` against a build runner that starts no process: its processes end at once with a chosen exit code, or only when destroyed. The spec takes about six seconds, so the nightly run keeps it. Measured with PIT as the nightly build runs it, on `BuildExecutor`: | Method | Before | After | | ------------------ | ------ | ----- | | `runBuildCommands` | 46.55 | 20.78 | | `shutdown` | 42 | 7.05 | | `cancel` | 30 | 4 | | `startBuild` | 28.67 | 2.98 | | the drain's wait | 20 | 8.13 | `runBuildCommands` is still above 15; its rest is the process handling of the next PR. The gate `maxMutationCrap` falls from 47 to 46: the highest score left is `NginxProxyManager.cleanupStaleContainers` at 45.04. ## Additional Changes - Version 1.34.5 with its release note. ## Follow-up PRs - The process handling of `BuildExecutor` in a class of its own, with a short spec against real, short processes that the nightly run keeps.