added age-based build retention: artifacts.retentionMaxAge (e.g. 30d, empty = no limit) drops builds older than the given age; combines with retentionPerBranch as independent caps — a build is kept only while it satisfies both limits; a branch's newest build is never age-pruned and keepLatestGreen now shields the latest green build from both limits, keeping the permanent /branches/... links valid
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b104eeee05
commit
d0b38c557a
@@ -220,6 +220,90 @@ class FileBuildResultRepositoryTest : FunSpec() {
|
||||
)
|
||||
}
|
||||
|
||||
test("prune drops entries older than the retention cutoff even within the retention count") {
|
||||
val repository = FileBuildResultRepository(newFile())
|
||||
repository.append(result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 0))
|
||||
repository.append(result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 60))
|
||||
repository.append(result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 120))
|
||||
|
||||
val removed =
|
||||
repository.prune(
|
||||
originBranches = listOf("main"),
|
||||
retentionPerBranch = 3,
|
||||
retentionCutoff = baseTime.plusSeconds(90),
|
||||
)
|
||||
|
||||
removed shouldContainExactlyInAnyOrder
|
||||
listOf(
|
||||
result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 0),
|
||||
result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 60),
|
||||
)
|
||||
repository.history() shouldContainExactly
|
||||
listOf(result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 120))
|
||||
}
|
||||
|
||||
test("prune never age-prunes a branch's newest entry") {
|
||||
val repository = FileBuildResultRepository(newFile())
|
||||
repository.append(result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 0))
|
||||
|
||||
val removed =
|
||||
repository.prune(
|
||||
originBranches = listOf("main"),
|
||||
retentionPerBranch = 3,
|
||||
retentionCutoff = baseTime.plusSeconds(300),
|
||||
)
|
||||
|
||||
removed.shouldBeEmpty()
|
||||
repository.history() shouldContainExactly
|
||||
listOf(result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 0))
|
||||
}
|
||||
|
||||
test("prune applies the retention count and cutoff as independent limits") {
|
||||
val repository = FileBuildResultRepository(newFile())
|
||||
repository.append(result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 0))
|
||||
repository.append(result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 60))
|
||||
repository.append(result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 120))
|
||||
|
||||
val removed =
|
||||
repository.prune(
|
||||
originBranches = listOf("main"),
|
||||
// the count drops the entry at 0, the cutoff drops the entry at 60
|
||||
retentionPerBranch = 2,
|
||||
retentionCutoff = baseTime.plusSeconds(90),
|
||||
)
|
||||
|
||||
removed shouldContainExactlyInAnyOrder
|
||||
listOf(
|
||||
result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 0),
|
||||
result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 60),
|
||||
)
|
||||
repository.history() shouldContainExactly
|
||||
listOf(result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 120))
|
||||
}
|
||||
|
||||
test("prune with keepLatestGreen keeps the newest green build beyond the retention cutoff") {
|
||||
val repository = FileBuildResultRepository(newFile())
|
||||
repository.append(result(branch = "main", status = BuildStatus.SUCCESS, startedOffsetSeconds = 0))
|
||||
repository.append(result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 60))
|
||||
repository.append(result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 120))
|
||||
|
||||
val removed =
|
||||
repository.prune(
|
||||
originBranches = listOf("main"),
|
||||
retentionPerBranch = 3,
|
||||
keepLatestGreen = true,
|
||||
retentionCutoff = baseTime.plusSeconds(90),
|
||||
)
|
||||
|
||||
removed shouldContainExactly
|
||||
listOf(result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 60))
|
||||
repository.history() shouldContainExactly
|
||||
listOf(
|
||||
result(branch = "main", status = BuildStatus.FAILED, startedOffsetSeconds = 120),
|
||||
result(branch = "main", status = BuildStatus.SUCCESS, startedOffsetSeconds = 0),
|
||||
)
|
||||
}
|
||||
|
||||
test("prune with keepLatestGreen keeps the newest green build beyond the retention count") {
|
||||
val repository = FileBuildResultRepository(newFile())
|
||||
repository.append(result(branch = "main", status = BuildStatus.SUCCESS, startedOffsetSeconds = 0))
|
||||
|
||||
@@ -420,6 +420,18 @@ class WatcherTest : FunSpec() {
|
||||
dropping.repository.history().map { it.status } shouldContainExactly listOf(BuildStatus.FAILED)
|
||||
}
|
||||
|
||||
test("poll drops builds older than retentionMaxAge but keeps the branch's newest build") {
|
||||
// seeds start one hour before the fixed clock, so a 30m age limit cuts them off
|
||||
val harness = Harness(GitTallyConfig(artifacts = ArtifactsConfig(retentionMaxAge = "30m")))
|
||||
harness.seed("main", BuildStatus.FAILED, commit = "commit-1")
|
||||
harness.seed("main", BuildStatus.FAILED, commit = "commit-2")
|
||||
every { harness.gitService.originBranches(any()) } returns listOf("main")
|
||||
|
||||
harness.watcher.poll(harness.workingDir)
|
||||
|
||||
harness.repository.history().map { it.commit } shouldContainExactly listOf("commit-2")
|
||||
}
|
||||
|
||||
test("worktrees of queued or running builds are never pruned") {
|
||||
val harness = Harness()
|
||||
harness.seed("busy", BuildStatus.RUNNING, commit = "commit-1")
|
||||
|
||||
Reference in New Issue
Block a user