Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,23 @@ org.gradle.jvmargs=-Xmx3g -XX:MaxMetaspaceSize=1536m -XX:+HeapDumpOnOutOfMemoryE

# The Kotlin compile daemon is a separate JVM that every module compiles through, and it was never
# configured at all - so it ran on whatever the Kotlin Gradle plugin defaults to, which varies by
# plugin version. Setting it explicitly is worth more than the specific number.
kotlin.daemon.jvmargs=-Xmx1536m -XX:MaxMetaspaceSize=768m
# plugin version. Measured with MODE=kotlin, run TWICE with the candidate order reversed, because
# a single pass cannot tell a heap effect from the build progressively warming up:
#
# heap 1st order 2nd order (reversed) mean
# 1g 44.1s 40.7s 42.4s
# 1536m 37.9s 37.2s 37.6s
# 2g 32.6s 33.8s 33.2s <- chosen
# 3g 30.1s 38.0s 34.1s
#
# The control is what makes this readable. 1g is slowest even from the LAST slot, where warming
# helps most, so it is genuinely too small. 3g looks like the winner in one order and mid-pack in
# the other - that swing is position, not heap. 2g is the only candidate that is fast in both.
#
# Honest limitation: only wall time is trustworthy here. The Kotlin daemon exits as soon as
# compilation ends, and its GC log was not reliably captured, so live-set and GC-overhead figures
# for this daemon varied ~40% between identical runs and were not used to pick the value.
kotlin.daemon.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=768m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
Expand Down
66 changes: 66 additions & 0 deletions konsist/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,74 @@ val filesUnderRules =

// useJUnitPlatform() is not repeated here - billionbeers.jvm.library sets it for this tier.
tasks.withType<Test>().configureEach {
// Gradle forks test workers with a 512 MB heap by default, and org.gradle.jvmargs does not
// reach them - it sizes the daemon, not the worker. These rules parse every .kt file in the repo
// into an in-memory Konsist model, and since this module joined billionbeers.jvm.library it also
// carries jacoco instrumentation, so 512 MB is not enough: a full run died with
// "OutOfMemoryError thrown from the UncaughtExceptionHandler in thread Test worker".
//
// It hid well. A single-class run (`--tests "*OneRuleTest*"`) fits in the default heap and
// passes, and an unchanged repo leaves the task UP-TO-DATE, so the gate looked green from both
// directions. Only a full, non-cached run reaches the ceiling - which is the run CI does.
maxHeapSize = "2g"

inputs
.files(filesUnderRules)
.withPropertyName("filesUnderArchitectureRules")
.withPathSensitivity(PathSensitivity.RELATIVE)
}

// Every rule file must actually have run.
//
// This gate has now failed silently three separate ways: it went UP-TO-DATE while the code it
// guards changed (fixed by declaring inputs above), it passed a single-class `--tests` run while a
// full run died of OutOfMemory (fixed by maxHeapSize above), and before either of those it simply
// never ran at all because `make test` targets testDebugUnitTest, which a pure-JVM module does not
// have (AGENTS.md §5). Each fix addressed one symptom. This addresses the shape.
//
// The check is self-maintaining on purpose: it counts `*Test.kt` rule files on disk and compares
// against the JUnit XML classes the run produced, so adding a rule raises the bar automatically and
// a hardcoded expected number can never go stale. A rule that is added but never executed - the
// exact thing AGENTS.md warns about twice - now fails the build instead of reading as coverage.
//
// Skipped when a `--tests` filter is supplied, because running one rule class is then correct.
val ruleSourceDir = layout.projectDirectory.dir("src/test/kotlin/com/simtop/konsist")
val hasTestFilter =
gradle.startParameter.taskRequests.any { request -> request.args.any { it == "--tests" } }

tasks.named<Test>("test") {
val resultsDir = reports.junitXml.outputLocation
val ruleDir = ruleSourceDir
val filtered = hasTestFilter

doLast {
if (filtered) return@doLast

val expected =
ruleDir.asFile
.listFiles { file -> file.name.endsWith("Test.kt") }
?.map { it.name.removeSuffix(".kt") }
?.toSet()
.orEmpty()
val executed =
resultsDir
.get()
.asFile
.listFiles { file -> file.name.startsWith("TEST-") && file.extension == "xml" }
?.map { it.name.removePrefix("TEST-").removeSuffix(".xml").substringAfterLast('.') }
?.toSet()
.orEmpty()

check(expected.isNotEmpty()) {
"Found no *Test.kt rule files in $ruleDir - the layout changed and this check, plus the " +
"whole architecture gate, would pass vacuously."
}

val missing = expected - executed
check(missing.isEmpty()) {
"These Konsist rules exist but did not run: ${missing.sorted().joinToString(", ")}.\n" +
"A rule that never executes looks like coverage and enforces nothing. Ran " +
"${executed.size} of ${expected.size}."
}
}
}
Loading
Loading