Skip to content
mutastic

Rust edition

Arithmetic

Operators that change a calculation: an operator, a number, a sign, or the bits. They ask whether your tests use inputs where the answer would come out differently.

OperatorChangesSet
mutastic/math+ and -, * and /, and their checked_, wrapping_, saturating_ and overflowing_ methodsdefaults
mutastic/arithmetic-replacement+ to *stronger
mutastic/other-arithmetican arithmetic operator to each of the othersstronger
mutastic/keep-operanda + b to a, and to bstronger
mutastic/remove-negation-x to xstronger
mutastic/constant-valuea number to the ones next to it, 0, 1 and -1stronger
mutastic/bitwise&, | and ^; << and >>stronger
library/unchecked-arithmeticchecked_add to a wrapping_add that can’t failstronger

Each card below comes from mutastic run fixtures/operators with these operators, on src/arithmetic.rs. A line with several missed mutants gets one card, written about the first, with the others under also.

Overflow checks are always on in mutastic’s build, so an arithmetic mutant that overflows panics, and the test that runs it fails: see Profiles.

mutastic/math, in the defaults. + becomes -, * becomes /, and the other way round, in compound assignments too (+= and -=). So do the methods: checked_add and checked_sub, checked_mul and checked_div, and likewise for wrapping_, saturating_ and overflowing_.

The bug it stands for: the wrong operator in a calculation: - for +, / for *. It costs in money, quantities and time. A wrong calculation is often silent: nothing panics, the number is just wrong, and it’s found later on an invoice or in a report.

pub fn line_total(price: u32, quantity: u32) -> u32 {
price * quantity
}

The test buys one, and 30 * 1 is 30 / 1:

#[test]
fn one_of_each() {
assert_eq!(line_total(30, 1), 30);
}
Terminal window
✗ MISSED tests pass even when * becomes / in line_total() · m7c2deaec
┌─ src/arithmetic.rs:4
│
3 │ pub fn line_total(price: u32, quantity: u32) -> u32 {
4 │ price * quantity
│ ^ * changed to /
5 │ }
│
= mutant - price * quantity
+ price / quantity
= also ✗ tests pass even when price * quantity becomes price in line_total() · m9fbcbdb2
+ price
= note: inputs seen: price = 30; quantity = 1
covered by arithmetic::tests::one_of_each (its 1 covering test still passes)
= help: use inputs where price * quantity and price / quantity differ, and assert the result
// in mod arithmetic::tests
#[test]
fn line_total_with_price_30_and_quantity_30() {
assert_eq!(line_total(30, 30), 900); // today 900 · with m7c2deaec 1
}
check today's value is the one you intend: the test pins what the code does now
done when mutastic verify m7c2deaec,m9fbcbdb2 fixtures/operators passes
reproduce mutastic run --only=m7c2deaec,m9fbcbdb2 fixtures/operators

The test that catches it: one whose values tell the operators apart. A quantity of 1 gives the same answer for * and /, and adding 0 the same for + and -: those are the values a quick test tends to pick.

#[test]
fn two_of_each() {
assert_eq!(line_total(30, 2), 60);
}

Edition notes: the Java edition has this operator too, mutastic/math, on int and long.

mutastic/arithmetic-replacement, in the stronger set. a + b becomes a * b, and += becomes *=, but not += 1, which would only stop a counter.

The bug it stands for: one operation mixed up with another entirely: adding where it should multiply. Its real value is as a check on the tests’ values: 2 + 2 == 2 * 2 and 0 + 0 == 0 * 0 are the numbers quick tests pick.

pub fn with_fee(price: u32, fee: u32) -> u32 {
price + fee
}
#[test]
fn a_fee_is_added() {
assert_eq!(with_fee(2, 2), 4);
}
Terminal window
✗ MISSED tests pass even when + becomes * in with_fee()
┌─ src/arithmetic.rs:8
│
7 │ pub fn with_fee(price: u32, fee: u32) -> u32 {
8 │ price + fee
│ ^ + changed to *
9 │ }
│
= mutant - price + fee
+ price * fee
= note: inputs seen: fee = 2; price = 2
covered by arithmetic::tests::a_fee_is_added (its 1 covering test still passes)
= help: use inputs where price + fee and price * fee differ, and assert the result
// in mod arithmetic::tests
#[test]
fn with_fee_with_price_2_and_fee_0() {
assert_eq!(with_fee(2, 0), 2); // today 2 · with this mutant 0
}
check today's value is the one you intend: the test pins what the code does now
done when mutastic verify m2159bc78 fixtures/operators passes
reproduce mutastic run --only=m2159bc78 fixtures/operators

