Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3916a42
refactor: use rstrip instead of strip in InvalidToken
dkubb Mar 17, 2026
5c39e6b
docs: add burn-down baseline to ignore list
dkubb Mar 17, 2026
ca6766d
refactor: use Array#sum in Result::ClassMethods#sum
dkubb Mar 17, 2026
6e12d75
test: verify capture_command arguments in World spec
dkubb Mar 17, 2026
b6838f8
refactor: remove unused token return from Parser#optional
dkubb Mar 17, 2026
81d50be
refactor: use sort_by instead of sort_by! in Sink#status
dkubb Mar 17, 2026
7f474da
refactor: remove else_branch variable from Case
dkubb Mar 17, 2026
01c23ce
refactor: reuse Util.one result in Index#emit_drop_mutation
dkubb Mar 17, 2026
ec47e1f
refactor: use public_method in Send receiver selector mutations
dkubb Mar 17, 2026
f159854
test: add partially done context for Parallel::Driver
dkubb Mar 17, 2026
25dd385
refactor: use guard clause in Arguments#emit_procarg0
dkubb Mar 17, 2026
b2cc700
test: add unused optarg meta spec for Define
dkubb Mar 17, 2026
7822594
test: add absent timeout context for Printer::Config
dkubb Mar 17, 2026
ef70773
refactor: remove dead private methods from Verification
dkubb Mar 17, 2026
acf71e6
refactor: remove dead mutate_single_child from Mutator::Node
dkubb Mar 17, 2026
55452cd
refactor: remove dead method nesting from Scope
dkubb Mar 17, 2026
3aecad4
docs: add investigation notes to ignore list entries
dkubb Mar 17, 2026
a333c53
test: add warning prevention coverage for RequireHighjack
dkubb Mar 17, 2026
4d0263d
docs: add Zombifier#include? to ignore list
dkubb Mar 17, 2026
a75cf9f
test: add Integration::Rspec#setup expectations
dkubb Mar 17, 2026
4b7e5ee
refactor: remove redundant example_group_map from Rspec#setup
dkubb Mar 17, 2026
879388d
refactor: remove dead nil guard from Evaluator#matched_view
dkubb Mar 17, 2026
00a6073
test: add Progressive format method coverage
dkubb Mar 17, 2026
611d138
test: add Scope#match_expressions coverage
dkubb Mar 17, 2026
bb83e84
refactor: remove dead forbid_argument? from Kwargs mutator
dkubb Mar 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ruby/lib/mutant/ast/pattern/lexer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class Error

class InvalidToken < self
def display_message
<<~MESSAGE.strip
<<~MESSAGE.rstrip
Invalid #{token.type} token:
#{token.display_location}
MESSAGE
Expand Down
1 change: 0 additions & 1 deletion ruby/lib/mutant/ast/pattern/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ def optional(type)
return unless token&.type.equal?(type)

advance_position
token
end

def parse_node_type
Expand Down
1 change: 0 additions & 1 deletion ruby/lib/mutant/integration/rspec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ def setup
@setup_elapsed = timer.elapsed do
@runner.setup(world.stderr, world.stdout)
fail 'RSpec setup failure' if rspec_setup_failure?
example_group_map
end
@runner.configuration.force(color_mode: :on)
@runner.configuration.reporter
Expand Down
2 changes: 0 additions & 2 deletions ruby/lib/mutant/matcher/method.rb
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,6 @@ def subject_config(node)
end

def matched_view
return if source_location.nil?

# This is a performance optimization when using --since to avoid the cost of parsing
# every source file that could possibly map to a subject. A more fine-grained filtering
# takes places later in the process.
Expand Down
18 changes: 0 additions & 18 deletions ruby/lib/mutant/meta/example/verification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,24 +118,6 @@ def no_diffs
end
memoize :no_diffs

def format_mutations(mutations)
mutations.map do |mutation|
{
'node' => mutation.node.inspect,
'source' => mutation.source
}
end
end

def no_diff_report
no_diffs.map do |mutation|
{
'node' => mutation.node.inspect,
'source' => mutation.source
}
end
end

end # Verification
end # Example
end # Meta
Expand Down
7 changes: 0 additions & 7 deletions ruby/lib/mutant/mutator/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,6 @@ def children_indices(range)
range.begin.upto(children.length + range.end)
end

def mutate_single_child
children.each_with_index do |child, index|
mutate_child(index)
yield child, index unless children.one?
end
end

