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
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,27 @@ Add the mutflow Gradle plugin to your `build.gradle.kts`:

```kotlin
plugins {
kotlin("jvm") version "2.3.0"
kotlin("jvm") version "2.4.0"
id("io.github.anschnapp.mutflow") version "<latest-version>"
}
```

### Kotlin Version Compatibility

mutflow tracks the newest Kotlin release and updates frequently. Because compiler
plugins are tightly coupled to the compiler's internal APIs, each mutflow release
requires the Kotlin version it was built against - it will not work with older
Kotlin versions in your project. If you are on an older Kotlin, use the matching
older mutflow release:

| mutflow | Kotlin |
|---------|--------|
| 1.1.0+ | 2.4.x |
| up to 1.0.3 | 2.2.x - 2.3.x |

A mismatch typically fails with an obscure compiler error (e.g. `NoClassDefFoundError`
during compilation) - if you see that, check your Kotlin version first.

Once available, the plugin automatically:
- Adds `mutflow-core` to your implementation dependencies (for `@MutationTarget` annotation)
- Adds `mutflow-junit6` to your test dependencies (for `@MutFlowTest` annotation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -473,11 +473,11 @@ class MutflowIrTransformer(
call.arguments[6] = builder.irInt(occurrenceOnLine)
}

// Generate when expression with inline check() calls - no temporary variable
// Generate when expression with inline check() calls - no temporary variable.
return IrWhenImpl(
startOffset = original.startOffset,
endOffset = original.endOffset,
type = pluginContext.irBuiltIns.booleanType,
type = original.type,
origin = null
).apply {
variants.forEachIndexed { index, variant ->
Expand Down
7 changes: 7 additions & 0 deletions mutflow-test-sample/src/main/kotlin/sample/Calculator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,11 @@ class Calculator {
fun passThroughBool(flag: Boolean): Boolean {
return flag
}

// --- Double arithmetic (regression: mutation when-wrapper must keep the
// expression's type; a Boolean-typed wrapper truncated fractional Doubles) ---

fun applyRate(amount: Double, rate: Double): Double {
return amount * rate
}
}
42 changes: 42 additions & 0 deletions mutflow-test-sample/src/test/kotlin/sample/DoubleArithmeticTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package sample

import io.github.anschnapp.mutflow.MutFlow
import io.github.anschnapp.mutflow.MutationRegistry
import io.github.anschnapp.mutflow.Selection
import io.github.anschnapp.mutflow.Shuffle
import kotlin.test.*

/**
* Regression test: mutated Double arithmetic must not lose precision.
*
* The mutation when-wrapper around IrCall nodes was typed Boolean regardless of
* the original expression's type. For Double-valued expressions the JVM backend
* then coerced the result, silently truncating fractional values even with no
* mutation active (50.0 * 0.05 returned 2.0 instead of 2.5). Every other test
* in this module uses integer-valued results, which is why only a fractional
* result catches this.
*/
class DoubleArithmeticTest {

private val calculator = Calculator()

@BeforeTest
fun setup() {
MutationRegistry.reset()
MutFlow.reset()
}

@Test
fun `fractional double result survives instrumentation outside a session`() {
// No session at all: check() returns null, so the original code path runs.
assertEquals(2.5, calculator.applyRate(50.0, 0.05))
}

@Test
fun `fractional double result survives instrumentation in baseline`() {
val result = MutFlow.underTest(run = 0, selection = Selection.MostLikelyStable, shuffle = Shuffle.PerChange) {
calculator.applyRate(50.0, 0.05)
}
assertEquals(2.5, result)
}
}