The test that catches it: one with ordinary values, where the sum and the product are far apart.

#[test]
fn a_fee_of_three() {
assert_eq!(with_fee(10, 3), 13);
}

Edition notes: the Java edition has this operator too, mutastic/arithmetic-replacement.

mutastic/other-arithmetic, in the stronger set. Each of + - * / % becomes each of the others that math and arithmetic replacement don’t make: + to / and %; - to *, / and %; * and / to +, - and %; % to the four others. Compound assignments too.

The bug it stands for: any operator mixed up with another: % for /, a - where a * belongs. It costs in rates, ratios, averages and paging, where the wrong operator gives a plausible number.

pub fn per_person(bill: u32, people: u32) -> u32 {
bill / people
}
#[test]
fn a_bill_is_shared() {
assert_eq!(per_person(4, 2), 2);
}

4 / 2 and 4 - 2 are both 2:

Terminal window
✗ MISSED tests pass even when / becomes - in per_person() · mea866977
┌─ src/arithmetic.rs:12
│
11 │ pub fn per_person(bill: u32, people: u32) -> u32 {
12 │ bill / people
│ ^ / changed to -
13 │ }
│
= mutant - bill / people
+ bill - people
= also ✗ tests pass even when bill / people becomes people in per_person() · m82fdb48b
+ people
= note: inputs seen: bill = 4; people = 2
covered by arithmetic::tests::a_bill_is_shared (its 1 covering test still passes)
= help: use inputs where bill / people and bill - people differ, and assert the result
// in mod arithmetic::tests
#[test]
fn per_person_with_bill_4_and_people_4() {
assert_eq!(per_person(4, 4), 1); // today 1 · with mea866977 0
}
check today's value is the one you intend: the test pins what the code does now
done when mutastic verify mea866977,m82fdb48b fixtures/operators passes
reproduce mutastic run --only=mea866977,m82fdb48b fixtures/operators

The test that catches it: one with values that tell every operator apart, and neither 0, 1 nor two values the same.

#[test]
fn twelve_shared_by_four() {
assert_eq!(per_person(12, 4), 3);
}

Edition notes: the Java edition has this operator too, mutastic/other-arithmetic, on float and double too.

mutastic/keep-operand, in the stronger set. A calculation becomes one of its values: price + fee becomes price, and fee. The other side is still worked out when working it out could do something, such as call a function. A constant beside a value is never what’s kept.

The bug it stands for: a part of a calculation left out: the fee not added, the refund not taken off, the offset forgotten. It costs in totals of several parts, where tests set the one being looked at and leave the rest at 0.

pub fn after_refund(paid: u32, refund: u32) -> u32 {
paid - refund
}
#[test]
fn nothing_refunded() {
assert_eq!(after_refund(10, 0), 10);
}

A refund of 0 can’t tell paid - refund from paid, nor from paid + refund, so the line’s card is math’s, with keep operand’s under it:

Terminal window
✗ MISSED tests pass even when - becomes + in after_refund() · m4e284c1b
┌─ src/arithmetic.rs:16
│
15 │ pub fn after_refund(paid: u32, refund: u32) -> u32 {
16 │ paid - refund
│ ^ - changed to +
17 │ }
│
= mutant - paid - refund
+ paid + refund
= also ✗ tests pass even when paid - refund becomes paid in after_refund() · mb46190a0
+ paid
= note: inputs seen: paid = 10; refund = 0
covered by arithmetic::tests::nothing_refunded (its 1 covering test still passes)
= help: use inputs where paid - refund and paid + refund differ, and assert the result
// in mod arithmetic::tests
#[test]
fn after_refund_with_paid_10_and_refund_10() {
assert_eq!(after_refund(10, 10), 0); // today 0 · with m4e284c1b 20
}
check today's value is the one you intend: the test pins what the code does now
done when mutastic verify m4e284c1b,mb46190a0 fixtures/operators passes
reproduce mutastic run --only=m4e284c1b,mb46190a0 fixtures/operators

