Rust edition
Differences from the Java edition
The two editions share their operators’ ids, their cards’ words, the event stream and the way they score. This page is only where you’d do something different in Rust. For the rest, the Java edition’s docs hold.
Installing and running
Section titled “Installing and running”- Install from a release or from source: nothing is on crates.io yet. See Installing mutastic.
- No build plugin. There’s no Maven or Gradle plugin to add: run
mutastic runin a package, orcargo mutastic run. - Your own toolchain. mutastic builds with the
cargoandrustcyou have, stable or not, from Rust 1.79 on, as the code it adds to your crate needs, and needs no JDK.
One package at a time
Section titled “One package at a time”The Java edition runs a whole multi-module build, and --tests-from brings in other modules’
tests. The Rust edition runs one Cargo package, with that package’s own tests: point it at a
workspace member’s directory, and a test in another member doesn’t count for it, even if it calls
the code. See Troubleshooting.
- What runs: unit tests in
#[cfg(test)]modules, integration tests intests/, and a binary’s tests, through libtest. Doc tests don’t run, so code only a doc test calls is not covered. A test marked#[ignore]is left out, as JUnit’s@Disabledis. - Test names are libtest’s,
tests::small_orders_pay_full_pricefor a unit test, and the target’s name first for an integration test,pricing::big_orders_get_ten_percent_offfor one intests/pricing.rs, where the Java edition writesPricingTest#smallOrdersPayFullPrice. - A test that fails with no mutant in runs again on its own, in both: one that then passes is
flaky, left out of the run and reported, and one that fails every time stops the run. What a
flaky test reaches that varies is read from source: calls such as
SystemTime::now,Instant::noworrand::thread_rng, and fields of a type likeRandomStatethat a method reads throughself. A Rust test fails with a panic, so there’s no failure chain, and none of the Java edition’s advice for a test that failed on the network or a file.
The Rust edition’s cards have the gap, the change, the inputs seen, the covering tests, the
suggested test and the commands, as the Java edition’s do. The suggested test is a #[test] fn for
the module’s mod tests, asserted with assert_eq! or assert!, and it’s written for functions
and methods a test can call with whole numbers alone: see
Suggested tests. Code no test reaches gets no starting point yet.
What’s different in the results
Section titled “What’s different in the results”- Unviable mutants. Java changes bytecode, so every mutant loads. Rust changes source, and a
few operators make changes the compiler then rejects. Those are skipped as
unviable:, with the compiler’s error, and left out of the score. See How a run works. - Crashes. A test process can die in Rust without a test failing. In safe code that counts as
caught, with no test named; near unsafe code, it’s a
RuntimeError, out of the score. Java has neither. See Results. - Unsafe code is left alone by default, and code near it is marked and run one test at a time. See Unsafe code.
- Overflow checks and debug assertions are always on in mutastic’s build, whatever your profiles say. See Profiles.
Operators
Section titled “Operators”The ids mean the same change in both editions, but a few sit in other sets, and some don’t exist in one of them:
- In
strongerhere,extendedin Java: the fouroptional/*ids andmutastic/remove-switch-arm, becauseOption,Resultandmatchare core Rust. - In
extendedhere,strongerin Java:mutastic/drop-vararg, as an array’s length is part of its type. - Not in Rust:
mutastic/flip-increment(no++),library/omit-argument(no overloading), the threepredicate/*ids, andmutastic/drop-catch-type(no multi-catch). - Only in Rust:
mutastic/remove-struct-field,library/reverse-ordering,mutastic/unwrap-try,mutastic/default-parameterandmutastic/swap-break. Of these,mutastic/unwrap-tryis in the defaults, so a Rust run’s defaults are the Java edition’s and one more.
Operators has the whole table.
Commands
Section titled “Commands”The commands and their output are the Java edition’s: the summary, the Not covered list, the
counter while mutants run, the report files, explain, ignore and --since. Where Rust differs:
explainshows the change as source,total >= 100 → (total) > (100), where the Java edition shows bytecode, and itslast runcomes from themutation.jsonof the last run of every mutant, byrunornext.nextqueues the misses as the Java edition does, in the same words and events, new misses before the baseline’s.--toptakes at most one item a function, where Java says a method. Whether the code under test is the same, so that only what the lastnextleft uncaught runs, is a hash of the files mutastic mutates with their test code (#[test]functions and#[cfg(test)]modules) taken out, as Java hashes only the classes under test. See Working through misses.- A function only the crate calls (not
pub) gets no suggested test on an input none of its callers passed: its card says no caller passed the value, and to test a caller that can, or propose the mutant unreachable. A test of it alone on that value could test what can’t happen. - A run compared with the last works as in the Java edition. A mutant that crashed where a crash proves nothing is out of the score, as a skipped one is, so a change to or from one isn’t counted against the test that caught it.
- Report files are
mutation.jsonandmutation.htmlintarget/mutastic; there’s nomutations.xml, whose consumers key on JVM mutator names. - One run across CI jobs,
--shardand--from-shards, works as in the Java edition: the same deal by id, the same folder for each share, the same refusals, and the whole run put back together. The one difference follows frommutation.jsonbeing the last run’s record here: a share writes its report files in its own folder,target/mutastic/shares/N-of-M/, beside its events, soexplainnever reads a share as the whole run. Each share makes the one build. See One run across CI jobs. mutastic.yamland rules files are the Java edition’s, with Rust paths for classes (shop::generated::**) and Rust types as owners. A rules file’s operator matches a call by its method’s name, since mutastic reads source without types; a rewrite that doesn’t fit doesn’t compile, and is withdrawn. See Configuration.- The gate is the Java edition’s, so the coding agents’ plugins run it unchanged. A test it
counts is a
#[test]-like function insrcortests, and one turned off is#[ignore], where Java counts@Testand@Disabled. - Skipped as arid: a statement that only writes output,
println!,eprintln!,dbg!or a logger’s macro, where the Java edition skips a call to a logger orSystem.out. - Skipped as equivalent: a
?that only passes on a formatter’s error, inside thefmtofDisplayand the other formatting traits, or on a write to aFormatter: no test can make that error happen. Also a change to a zero check on aNonZero*value that differs only at 0:parts.get() == 0made never true, wherepartsis aNonZeroU32, which can’t hold 0. And a change to a zero check on an unsigned value that differs only below 0,n == 0maden <= 0, wheren’s type is writtenusizeor it’s cast to one; the Java edition takes lengths and counts from the JDK. Andcfg!(debug_assertions)forced true, which it always is under mutastic’s build. Both editions skip a boundary inside a guard that its sides differ,a < bmade<=insideif a != b; Rust also takes a field of a binding that can’t change and thexof aSome(x)(ver.major > cmp.majorinsideif ver.major != cmp.major), where Java takes onlyintandlonglocals.
Not in the Rust edition yet
Section titled “Not in the Rust edition yet”These Java edition features have no Rust counterpart yet. Where you’d use one, here’s what to do instead:
| In the Java edition | In the Rust edition, for now |
|---|---|
Compiled plugins, and filters: in mutastic.yaml | A rules file, for call operators: Configuration |
@DoNotMutate | mutastic ignore, one mutant at a time: Configuration |
| A suggested starting point for code no test reaches | The Not covered list, which names each function and its lines |