Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Rust: parallel workers report surviving mutants as KILLED (cold compile hits the timeout)

Aperta
#1 1 commento 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 3 giorni

Una pull request collegata è già stata integrata.

  • #13 di @qinyuanj — integrata

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
25/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Ferma
Stack tecnologico
python, rust

Direzione di ricerca

Read engine.py's timeout calculation and workers.py:_execute, then reproduce the reported false kills with the Rust example using default workers and --max-workers 1. The issue also raises separate Python test-discovery and mutation-operator gaps; confirm which work remains, since a linked pull request has been merged, and verify that timeouts or build failures are not counted as killed.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Summary. For a Rust crate, the default run (one worker per core) reports mutants as KILLED that actually survive. With --max-workers 1 or a large --timeout-factor, the same mutants are correctly reported as SURVIVED. cargo-mutants agrees with the single-worker result.

Version. mutator bb9262a (main, 2026-10-02), with crapper ce38952 checked out next to it. macOS 26 on arm64, 18 cores, cargo 1.95 (Homebrew), Python 3.12.

Repro. A fresh cargo new --lib repro with this src/lib.rs:

pub fn clamp_add(a: i64, b: i64, hi: i64) -> i64 {
    let s = a + b;
    if s > hi { hi } else { s }
}

pub fn is_small(x: i64) -> bool {
    x < 10 || x == 42
}

pub fn count_until(v: &[i64], stop: i64) -> usize {
    let mut n = 0;
    for &x in v {
        if x != stop {
            n += 1;
        } else {
            break;
        }
    }
    n
}

and this tests/basic.rs:

use repro::*;
#[test] fn add() { assert_eq!(clamp_add(1, 2, 100), 3); assert_eq!(clamp_add(50, 60, 100), 100); }
#[test] fn small() { assert!(is_small(3)); assert!(!is_small(20)); }
#[test] fn count() { assert_eq!(count_until(&[1, 2, 3], 9), 3); }

Results:

$ mutator --no-coverage --mutate-all src/lib.rs           # default workers
KILLED    src/lib.rs:3 > -> >=
KILLED    src/lib.rs:7 < -> <=
... 8/8 killed

$ mutator --no-coverage --mutate-all --max-workers 1 src/lib.rs
SURVIVED  src/lib.rs:3 > -> >=
SURVIVED  src/lib.rs:7 < -> <=
... 6/8 killed

$ mutator --no-coverage --mutate-all --timeout-factor 300 src/lib.rs   # default workers
2 survived (same two)

$ cargo mutants
MISSED   src/lib.rs:3:10: replace > with >= in clamp_add
MISSED   src/lib.rs:7:7: replace < with <= in is_small

Applying either mutation by hand and running cargo test passes, so the single-worker result is the correct one.

I saw the same thing on a real 282-line crate. The default run scored 48/50; 4 of those "kills" were false (!=→==, <→<=, and ||→&& in a parser). cargo-mutants and a manual check both showed the mutants surviving.

Cause (as far as I can tell).

  • engine.py sets timeout = max(2.0, seconds * timeout_factor), where seconds is the warm baseline (cargo test on an already-built tree, about 0.2–0.3 s).
  • Each worker overlay under target/mutation-workers/ gets its own cargo target dir. So every worker's first mutant compiles the crate and its deps from cold, with N workers compiling at the same time.
  • That cold build takes longer than the timeout. workers.py:_execute then returns "killed" for result.timed_out or result.code != 0. A build failure caused by the harness is also counted as a kill.

Possible fixes.

  • Share one CARGO_TARGET_DIR per worker across all of its mutants and warm it once before timing starts (cargo-mutants builds each scratch dir once, then reuses it).
  • Or time the baseline in a cold worker dir.
  • Or don't start the timeout clock until cargo test --no-run has finished: build separately, then time only the test phase.
  • Report timeouts as a separate outcome, not as killed. A build error should be "unviable" / not counted, not killed.

Two smaller things I noticed while testing:

  1. In a Python project with no pytest.ini, no [tool.pytest] and no setup.cfg, but with tests/conftest.py, the runner falls back to python -m unittest discover. That collects 0 tests and passes, so every mutant on a line that runs at import time is reported SURVIVED, and the rest are reported uncovered. A baseline that ran 0 tests should probably fail the baseline.
  2. The Rust/TS/Python operator table has no ^ >> << % & | swaps and no return-value or body replacement. For example, next_u64 in a SplitMix64 RNG gets 0 sites and shows as n/a, not as untested.
Lingua principale
Python
Stelle
55
Fork
7
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di unclebob/mutator

Tutte le issue di unclebob/mutator

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.