Files
werkator/docs/prs/2026-10-05-PR#120-config-trigger-checks.md
miandmhoennig 3cfed35fdf
werkator build succeeded after 05:03
werkator/deploy build succeeded after 00:07
Trigger and pull-request target checks of the configuration split up, their warnings tested (v1.37.1) (#120)
> Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.

## The Problem

PR#117 added a fourth condition to `ConfigLoader.isTriggered`.
A local PIT run of the classes changed by PR#117 to PR#119 measured these methods above the Kotlin target of 15:

| Method                                         | Mutation-CRAP | Complexity | Mutation score |
|------------------------------------------------|---------------|------------|----------------|
| `ConfigLoader.isTriggered`                     | 34.12         | 13         | 0.50           |
| `ConfigLoader.withoutPullRequestTargets`       | 17.58         | 7          | 0.40           |
| `ConfigLoader.checkPullRequestTargetSandboxes` | 15.05         | 15         | 0.94           |

`isTriggered` is above the nightly gate of 31, so tonight's mutation build would fail.
Its only effect is a warning, and no test looked at that warning; the same holds for the warnings of `withoutPullRequestTargets`.

## Non-Goals

- No change in behaviour: every check refuses and warns exactly as before, with the same texts.
- The other methods above 15 follow in their own PRs.

## The Scenarios

### Feature: the checks that only warn are tested by their warnings

#### Scenario#120.01: definitions that nothing starts are said once, and every kind of trigger silences it

So that the warning on an instance that never builds keeps coming exactly when it should.

- **Given** `builds.default` without a trigger
  - **and** a definition `nightly`
- **When** `nightly` has no trigger, an empty `trigger` block, or one whose `onPush`, `atTimes` and `afterSuccessOf` say nothing
- **Then** the configuration loads with one warning that the watcher will never start a build, also when loaded twice
- **When** `nightly` has `onPush: true`, an `atTimes` entry, an `afterSuccessOf`, or `onPullRequestTarget: true`
- **Then** there is no such warning

##### Verified by

- [ConfigLoaderUnitTest](../../src/test/kotlin/de/hoennig/werkator/config/ConfigLoaderUnitTest.kt) `definitions that nothing starts are said once, and every kind of trigger silences it (Scenario#120.01)`

#### Scenario#120.02: a branch's pull-request target definitions are ignored with one warning each

So that a branch author learns why the change to the review build has no effect.

- **Given** the host defines the pull-request target build `review`
- **When** a branch layer changes `review` and adds its own pull-request target `sneaky`
- **Then** each of the two is ignored with its own warning, naming the branch, or "this branch" without a name
  - **and** the same warning is not repeated for the same branch

##### Verified by

- [PullRequestTargetConfigUnitTest](../../src/test/kotlin/de/hoennig/werkator/config/PullRequestTargetConfigUnitTest.kt) `a branch's pull-request target definitions are ignored with one warning each, naming the branch (Scenario#120.02)`, and the Scenario#117.04 tests for what is ignored

#### Scenario#120.03: the sandbox check reads each pull-request target on its own

So that the check of PR#118 stays exact after the split.

- **Given** a pull-request target build in werkdock with a disabled `docker` block that says `socket: true`
- **When** the configuration loads
- **Then** it is accepted, since the socket belongs to a container that does not run
- **And** a target build without a sandbox, or in Docker with the socket, is still refused

##### Verified by

- [PullRequestTargetConfigUnitTest](../../src/test/kotlin/de/hoennig/werkator/config/PullRequestTargetConfigUnitTest.kt) `a pull-request target build in werkdock passes, whatever a disabled docker block says (Scenario#120.03)`, and the Scenario#118.03 tests

## The Solution

`triggerOf` reads the `trigger` block of a raw definition once for `isTriggered`, `predecessorOf` and `isPullRequestTarget`.
`isTriggered` asks three questions in one line; the push and the schedule moved to `startsItself`.
`withoutPullRequestTargets` gives both kinds of warning through one `warnIgnored`, instead of two loops.
`checkPullRequestTargetSandboxes` merges `builds.default` into each target and hands it to `checkPullRequestTargetSandbox`, which holds the two refusals.

The tests capture the log of `ConfigLoader` through `captureConfigLog`.

A local PIT run of `ConfigLoader` afterwards:

| Method                                         | Mutation-CRAP | Complexity | Mutation score |
|------------------------------------------------|---------------|------------|----------------|
| `ConfigLoader.isTriggered`                     | 4             | 4          | 1.00           |
| `ConfigLoader.startsItself`                    | 7             | 7          | 1.00           |
| `ConfigLoader.triggerOf`                       | 4             | 4          | 1.00           |
| `ConfigLoader.withoutPullRequestTargets`       | 5             | 5          | 1.00           |
| `ConfigLoader.warnIgnored`                     | 1             | 1          | 1.00           |
| `ConfigLoader.checkPullRequestTargetSandboxes` | 5.03          | 5          | 0.89           |
| `ConfigLoader.checkPullRequestTargetSandbox`   | 10            | 10         | 1.00           |

The highest score left in `ConfigLoader` is `checkRestartsInstance` at 15.
The project maximum stays `GitService.authenticated` at 30.12, so the gate stays at 31.

## Additional Changes

- Version 1.37.1 with its entry in `releases.html`.
- The integration test of Scenario#119.01 failed on CI: its review ran `echo` and ended before the test polled the running builds.
  The review now sleeps 2 s, so the test sees both builds running at once.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #120
2026-10-05 15:59:44 +02:00

5.8 KiB

PR#120: Trigger and Pull-Request Target Checks of the Configuration Split Up

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

PR#117 added a fourth condition to ConfigLoader.isTriggered. A local PIT run of the classes changed by PR#117 to PR#119 measured these methods above the Kotlin target of 15:

Method Mutation-CRAP Complexity Mutation score
ConfigLoader.isTriggered 34.12 13 0.50
ConfigLoader.withoutPullRequestTargets 17.58 7 0.40
ConfigLoader.checkPullRequestTargetSandboxes 15.05 15 0.94

isTriggered is above the nightly gate of 31, so tonight's mutation build would fail. Its only effect is a warning, and no test looked at that warning; the same holds for the warnings of withoutPullRequestTargets.

Non-Goals

  • No change in behaviour: every check refuses and warns exactly as before, with the same texts.
  • The other methods above 15 follow in their own PRs.

The Scenarios

Feature: the checks that only warn are tested by their warnings

Scenario#120.01: definitions that nothing starts are said once, and every kind of trigger silences it

So that the warning on an instance that never builds keeps coming exactly when it should.

  • Given builds.default without a trigger
    • and a definition nightly
  • When nightly has no trigger, an empty trigger block, or one whose onPush, atTimes and afterSuccessOf say nothing
  • Then the configuration loads with one warning that the watcher will never start a build, also when loaded twice
  • When nightly has onPush: true, an atTimes entry, an afterSuccessOf, or onPullRequestTarget: true
  • Then there is no such warning
Verified by
  • ConfigLoaderUnitTest definitions that nothing starts are said once, and every kind of trigger silences it (Scenario#120.01)

Scenario#120.02: a branch's pull-request target definitions are ignored with one warning each

So that a branch author learns why the change to the review build has no effect.

  • Given the host defines the pull-request target build review
  • When a branch layer changes review and adds its own pull-request target sneaky
  • Then each of the two is ignored with its own warning, naming the branch, or "this branch" without a name
    • and the same warning is not repeated for the same branch
Verified by
  • PullRequestTargetConfigUnitTest a branch's pull-request target definitions are ignored with one warning each, naming the branch (Scenario#120.02), and the Scenario#117.04 tests for what is ignored

Scenario#120.03: the sandbox check reads each pull-request target on its own

So that the check of PR#118 stays exact after the split.

  • Given a pull-request target build in werkdock with a disabled docker block that says socket: true
  • When the configuration loads
  • Then it is accepted, since the socket belongs to a container that does not run
  • And a target build without a sandbox, or in Docker with the socket, is still refused
Verified by
  • PullRequestTargetConfigUnitTest a pull-request target build in werkdock passes, whatever a disabled docker block says (Scenario#120.03), and the Scenario#118.03 tests

The Solution

triggerOf reads the trigger block of a raw definition once for isTriggered, predecessorOf and isPullRequestTarget. isTriggered asks three questions in one line; the push and the schedule moved to startsItself. withoutPullRequestTargets gives both kinds of warning through one warnIgnored, instead of two loops. checkPullRequestTargetSandboxes merges builds.default into each target and hands it to checkPullRequestTargetSandbox, which holds the two refusals.

The tests capture the log of ConfigLoader through captureConfigLog.

A local PIT run of ConfigLoader afterwards:

Method Mutation-CRAP Complexity Mutation score
ConfigLoader.isTriggered 4 4 1.00
ConfigLoader.startsItself 7 7 1.00
ConfigLoader.triggerOf 4 4 1.00
ConfigLoader.withoutPullRequestTargets 5 5 1.00
ConfigLoader.warnIgnored 1 1 1.00
ConfigLoader.checkPullRequestTargetSandboxes 5.03 5 0.89
ConfigLoader.checkPullRequestTargetSandbox 10 10 1.00

The highest score left in ConfigLoader is checkRestartsInstance at 15. The project maximum stays GitService.authenticated at 30.12, so the gate stays at 31.

Additional Changes

  • Version 1.37.1 with its entry in releases.html.
  • The integration test of Scenario#119.01 failed on CI: its review ran echo and ended before the test polled the running builds. The review now sleeps 2 s, so the test sees both builds running at once.

🤖 Generated with Claude Code