#![doc = include_str!("../README.md")]
#![no_std]
#![deny(missing_docs)]
#![deny(rustdoc::broken_intra_doc_links)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "std")]
#[macro_use]
extern crate std;
pub mod _guide;
pub mod error;
mod log;
pub mod mutators;
mod rng;
use core::ops;
pub use error::{Error, Result};
pub use rng::Rng;
#[cfg(feature = "check")]
pub mod check;
#[cfg(feature = "derive")]
pub use mutatis_derive::Mutate;
#[derive(Debug)]
pub struct Session {
context: Context,
}
impl Default for Session {
fn default() -> Self {
Self::new()
}
}
impl Session {
pub fn new() -> Self {
Self {
context: Context {
rng: Rng::default(),
shrink: false,
generate_via_mutate_depth: 0,
},
}
}
pub fn seed(mut self, seed: u64) -> Self {
self.context.rng = Rng::new(seed);
self
}
pub fn shrink(mut self, shrink: bool) -> Self {
self.context.shrink = shrink;
self
}
pub fn mutate<T>(&mut self, value: &mut T) -> Result<()>
where
T: DefaultMutate,
{
self.context.mutate(value)
}
pub fn mutate_with<T>(
&mut self,
mutator: &mut (impl Mutate<T> + ?Sized),
value: &mut T,
) -> Result<()> {
self.context.mutate_with(mutator, value)
}
pub fn generate<T>(&mut self) -> Result<T>
where
T: DefaultMutate,
T::DefaultMutate: Generate<T>,
{
let mut generator = T::DefaultMutate::default();
generator.generate(&mut self.context)
}
pub fn generate_with<T>(&mut self, generator: &mut impl Generate<T>) -> Result<T> {
generator.generate(&mut self.context)
}
}
#[derive(Debug)]
pub struct Context {
rng: Rng,
shrink: bool,
generate_via_mutate_depth: u32,
}
impl Context {
#[inline]
#[must_use]
pub fn rng(&mut self) -> &mut Rng {
&mut self.rng
}
#[inline]
#[must_use]
pub fn shrink(&self) -> bool {
self.shrink
}
#[inline]
pub(crate) fn mutate<T>(&mut self, value: &mut T) -> Result<()>
where
T: DefaultMutate,
{
let mut mutator = mutators::default::<T>();
self.mutate_with(&mut mutator, value)
}
#[inline]
pub(crate) fn mutate_with<T>(
&mut self,
mutator: &mut (impl Mutate<T> + ?Sized),
value: &mut T,
) -> Result<()> {
if let Some(count) = mutator.mutation_count(value, self.shrink) {
self.apply_mutation(value, count, |c, value| mutator.mutate(c, value))
} else {
self.choose_and_apply_mutation(value, |c, value| mutator.mutate(c, value))
}
}
fn apply_mutation<T>(
&mut self,
value: &mut T,
count: u32,
mut mutate_impl: impl FnMut(&mut Candidates, &mut T) -> Result<()>,
) -> Result<()> {
log::trace!("=== applying mutation (fast path, count={count}) ===");
let count_usize = usize::try_from(count).unwrap();
if count_usize == 0 {
log::trace!("mutator exhausted");
return Err(Error::exhausted());
}
let target = self.rng().gen_index(count_usize).unwrap();
log::trace!("targeting mutation {target}");
debug_assert!(target < count_usize);
let mut candidates = Candidates {
context: self,
phase: Phase::mutate(u32::try_from(target).unwrap()),
applied_mutation: false,
};
match mutate_impl(&mut candidates, value) {
Err(e) if e.is_early_exit() => {
log::trace!("mutation applied successfully");
Ok(())
}
Err(e) => {
log::error!("failed to apply mutation: {e}");
Err(e)
}
Ok(()) if candidates.applied_mutation => {
panic!(
"We applied a mutation but did not receive an early-exit error \
from the mutator. This means that errors are not always being \
propagated, for example a `?` is missing from a call to the \
`Candidates::mutation` method.",
)
}
Ok(()) => {
let found = candidates.phase.current;
panic!(
"Mutation count mismatch: expected {count} candidates but \
mutate only enumerated {found}. Ensure mutation_count and \
mutate agree, and that mutate is deterministic.",
)
}
}
}
fn choose_and_apply_mutation<T>(
&mut self,
value: &mut T,
mut mutate_impl: impl FnMut(&mut Candidates, &mut T) -> Result<()>,
) -> Result<()> {
log::trace!("=== choosing and applying a mutation ===");
let count = {
let mut candidates = Candidates {
context: self,
phase: Phase::counting(),
applied_mutation: false,
};
mutate_impl(&mut candidates, value)?;
candidates.phase.current
};
log::trace!("counted {count} mutations");
self.apply_mutation(value, count, mutate_impl)
}
#[inline]
pub(crate) fn mutate_in_range_with<T>(
&mut self,
mutator: &mut impl MutateInRange<T>,
value: &mut T,
range: &ops::RangeInclusive<T>,
) -> Result<()> {
self.choose_and_apply_mutation(value, |c, value| mutator.mutate_in_range(c, value, range))
}
const MAX_DEPTH: u32 = 8;
pub(crate) fn with_generate_via_mutate_scope(
&mut self,
mut f: impl FnMut(&mut Self) -> Result<()>,
) -> Result<()> {
self.generate_via_mutate_depth += 1;
let result = if !self.shrink() && self.generate_via_mutate_depth < Self::MAX_DEPTH {
f(self)
} else {
Ok(())
};
self.generate_via_mutate_depth -= 1;
result
}
}
#[derive(Clone)]
struct Phase {
current: u32,
target: u32,
}
impl Phase {
fn counting() -> Self {
Phase {
current: 0,
target: u32::MAX,
}
}
fn mutate(target: u32) -> Self {
Phase { current: 0, target }
}
}
pub struct Candidates<'a> {
context: &'a mut Context,
phase: Phase,
applied_mutation: bool,
}
impl<'a> Candidates<'a> {
#[inline]
pub fn mutation(&mut self, mut f: impl FnMut(&mut Context) -> Result<()>) -> Result<()> {
let idx = self.phase.current;
self.phase.current = idx + 1;
if idx == self.phase.target {
self.applied_mutation = true;
f(&mut self.context)?;
Err(Error::early_exit())
} else {
Ok(())
}
}
#[inline]
pub fn mutation_group(
&mut self,
count: u32,
f: impl FnOnce(&mut Context, u32) -> Result<()>,
) -> Result<()> {
let base = self.phase.current;
self.phase.current = base + count;
if self.phase.target.wrapping_sub(base) < count {
self.applied_mutation = true;
f(&mut self.context, self.phase.target - base)?;
Err(Error::early_exit())
} else {
Ok(())
}
}
pub fn shrink(&self) -> bool {
self.context.shrink()
}
}
pub trait Mutate<T>
where
T: ?Sized,
{
fn mutate(&mut self, mutations: &mut Candidates<'_>, value: &mut T) -> Result<()>;
#[inline]
fn mutation_count(&self, value: &T, shrink: bool) -> Option<u32> {
let _ = (value, shrink);
None
}
fn generate_via_mutate(&mut self, context: &mut Context, iters: usize) -> Result<T>
where
T: Sized + Default,
{
let mut value = T::default();
context.with_generate_via_mutate_scope(|context| {
for _ in 0..iters {
context.mutate_with(self, &mut value)?;
}
Ok(())
})?;
Ok(value)
}
fn or<M>(self, other: M) -> mutators::Or<Self, M>
where
Self: Sized,
{
mutators::Or {
left: self,
right: other,
}
}
#[inline]
#[must_use = "mutator combinators do nothing until you call their `mutate` method"]
fn map<F>(self, f: F) -> mutators::Map<Self, F>
where
Self: Sized,
F: FnMut(&mut Context, &mut T) -> Result<()>,
{
mutators::Map { mutator: self, f }
}
#[inline]
#[must_use = "mutator combinators do nothing until you call their `mutate` method"]
fn proj<F, U>(self, f: F) -> mutators::Proj<Self, F>
where
Self: Sized,
F: FnMut(&mut U) -> &mut T,
{
mutators::Proj { mutator: self, f }
}
#[inline]
#[must_use = "mutator combinators do nothing until you call their `mutate` method"]
fn by_ref(&mut self) -> &mut Self
where
Self: Sized,
{
self
}
}
#[cfg(test)]
mod tests {
use super::*;
const ITERS: usize = 10_000;
const EXPECTED: usize = ITERS / 4;
const TOLERANCE: usize = ITERS / 20;
fn assert_uniform(counts: &[usize; 4], label: &str) {
for (i, &count) in counts.iter().enumerate() {
assert!(
count.abs_diff(EXPECTED) <= TOLERANCE,
"{label} {i} was chosen {count} times (expected ~{EXPECTED}, \
tolerance ±{TOLERANCE}); mutation distribution is not uniform",
);
}
}
#[derive(Clone, Copy)]
enum FourVariants {
A,
B,
C,
D,
}
struct FourVariantsMutator;
impl Mutate<FourVariants> for FourVariantsMutator {
fn mutate(&mut self, c: &mut Candidates<'_>, value: &mut FourVariants) -> Result<()> {
c.mutation(|_| {
*value = FourVariants::A;
Ok(())
})?;
c.mutation(|_| {
*value = FourVariants::B;
Ok(())
})?;
c.mutation(|_| {
*value = FourVariants::C;
Ok(())
})?;
c.mutation(|_| {
*value = FourVariants::D;
Ok(())
})?;
Ok(())
}
}
#[test]
fn enum_mutation_is_uniform() {
let mut session = Session::new();
let mut value = FourVariants::A;
let mut counts = [0usize; 4];
let mut mutator = FourVariantsMutator;
for _ in 0..ITERS {
session.mutate_with(&mut mutator, &mut value).unwrap();
counts[match value {
FourVariants::A => 0,
FourVariants::B => 1,
FourVariants::C => 2,
FourVariants::D => 3,
}] += 1;
}
assert_uniform(&counts, "variant");
}
struct FourFields {
a: bool,
b: bool,
c: bool,
d: bool,
}
#[derive(Default)]
struct FourFieldsMutator {
a: mutators::Bool,
b: mutators::Bool,
c: mutators::Bool,
d: mutators::Bool,
}
impl Mutate<FourFields> for FourFieldsMutator {
fn mutate(&mut self, c: &mut Candidates<'_>, value: &mut FourFields) -> Result<()> {
self.a.mutate(c, &mut value.a)?;
self.b.mutate(c, &mut value.b)?;
self.c.mutate(c, &mut value.c)?;
self.d.mutate(c, &mut value.d)?;
Ok(())
}
}
#[test]
fn struct_mutation_is_uniform() {
let mut session = Session::new();
let mut mutator = FourFieldsMutator::default();
let mut counts = [0usize; 4];
for _ in 0..ITERS {
let mut value = FourFields {
a: false,
b: false,
c: false,
d: false,
};
session.mutate_with(&mut mutator, &mut value).unwrap();
if value.a {
counts[0] += 1;
}
if value.b {
counts[1] += 1;
}
if value.c {
counts[2] += 1;
}
if value.d {
counts[3] += 1;
}
}
assert_uniform(&counts, "field");
}
struct NestedAbcd {
a: bool,
bcd: NestedBcd,
}
struct NestedBcd {
b: bool,
cd: NestedCd,
}
struct NestedCd {
c: bool,
d: bool,
}
#[derive(Default)]
struct NestedCdMutator {
c: mutators::Bool,
d: mutators::Bool,
}
#[derive(Default)]
struct NestedBcdMutator {
b: mutators::Bool,
cd: NestedCdMutator,
}
#[derive(Default)]
struct NestedAbcdMutator {
a: mutators::Bool,
bcd: NestedBcdMutator,
}
impl Mutate<NestedCd> for NestedCdMutator {
fn mutate(&mut self, c: &mut Candidates<'_>, value: &mut NestedCd) -> Result<()> {
self.c.mutate(c, &mut value.c)?;
self.d.mutate(c, &mut value.d)?;
Ok(())
}
}
impl Mutate<NestedBcd> for NestedBcdMutator {
fn mutate(&mut self, c: &mut Candidates<'_>, value: &mut NestedBcd) -> Result<()> {
self.b.mutate(c, &mut value.b)?;
self.cd.mutate(c, &mut value.cd)?;
Ok(())
}
}
impl Mutate<NestedAbcd> for NestedAbcdMutator {
fn mutate(&mut self, c: &mut Candidates<'_>, value: &mut NestedAbcd) -> Result<()> {
self.a.mutate(c, &mut value.a)?;
self.bcd.mutate(c, &mut value.bcd)?;
Ok(())
}
}
#[test]
fn nested_struct_mutation_is_uniform() {
let mut session = Session::new();
let mut mutator = NestedAbcdMutator::default();
let mut counts = [0usize; 4];
for _ in 0..ITERS {
let mut value = NestedAbcd {
a: false,
bcd: NestedBcd {
b: false,
cd: NestedCd { c: false, d: false },
},
};
session.mutate_with(&mut mutator, &mut value).unwrap();
if value.a {
counts[0] += 1;
}
if value.bcd.b {
counts[1] += 1;
}
if value.bcd.cd.c {
counts[2] += 1;
}
if value.bcd.cd.d {
counts[3] += 1;
}
}
assert_uniform(&counts, "field");
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum EnumAbcd {
A,
Bcd(EnumBcd),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum EnumBcd {
B,
Cd(EnumCd),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum EnumCd {
C,
D,
}
struct EnumCdMutator;
impl Mutate<EnumCd> for EnumCdMutator {
fn mutate(&mut self, c: &mut Candidates<'_>, value: &mut EnumCd) -> Result<()> {
c.mutation(|_| {
*value = EnumCd::C;
Ok(())
})?;
c.mutation(|_| {
*value = EnumCd::D;
Ok(())
})?;
Ok(())
}
}
struct EnumBcdMutator {
cd: EnumCdMutator,
}
impl Mutate<EnumBcd> for EnumBcdMutator {
fn mutate(&mut self, c: &mut Candidates<'_>, value: &mut EnumBcd) -> Result<()> {
c.mutation(|_| {
*value = EnumBcd::B;
Ok(())
})?;
match value {
EnumBcd::B => {
c.mutation(|_| {
*value = EnumBcd::Cd(EnumCd::C);
Ok(())
})?;
c.mutation(|_| {
*value = EnumBcd::Cd(EnumCd::D);
Ok(())
})?;
}
EnumBcd::Cd(cd) => {
self.cd.mutate(c, cd)?;
}
}
Ok(())
}
}
struct EnumAbcdMutator {
bcd: EnumBcdMutator,
}
impl Mutate<EnumAbcd> for EnumAbcdMutator {
fn mutate(&mut self, c: &mut Candidates<'_>, value: &mut EnumAbcd) -> Result<()> {
c.mutation(|_| {
*value = EnumAbcd::A;
Ok(())
})?;
match value {
EnumAbcd::A => {
c.mutation(|_| {
*value = EnumAbcd::Bcd(EnumBcd::B);
Ok(())
})?;
c.mutation(|_| {
*value = EnumAbcd::Bcd(EnumBcd::Cd(EnumCd::C));
Ok(())
})?;
c.mutation(|_| {
*value = EnumAbcd::Bcd(EnumBcd::Cd(EnumCd::D));
Ok(())
})?;
}
EnumAbcd::Bcd(bcd) => {
self.bcd.mutate(c, bcd)?;
}
}
Ok(())
}
}
#[test]
fn nested_enum_mutation_is_uniform() {
let mut session = Session::new();
let mut mutator = EnumAbcdMutator {
bcd: EnumBcdMutator { cd: EnumCdMutator },
};
let mut counts = [0usize; 4];
for _ in 0..ITERS {
let mut value = EnumAbcd::Bcd(EnumBcd::Cd(EnumCd::D));
session.mutate_with(&mut mutator, &mut value).unwrap();
counts[match value {
EnumAbcd::A => 0,
EnumAbcd::Bcd(EnumBcd::B) => 1,
EnumAbcd::Bcd(EnumBcd::Cd(EnumCd::C)) => 2,
EnumAbcd::Bcd(EnumBcd::Cd(EnumCd::D)) => 3,
}] += 1;
}
assert_uniform(&counts, "variant");
}
#[derive(Clone, Copy)]
enum FourGroupAlts {
A,
B,
C,
D,
}
struct FourGroupAltsMutator;
impl Mutate<FourGroupAlts> for FourGroupAltsMutator {
fn mutate(&mut self, c: &mut Candidates<'_>, value: &mut FourGroupAlts) -> Result<()> {
c.mutation_group(4, |_ctx, which| {
*value = match which {
0 => FourGroupAlts::A,
1 => FourGroupAlts::B,
2 => FourGroupAlts::C,
_ => FourGroupAlts::D,
};
Ok(())
})?;
Ok(())
}
}
#[test]
fn mutation_group_is_uniform() {
let mut session = Session::new();
let mut value = FourGroupAlts::A;
let mut counts = [0usize; 4];
let mut mutator = FourGroupAltsMutator;
for _ in 0..ITERS {
session.mutate_with(&mut mutator, &mut value).unwrap();
counts[match value {
FourGroupAlts::A => 0,
FourGroupAlts::B => 1,
FourGroupAlts::C => 2,
FourGroupAlts::D => 3,
}] += 1;
}
assert_uniform(&counts, "mutation_group alt");
}
struct MixedGroupMutator;
impl Mutate<FourGroupAlts> for MixedGroupMutator {
fn mutate(&mut self, c: &mut Candidates<'_>, value: &mut FourGroupAlts) -> Result<()> {
c.mutation_group(2, |_ctx, which| {
*value = match which {
0 => FourGroupAlts::A,
_ => FourGroupAlts::B,
};
Ok(())
})?;
c.mutation(|_ctx| {
*value = FourGroupAlts::C;
Ok(())
})?;
c.mutation(|_ctx| {
*value = FourGroupAlts::D;
Ok(())
})?;
Ok(())
}
}
#[test]
fn mutation_group_mixed_with_individual_is_uniform() {
let mut session = Session::new();
let mut value = FourGroupAlts::A;
let mut counts = [0usize; 4];
let mut mutator = MixedGroupMutator;
for _ in 0..ITERS {
session.mutate_with(&mut mutator, &mut value).unwrap();
counts[match value {
FourGroupAlts::A => 0,
FourGroupAlts::B => 1,
FourGroupAlts::C => 2,
FourGroupAlts::D => 3,
}] += 1;
}
assert_uniform(&counts, "mixed group alt");
}
}
fn _static_assert_object_safety(
_: &dyn Mutate<u8>,
_: &dyn Generate<u8>,
_: &dyn MutateInRange<u8>,
) {
}
impl<M, T> Mutate<T> for &mut M
where
M: Mutate<T>,
{
fn mutate(&mut self, c: &mut Candidates, value: &mut T) -> Result<()> {
(**self).mutate(c, value)
}
#[inline]
fn mutation_count(&self, value: &T, shrink: bool) -> Option<u32> {
(**self).mutation_count(value, shrink)
}
}
impl<M, T> Generate<T> for &mut M
where
M: Generate<T>,
{
fn generate(&mut self, context: &mut Context) -> Result<T> {
(**self).generate(context)
}
}
pub trait DefaultMutate {
type DefaultMutate: Mutate<Self> + Default;
}
pub trait Generate<T>: Mutate<T> {
fn generate(&mut self, context: &mut Context) -> Result<T>;
}
pub trait MutateInRange<T>: Mutate<T> {
fn mutate_in_range(
&mut self,
mutations: &mut Candidates,
value: &mut T,
range: &ops::RangeInclusive<T>,
) -> Result<()>;
}