The test that catches it: one where each part has a value that changes the result.

#[test]
fn a_refund_is_taken_off() {
assert_eq!(after_refund(10, 3), 7);
}

Edition notes: the Java edition has this operator too, mutastic/keep-operand.

mutastic/remove-negation, in the stronger set. -x becomes x.

The bug it stands for: a missing sign: a refund that adds, a delta applied the wrong way round. It costs in money and coordinates, where a value and its opposite are both plausible, and a test that only uses zero can’t tell them apart.

pub fn reversed(balance: i32) -> i32 {
-balance
}
#[test]
fn an_empty_balance_reversed() {
assert_eq!(reversed(0), 0);
}
Terminal window
✗ MISSED tests pass even when -balance becomes balance in reversed()
┌─ src/arithmetic.rs:20
│
19 │ pub fn reversed(balance: i32) -> i32 {
20 │ -balance
│ ^ changed to balance
21 │ }
│
= mutant - -balance
+ balance
= note: inputs seen: balance = 0
covered by arithmetic::tests::an_empty_balance_reversed (its 1 covering test still passes)
= help: use inputs where -balance and balance differ, and assert what reversed() does
// in mod arithmetic::tests
#[test]
fn reversed_with_balance_1() {
assert_eq!(reversed(1), -1); // today -1 · with this mutant 1
}
check today's value is the one you intend: the test pins what the code does now
done when mutastic verify me0933ca7 fixtures/operators passes
reproduce mutastic run --only=me0933ca7 fixtures/operators

The test that catches it: one with a value that isn’t zero, asserting the sign of the result.

#[test]
fn a_balance_reversed() {
assert_eq!(reversed(5), -5);
}

Edition notes: the Java edition has this operator too, mutastic/remove-negation.

mutastic/constant-value, in the stronger set. A whole number becomes the numbers next to it, 0 and 1, and -1 where the type is plainly signed. Never a number outside its type’s range, and never one that only sizes something, sets a wait, or starts a counter at 0 or 1.

The bug it stands for: a number set wrong: a rate of 3 that should be 4, a minimum one too high, a digit out. Nothing else shows these: the calculation is right, and only its number is wrong. It costs in limits and rates held as constants, which tests often reuse instead of stating.

/// 2 to send anything, and 3 for each kilo.
pub fn postage(kilos: u32) -> u32 {
kilos * 3 + 2
}
#[test]
fn a_letter_costs_the_minimum() {
assert_eq!(postage(0), 2);
}

With kilos at 0, nothing about the price per kilo shows, so every change to kilos * 3 gets past the test: the operator’s, the constant’s, and keep operand’s, on one card:

Terminal window
✗ MISSED tests pass even when * becomes / in postage() · m0f04c581
┌─ src/arithmetic.rs:25
│
24 │ pub fn postage(kilos: u32) -> u32 {
25 │ kilos * 3 + 2
│ ^ * changed to /
26 │ }
│
= mutant - kilos * 3 + 2
+ kilos / 3 + 2
= also ✗ tests pass even when * becomes % in postage() · mc5b1b361
+ kilos % 3 + 2
= also ✗ tests pass even when kilos * 3 becomes kilos in postage() · mebd4cb5e
+ kilos + 2
= also ✗ tests pass even when 3 becomes 4 in postage() · mbed3c12d
+ kilos * 4 + 2
= also ✗ tests pass even when 3 becomes 2 in postage() · ma0fa708a
+ kilos * 2 + 2
= also ✗ tests pass even when 3 becomes 0 in postage() · m9d8f7916
+ kilos * 0 + 2
= also ✗ tests pass even when 3 becomes 1 in postage() · mb750003e
+ kilos * 1 + 2
= note: inputs seen: kilos = 0
covered by arithmetic::tests::a_letter_costs_the_minimum (its 1 covering test still passes)
= help: use inputs where kilos * 3 and kilos / 3 differ, and assert the result
// in mod arithmetic::tests
#[test]
fn postage_with_kilos_3() {
assert_eq!(postage(3), 11); // today 11 · with m0f04c581 3
}
check today's value is the one you intend: the test pins what the code does now
done when mutastic verify m0f04c581,mc5b1b361,mebd4cb5e,mbed3c12d,ma0fa708a,m9d8f7916,mb750003e fixtures/operators passes
reproduce mutastic run --only=m0f04c581,mc5b1b361,mebd4cb5e,mbed3c12d,ma0fa708a,m9d8f7916,mb750003e fixtures/operators

