Literal Mutators
Relevant source files
The following files were used as context for generating this wiki page:
Literal Mutators identify and modify hardcoded values within Ruby source code to verify that test suites adequately assert the behavior of these constants. These mutators target various data types, including integers, strings, arrays, booleans, ranges, and regular expressions, replacing them with alternative values or empty structures.
Overview of Literal Mutation Logic
The mutation process involves visiting specific nodes in the Abstract Syntax Tree (AST) using Prism. When a mutator encounters a supported literal type, it evaluates the current value and emits one or more mutations with alternative replacements.
The diagram above shows how different literal nodes are routed to specific mutator classes.
Sources: lib/mutineer/mutators/literal_mutation.rb, lib/mutineer/mutators/array_literal.rb, lib/mutineer/mutators/boolean_literal.rb
Mutator Implementations
Integer and String Mutator (LiteralMutation)
The LiteralMutation class implements Tier-2 literal rules for integers and basic strings. It avoids certain mutations, such as emptying heredocs, to maintain code validity.
- Integers: If the value is not
0, it emits0. If the value is not1, it emits1. It always emitsn + 1. - Strings: It replaces non-empty strings with an empty string
"". It skips strings that are already empty.
Sources: lib/mutineer/mutators/literal_mutation.rb:14-34, test/mutators/literal_mutation_test.rb:18-35
Boolean and Nil Mutator
The BooleanLiteral mutator flips truthiness. It transforms true to false, false to true, and nil to true.
Sources: test/mutators/boolean_literal_test.rb:25-27
Collection Mutators (Array and Range)
These mutators target data structures and their boundaries.
| Mutator | Targeted Literal | Mutation Action |
|---|---|---|
ArrayLiteral | [a, b], %i[a], %w[x] | Replaces with an empty array []. |
RangeLiteral | 1..10 (Inclusive) | Replaces with exclusive range 1...10. |
RangeLiteral | 1...10 (Exclusive) | Replaces with inclusive range 1..10. |
RangeLiteral | ..5 (Beginless) | Swaps .. with ... and vice versa. |
Sources: lib/mutineer/mutators/array_literal.rb, test/mutators/array_literal_test.rb:14-25, test/mutators/range_literal_test.rb:15-35
Specific Exclusion Rules
Literal mutators include logic to avoid "equivalent mutants" or invalid code:
- ArrayLiteral: Skips implicit arrays (e.g.,
x = 1, 2) and arrays containing heredocs. - RangeLiteral: Skips endless ranges (e.g.,
1..) because they are often used for slicing to the end of a collection, making inclusive vs exclusive changes equivalent. - StringLiteral: Skips interpolated strings to avoid complex syntax reconstruction.
Sources: test/mutators/array_literal_test.rb:34-42, test/mutators/range_literal_test.rb:39-43, test/mutators/string_literal_test.rb:31-33
Technical Implementation Details
All literal mutators inherit from Mutineer::Mutators::Base and use the Prism visitor pattern. They identify the source location and provide a replacement string.
# Example from LiteralMutation
def emit(loc, replacement)
@mutations << Mutation.new(
start_offset: loc.start_offset,
end_offset: loc.end_offset,
replacement: replacement,
operator: :literal_mutation
)
end
Sources: lib/mutineer/mutators/literal_mutation.rb:38-47
Summary
Literal Mutators provide a foundational layer of mutation testing by challenging the specific values used in a program. By emptying arrays, flipping booleans, and shifting integers, these mutators ensure that the test suite is sensitive to the data being processed, rather than just the control flow.