Multiplatform mutation runtime and new operators - #14
Conversation
Convert mutflow from JVM-only to a multiplatform mutation-testing framework targeting JVM, Kotlin/JS, Kotlin/WASM, and Kotlin/Native: - Move core, runtime, and annotations sources to commonMain/commonTest - Add expect/actual primitives (concurrent map, thread id) to mutflow-core - Add wasmJs target across all KMP modules - Wire the compiler plugin into every compilation via the Gradle plugin Add mutation operators previously marked planned in the catalog: - ReferenceEquality (=== <-> !==) - Elvis (a ?: b -> a / b) and SafeCall (a?.b -> a!!.b), with block-origin detection for common IR - EmptyCollectionReturn (collection return -> emptyList/emptySet/emptyMap) - AssignConst (a = b -> a = default), via a new assignment visitor path - % -> * and xor -> or arithmetic/bitwise variants Add the mutflow-test-kmp module to prove injection works on all four targets.
Generated by the Kotlin Gradle plugin for the js()/wasmJs() targets added in the multiplatform conversion. Kotlin/JS recommends committing this for reproducible builds.
Add a multiplatform mutation inspector that runs the mutated KMP binary
against a test battery on every target (JVM, JS, WASM, Native) and
aggregates per-platform results into one HTML dashboard.
- PlatformInspectorTest (commonTest) discovers every mutation point and
classifies each point x variant as killed/survived, writing JSON per
platform via PlatformInspectorActual.{jvm,js,wasm,native}.
- tools/mutflow-inspect/inspect-all.sh builds all targets, runs the
inspector on each, and renders report-all.html.
- Fix ConstructorCallOperator to skip Regex(...) constructor calls:
replacing a Regex with null makes the subsequent .containsMatchIn() a
null-deref that is a catchable NPE on JVM/JS but an uncatchable
segfault on Kotlin/Native and Kotlin/Wasm, killing the whole test
process.
- ArgumentPropagationOperator: derive value-arg indices from each parameter's actual kind instead of a single offset heuristic, so calls with both a dispatch and extension receiver, or context parameters, no longer mutate the wrong argument slot. - inspect-all.sh: guard the kill-rate division so a platform with zero discovered variants doesn't crash the whole aggregation. - Remove BinaryInspector.java/inspect.sh: a JVM-only duplicate of the battery/kill logic already covered by PlatformInspectorTest.kt + inspect-all.sh (which includes JVM as one of its four targets). - ConstructorCallOperator: move the Native/Wasm uncatchable-segfault workaround into a documented, extensible set of unsafe types instead of a single inline FQN comparison. - PlatformInspectorTest: stop running the baseline battery twice. - PlatformInspectorActual (native): surface mkdir/open failures instead of silently skipping the write. - PlatformInspectorActual (wasm): check existsSync before mkdirSync instead of catching and discarding every exception, so real mkdir failures are no longer masked as "already exists".
Declare macosArm64() alongside linuxX64() in every KMP module (annotations, core, runtime, test-kmp) so the library and its mutation compiler plugin also build and test on Apple Silicon natively, instead of only cross-compiling on Linux CI. For mutflow-test-kmp's binary inspector, split the native actuals: the POSIX-based writeResultsFile now lives in a shared nativeTest source set (used by both linuxX64 and macosArm64 via the default hierarchy template), while currentPlatform() stays a per-leaf-target actual. open()'s/mkdir()'s mode_t argument uses .convert() instead of a fixed .toUInt(), since mode_t is UShort on Darwin but UInt on Linux. inspect-all.sh now builds, runs, and aggregates the macosArm64 target too; whichever native target doesn't match the current host is simply skipped by Kotlin/Native rather than failing the run. kotlin.native.ignoreDisabledTargets=true suppresses the resulting "target disabled" warning now that two native targets are declared.
Add multiplatform binary inspector and HTML dashboard
|
Hi @iho, thank you very much for your contribution! I tried to read and understand all the changes as well as I could. But I am I hope it is not too disappointing that we cannot merge the whole PR. In general, smaller PRs (one feature each) are much easier to review and I would like to stick with the design in
But there is a lot in this PR I would love to get into master:
These operators, in one PR: reference equality, unary minus, bitwise, The operators I left out are either touching a deliberate decision ( A discussion ticket about opt-in operators. If we find a good way to make And help checking the It would be great to find a way to bring these things in together, it could give Let me know what you think. My suggestion would be to close this PR after some |
Summary
Convert mutflow from JVM-only to a multiplatform mutation-testing framework targeting JVM, Kotlin/JS, Kotlin/WASM, and Kotlin/Native:
commonMain/commonTest, withexpect/actualprimitives (concurrent map, thread id) added tomutflow-core, awasmJstarget added across all KMP modules, and the compiler plugin wired into every compilation via the Gradle plugin. Adds operators previously marked planned in the catalog:ReferenceEquality(===/!==),Elvis/SafeCall(with block-origin detection for common IR),EmptyCollectionReturn,AssignConst(via a new assignment visitor path), and%→*/xor→orarithmetic/bitwise variants.PlatformInspectorTest(commonTest) discovers every mutation point and classifies each point × variant as killed/survived, writing JSON per platform viaPlatformInspectorActual.{jvm,js,wasm,native};tools/mutflow-inspect/inspect-all.shbuilds all targets, runs the inspector on each, and rendersreport-all.html. Also fixesConstructorCallOperatorto skipRegex(...)constructor calls (a null-deref that is a catchable NPE on JVM/JS but an uncatchable segfault on Kotlin/Native and Kotlin/Wasm).ArgumentPropagationOperatorderives value-arg indices from each parameter's actual kind (handles dispatch+extension receiver / context-parameter combos);inspect-all.shguards the kill-rate division against zero variants; removes the JVM-onlyBinaryInspector.java/inspect.shduplicate; moves the Native/Wasm segfault workaround into a documented unsafe-types set; stops running the baseline battery twice; native actual surfaces mkdir/open failures; WASM actual checksexistsSyncinstead of swallowing all exceptions.linuxX64()in every KMP module so the library and plugin build/test on Apple Silicon natively. The POSIXwriteResultsFilemoves to a sharednativeTestsource set;mode_tuses.convert()(UShort on Darwin vs UInt on Linux);inspect-all.shaggregates macosArm64 too, with the non-host native target skipped rather than failing;kotlin.native.ignoreDisabledTargets=truesuppresses the target-disabled warning.Test plan
./gradlew build— full build across all modules and targets, includingmutflow-test-kmp:allTestson JVM/JS/WASM — passes locallymutflow-test-kmp:allTestson linuxX64 — passes locally (macosArm64 is skipped on a Linux host)tools/mutflow-inspect/inspect-all.sh— renders the dashboard for all runnable targets (jvm/js/wasmJs/linuxX64 at 37/44 killed; macosArm64 shows "no results" on Linux)