The test that catches it: one that states the numbers, with an input where each of them shows.

#[test]
fn two_kilos_cost_eight() {
assert_eq!(postage(2), 8);
}

Edition notes: the Java edition has this operator too, mutastic/constant-value, and reaches a static final number through the value javac writes where it’s used.

mutastic/bitwise, in the stronger set. & becomes | and ^, | becomes & and ^, ^ becomes & and |, and << and >> swap. Compound assignments too, except that &= and |= never become ^=.

The bug it stands for: the wrong bit operation: setting a flag where it should be tested, toggling where it should be set. It costs in permissions, flags and packed fields, where tests use bits that are set, and on those &, | and ^ agree.

pub fn has_flag(flags: u8, flag: u8) -> bool {
flags & flag != 0
}
#[test]
fn a_flag_that_is_set() {
assert!(has_flag(3, 1));
}
Terminal window
✗ MISSED tests pass even when & becomes | in has_flag() · ma32c7819
┌─ src/arithmetic.rs:29
│
28 │ pub fn has_flag(flags: u8, flag: u8) -> bool {
29 │ flags & flag != 0
│ ^ & changed to |
30 │ }
│
= mutant - flags & flag != 0
+ flags | flag != 0
= also ✗ tests pass even when & becomes ^ in has_flag() · maace5f2b
+ flags ^ flag != 0
= note: inputs seen: flag = 1; flags = 3
covered by arithmetic::tests::a_flag_that_is_set (its 1 covering test still passes)
= help: use inputs where & and | give different results, and assert what has_flag() does
// in mod arithmetic::tests
#[test]
fn has_flag_with_flags_0_and_flag_1() {
assert!(!has_flag(0, 1)); // today false · with ma32c7819 true
}
check today's value is the one you intend: the test pins what the code does now
done when mutastic verify ma32c7819,maace5f2b fixtures/operators passes
reproduce mutastic run --only=ma32c7819,maace5f2b fixtures/operators

The test that catches it: one with a flag that isn’t set.

#[test]
fn a_flag_that_is_not_set() {
assert!(!has_flag(2, 1));
}

Edition notes: the Java edition has this operator too, mutastic/bitwise, for >>> too.

library/unchecked-arithmetic, in the stronger set. A checked operation becomes one that can’t fail: a.checked_add(b) becomes Some(a.wrapping_add(b)), and saturating_sub becomes wrapping_sub. It goes unnoticed when no test reaches the overflow the check is there for.

The bug it stands for: arithmetic that overflows silently: a total that wraps round, because nothing checked that it fits. It costs in money and sizes, where someone chose checked_add because a wrong number is worse than none. If no test overflows, nothing shows that the check is still there.

/// `None` when the points would overflow.
pub fn add_points(points: u32, more: u32) -> Option<u32> {
points.checked_add(more)
}
#[test]
fn points_add_up() {
assert_eq!(add_points(1, 2), Some(3));
}
Terminal window
✗ MISSED tests pass even when points.checked_add(more) becomes Some(points.wrapping_add(more)) in add_points()
┌─ src/arithmetic.rs:34
│
33 │ pub fn add_points(points: u32, more: u32) -> Option<u32> {
34 │ points.checked_add(more)
│ ^^^^^^^^^^^ changed to Some(points.wrapping_add(more))
35 │ }
│
= mutant - points.checked_add(more)
+ Some(points.wrapping_add(more))
= note: inputs seen: more = 2; points = 1
covered by arithmetic::tests::points_add_up (its 1 covering test still passes)
= help: use inputs where points.checked_add(more) and Some(points.wrapping_add(more)) differ, and assert what add_points() does
done when mutastic verify mcc88a618 fixtures/operators passes
reproduce mutastic run --only=mcc88a618 fixtures/operators

The test that catches it: one whose result doesn’t fit the type, asserting there’s no answer.

#[test]
fn too_many_points() {
assert_eq!(add_points(u32::MAX, 1), None);
}

Edition notes: the Java edition has this operator too, library/unchecked-arithmetic, for Math.addExact and the like, which throw.