def run(mutator)
mutator.call(
config:,
Expand Down
5 changes: 4 additions & 1 deletion ruby/lib/mutant/mutator/node/arguments.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ def emit_procarg0_removal
return unless children.one? && n_procarg0?(procarg0 = Mutant::Util.one(children))

arguments = procarg0.children
emit_type(*arguments) if arguments.count > 1

return if arguments.one?

emit_type(*arguments)
end

end # Arguments
Expand Down
3 changes: 1 addition & 2 deletions ruby/lib/mutant/mutator/node/case.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ def emit_when_mutations
end

def emit_else_mutations
else_branch = children.last
else_index = children.length - 1
return unless else_branch
return unless children.fetch(else_index)
mutate_child(else_index)
emit_child_update(else_index, nil)
end
Expand Down
8 changes: 6 additions & 2 deletions ruby/lib/mutant/mutator/node/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@ def emit_send_forms
end

def emit_drop_mutation
return unless indices.one? && n_irange?(Mutant::Util.one(indices))
return unless indices.one?

start, ending = *indices.first
range = Mutant::Util.one(indices)

return unless n_irange?(range)

start, ending = *range

return unless ending.eql?(s(:int, -1))

Expand Down
16 changes: 2 additions & 14 deletions ruby/lib/mutant/mutator/node/kwargs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ class Node
# Mutator for kwargs node
class Kwargs < self

DISALLOW = %i[nil self].freeze

private_constant(*constants(false))

handle(:kwargs)

private
Expand All @@ -26,18 +22,10 @@ def emit_argument_presence
end

def emit_argument_mutations
children.each_with_index do |child, index|
mutate(node: child).each do |mutant|
unless forbid_argument?(mutant)
emit_child_update(index, mutant)
end
end
children.each_with_index do |_child, index|
mutate_child(index)
end
end

def forbid_argument?(node)
n_pair?(node) && DISALLOW.include?(node.children.first.type)
end
end # Kwargs
end # Node
end # Mutator
Expand Down
2 changes: 1 addition & 1 deletion ruby/lib/mutant/mutator/node/send.rb
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def emit_receiver_selector_mutations
RECEIVER_SELECTOR_REPLACEMENTS
.fetch(receiver.children.last, EMPTY_HASH)
.fetch(selector, EMPTY_ARRAY)
.each(&method(:emit_selector))
.each(&public_method(:emit_selector))
end

def emit_double_negation_mutation
Expand Down
2 changes: 1 addition & 1 deletion ruby/lib/mutant/result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ module ClassMethods
# @return [undefined]
def sum(name, collection)
define_method(name) do
public_send(collection).map(&name).reduce(0, :+)
public_send(collection).map(&name).sum
end
memoize(name)
end
Expand Down
11 changes: 0 additions & 11 deletions ruby/lib/mutant/scope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,6 @@ class Scope

NAMESPACE_DELIMITER = '::'

# Nesting of scope
#
# @return [Enumerable<Class,Module>]
def nesting
const = Object
name_nesting.map do |name|
const = const.const_get(name)
end
end
memoize :nesting

# Unqualified name of scope
#
# @return [String]
Expand Down
2 changes: 1 addition & 1 deletion ruby/lib/mutant/test/runner/sink.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def status
Result::TestEnv.new(
env:,
runtime: env.world.timer.now - @start,
test_results: @test_results.sort_by!(&:job_index)
test_results: @test_results.sort_by(&:job_index)
)
end

Expand Down
9 changes: 9 additions & 0 deletions ruby/meta/def.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@
mutation 'def foo(a = true); super; end'
end

Mutant::Meta::Example.add :def do
source 'def foo(_a = true); end'

mutation 'def foo(_a); end'
mutation 'def foo(_a = false); end'
mutation 'def foo(_a = true); raise; end'
mutation 'def foo(_a = true); super; end'
end

Mutant::Meta::Example.add :def do
source 'def self.foo; true; false; end'

Expand Down
22 changes: 22 additions & 0 deletions ruby/meta/send.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# frozen_string_literal: true

# Date.parse receiver selector replacements
Mutant::Meta::Example.add :send do
source 'Date.parse(foo)'

singleton_mutations
mutation 'Date.jd(foo)'
mutation 'Date.civil(foo)'
mutation 'Date.strptime(foo)'
mutation 'Date.iso8601(foo)'
mutation 'Date.rfc3339(foo)'
mutation 'Date.xmlschema(foo)'
mutation 'Date.rfc2822(foo)'
mutation 'Date.rfc822(foo)'
mutation 'Date.httpdate(foo)'
mutation 'Date.jisx0301(foo)'
mutation 'Date.parse'
mutation 'Date.parse(nil)'
mutation 'self.parse(foo)'
mutation 'Date'
mutation 'foo'
end

Mutant::Meta::Example.add :send do
source 'a > b'

Expand Down
6 changes: 6 additions & 0 deletions ruby/mutant.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@ matcher:
- Mutant::Mutator::Node::Arguments#emit_argument_presence
- Mutant::Mutator::Node::Arguments#removed_block_arg?
- Mutant::Mutator::Node::BlockPass#dispatch
# mutation crashes killfork in zombie mode; Timer#now is used by mutant infrastructure
- Mutant::Timer#now
# unkillable: mutate_child on arg nodes within procarg0 produces no observable mutations
- Mutant::Mutator::Node::ProcargZero#dispatch # 5 alive
# unkillable: Zombifier is mutant infrastructure in zombie mode; mutations corrupt zombification, crashing the killfork
- Mutant::Zombifier#include? # 9 alive
22 changes: 22 additions & 0 deletions ruby/spec/unit/mutant/integration/rspec_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,28 @@
it 'freezes object' do
expect { subject }.to change { object.frozen? }.from(false).to(true)
end

it 'measures setup time' do
subject
expect(world.timer).to have_received(:elapsed)
end

it 'forces color mode on' do
subject
expect(rspec_configuration).to have_received(:force).with(color_mode: :on)
end

it 'accesses reporter' do
subject
expect(rspec_configuration).to have_received(:reporter)
end

it 'resets examples' do
subject
filtered_examples.each_value do |examples|
expect(examples).to be_empty
end
end
end

context 'on success' do
Expand Down
16 changes: 16 additions & 0 deletions ruby/spec/unit/mutant/parallel/driver_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,22 @@ def apply
include_examples 'returns expected status'
end

context 'when partially done' do
before do
allow(thread_a).to receive_messages(alive?: false)
end

let(:expected_status) do
Mutant::Parallel::Status.new(
active_jobs:,
done: false,
payload: sink_status
)
end

include_examples 'returns expected status'
end

include_examples 'when done'
end
end
Expand Down
31 changes: 31 additions & 0 deletions ruby/spec/unit/mutant/reporter/cli/format/progressive_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,37 @@
)
end

describe '#start' do
let(:tty?) { false }

subject { format.start(env) }

it 'returns env report using Printer::Env' do
expect(subject).to include('Mutant environment:')
expect(subject).to include('Subjects:')
expect(subject).to include('Mutations:')
end

it 'passes env to the printer' do
expect(subject).to include("Subjects: #{env.subjects.length}")
end
end

describe '#test_start' do
let(:tty?) { false }

subject { format.test_start(env) }

it 'returns test env report using Printer::Test::Env' do
expect(subject).to include('Test environment:')
expect(subject).to include('Integration:')
end

it 'passes env to the printer' do
expect(subject).to include("Tests: #{env.integration.all_tests.length}")
end
end

describe '#progress' do
subject { format.progress(status) }

Expand Down
16 changes: 16 additions & 0 deletions ruby/spec/unit/mutant/reporter/cli/printer/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,20 @@
REPORT
end
end

context 'on absent mutation timeout' do
let(:reportable) { config.with(mutation: config.mutation.with(timeout: nil)) }

describe '.call' do
it_reports(<<~'REPORT')
Usage: unknown
Matcher: #<Mutant::Matcher::Config empty>
Integration: null
Jobs: auto
Includes: []
Requires: []
Operators: light
REPORT
end
end
end
13 changes: 13 additions & 0 deletions ruby/spec/unit/mutant/require_highjack_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,18 @@ def apply
.not_to change { require_calls }.from([])
end
end

context 'with native-style method definitions' do
let(:target_module) do
Module.new.tap do |mod|
mod.class_eval('def require(x); end', __FILE__, __LINE__)
mod.class_eval('def self.require(x); end', __FILE__, __LINE__)
end
end

it 'does not emit method redefinition warnings' do
expect { apply }.not_to output.to_stderr
end
end
end
end
Loading