Add support for --fail-fast flag
* Some long planned runner design changes. * It now also uses the emitter pattern
This commit is contained in:
parent
e8ecdab025
commit
8a6a51fd08
16 changed files with 279 additions and 215 deletions
|
@ -56,12 +56,13 @@ module Mutant
|
|||
#
|
||||
def config
|
||||
Config.new(
|
||||
:cache => @cache,
|
||||
:debug => debug?,
|
||||
:matcher => matcher,
|
||||
:filter => filter,
|
||||
:strategy => strategy,
|
||||
:reporter => reporter
|
||||
:cache => @cache,
|
||||
:debug => debug?,
|
||||
:matcher => matcher,
|
||||
:filter => filter,
|
||||
:fail_fast => !!@fail_fast,
|
||||
:strategy => strategy,
|
||||
:reporter => reporter
|
||||
)
|
||||
end
|
||||
memoize :config
|
||||
|
@ -151,6 +152,16 @@ module Mutant
|
|||
@filters << klass.new(filter)
|
||||
end
|
||||
|
||||
# Set fail fast
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
def set_fail_fast
|
||||
@fail_fast = true
|
||||
end
|
||||
|
||||
# Set debug mode
|
||||
#
|
||||
# @api private
|
||||
|
@ -252,6 +263,8 @@ module Mutant
|
|||
|
||||
opts.on('--code FILTER', 'Adds a code filter') do |filter|
|
||||
add_filter Mutation::Filter::Code, filter
|
||||
end.on('--fail-fast', 'Fail fast') do
|
||||
set_fail_fast
|
||||
end.on('-d','--debug', 'Enable debugging output') do
|
||||
set_debug
|
||||
end.on_tail('-h', '--help', 'Show this message') do
|
||||
|
|
|
@ -2,7 +2,7 @@ module Mutant
|
|||
# The configuration of a mutator run
|
||||
class Config
|
||||
include Adamantium::Flat, Anima.new(
|
||||
:cache, :debug, :strategy, :matcher, :filter, :reporter
|
||||
:cache, :debug, :strategy, :matcher, :filter, :reporter, :fail_fast
|
||||
)
|
||||
|
||||
# Enumerate subjects
|
||||
|
|
|
@ -1,8 +1,57 @@
|
|||
module Mutant
|
||||
# Runner baseclass
|
||||
class Runner
|
||||
include Adamantium::Flat, AbstractType, Equalizer.new(:config)
|
||||
extend MethodObject
|
||||
include Adamantium::Flat, AbstractType
|
||||
|
||||
REGISTRY = {}
|
||||
|
||||
# Register handler
|
||||
#
|
||||
# @param [Class] klass
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.register(klass)
|
||||
REGISTRY[klass]=self
|
||||
end
|
||||
private_class_method :register
|
||||
|
||||
# Lookup runner
|
||||
#
|
||||
# @param [Class] klass
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.lookup(klass)
|
||||
current = klass
|
||||
while current
|
||||
handler = REGISTRY.fetch(current) do
|
||||
current = current.superclass; nil
|
||||
end
|
||||
return handler if handler
|
||||
end
|
||||
|
||||
raise ArgumentError, "No handler for: #{klass}"
|
||||
end
|
||||
private_class_method :lookup
|
||||
|
||||
# Run runner for object
|
||||
#
|
||||
# @param [Config] config
|
||||
# @param [Object] object
|
||||
#
|
||||
# @return [Runner]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.run(config, object)
|
||||
handler = lookup(object.class)
|
||||
handler.new(config, object)
|
||||
end
|
||||
|
||||
# Return config
|
||||
#
|
||||
|
@ -27,6 +76,18 @@ module Mutant
|
|||
@end = Time.now
|
||||
end
|
||||
|
||||
# Test if runner should stop
|
||||
#
|
||||
# @return [true]
|
||||
# if runner should stop
|
||||
#
|
||||
# @return [false]
|
||||
# otherwise
|
||||
#
|
||||
def stop?
|
||||
!!@stop
|
||||
end
|
||||
|
||||
# Return runtime
|
||||
#
|
||||
# @return [Float]
|
||||
|
@ -34,22 +95,17 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def runtime
|
||||
@end - @start
|
||||
(@end || Time.now) - @start
|
||||
end
|
||||
memoize :runtime
|
||||
|
||||
# Test if runner failed
|
||||
# Return reporter
|
||||
#
|
||||
# @return [true]
|
||||
# if failed
|
||||
#
|
||||
# @return [false]
|
||||
# otherwise
|
||||
# @return [Reporter]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def failed?
|
||||
!success?
|
||||
def reporter
|
||||
config.reporter
|
||||
end
|
||||
|
||||
# Test if runner is successful
|
||||
|
@ -64,16 +120,6 @@ module Mutant
|
|||
#
|
||||
abstract_method :success?
|
||||
|
||||
# Return reporter
|
||||
#
|
||||
# @return [Reporter]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def reporter
|
||||
config.reporter
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Perform operation
|
||||
|
@ -96,5 +142,36 @@ module Mutant
|
|||
reporter.report(object)
|
||||
end
|
||||
|
||||
# Perform dispatch
|
||||
#
|
||||
# @return [Enumerable<Runner>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def dispatch(input)
|
||||
collection = []
|
||||
input.each do |object|
|
||||
runner = visit(object)
|
||||
collection << runner
|
||||
if runner.stop?
|
||||
@stop = true
|
||||
break
|
||||
end
|
||||
end
|
||||
collection
|
||||
end
|
||||
|
||||
# Visit object
|
||||
#
|
||||
# @param [Object] object
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def visit(object)
|
||||
Runner.run(config, object)
|
||||
end
|
||||
|
||||
end # Runner
|
||||
end # Mutant
|
||||
|
|
|
@ -3,6 +3,22 @@ module Mutant
|
|||
# Runner for object config
|
||||
class Config < self
|
||||
|
||||
register Mutant::Config
|
||||
|
||||
# Run runner for object
|
||||
#
|
||||
# @param [Config] config
|
||||
# @param [Object] object
|
||||
#
|
||||
# @return [Runner]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.run(config)
|
||||
handler = lookup(config.class)
|
||||
handler.new(config)
|
||||
end
|
||||
|
||||
# Return subject runners
|
||||
#
|
||||
# @return [Enumerable<Runner::Subject>]
|
||||
|
@ -18,7 +34,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def failed_subjects
|
||||
subjects.select(&:failed?)
|
||||
subjects.reject(&:success?)
|
||||
end
|
||||
memoize :failed_subjects
|
||||
|
||||
|
@ -58,9 +74,7 @@ module Mutant
|
|||
def run_subjects
|
||||
strategy = self.strategy
|
||||
strategy.setup
|
||||
@subjects = config.subjects.map do |subject|
|
||||
Subject.run(config, subject)
|
||||
end
|
||||
@subjects = dispatch(config.subjects)
|
||||
strategy.teardown
|
||||
end
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ module Mutant
|
|||
class Mutation < self
|
||||
include Concord::Public.new(:config, :mutation)
|
||||
|
||||
register Mutant::Mutation
|
||||
|
||||
# Return killer instance
|
||||
#
|
||||
# @return [Killer]
|
||||
|
@ -50,7 +52,8 @@ module Mutant
|
|||
#
|
||||
def run
|
||||
@killer = config.strategy.kill(mutation)
|
||||
report(@killer)
|
||||
report(killer)
|
||||
@stop = config.fail_fast && !killer.success?
|
||||
end
|
||||
|
||||
end # Mutation
|
||||
|
|
|
@ -4,13 +4,7 @@ module Mutant
|
|||
class Subject < self
|
||||
include Concord::Public.new(:config, :subject)
|
||||
|
||||
# Return subject
|
||||
#
|
||||
# @return [Subject]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :subject
|
||||
register Mutant::Subject
|
||||
|
||||
# Initialize object
|
||||
#
|
||||
|
@ -41,7 +35,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def failed_mutations
|
||||
mutations.select(&:failed?)
|
||||
mutations.reject(&:success?)
|
||||
end
|
||||
memoize :failed_mutations
|
||||
|
||||
|
@ -70,9 +64,7 @@ module Mutant
|
|||
def run
|
||||
subject = self.subject
|
||||
report(subject)
|
||||
@mutations = subject.map do |mutation|
|
||||
Mutation.run(config, mutation)
|
||||
end
|
||||
@mutations = dispatch(subject.mutations)
|
||||
report(self)
|
||||
end
|
||||
|
||||
|
|
|
@ -3,38 +3,18 @@ module Mutant
|
|||
class Subject
|
||||
include AbstractType, Adamantium::Flat, Enumerable, Concord::Public.new(:context, :node)
|
||||
|
||||
# Enumerate possible mutations
|
||||
# Return mutations
|
||||
#
|
||||
# @return [self]
|
||||
# returns self if block given
|
||||
#
|
||||
# @return [Enumerator<Mutation>]
|
||||
# returns eumerator if no block given
|
||||
# @return [Enumerable<Mutation>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def each
|
||||
return to_enum unless block_given?
|
||||
|
||||
yield noop_mutation
|
||||
|
||||
mutations.each do |mutation|
|
||||
yield mutation
|
||||
end
|
||||
|
||||
self
|
||||
def mutations
|
||||
mutations = []
|
||||
generate_mutations(mutations)
|
||||
mutations
|
||||
end
|
||||
|
||||
# Return noop mutation
|
||||
#
|
||||
# @return [Mutation::Noop]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def noop
|
||||
Mutation::Neutral.new(self, node)
|
||||
end
|
||||
memoize :noop
|
||||
memoize :mutations
|
||||
|
||||
# Return source path
|
||||
#
|
||||
|
@ -122,5 +102,15 @@ module Mutant
|
|||
Mutation::Neutral::Noop.new(self, node)
|
||||
end
|
||||
|
||||
# Generate mutations
|
||||
#
|
||||
# @param [#<<] emitter
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
abstract_method :generate_mutations
|
||||
|
||||
end # Subject
|
||||
end # Mutant
|
||||
|
|
|
@ -29,13 +29,16 @@ module Mutant
|
|||
|
||||
# Return mutations
|
||||
#
|
||||
# @return [Enumerable<Mutation>]
|
||||
# @param [#<<] emitter
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def mutations
|
||||
Mutator.each(node).map do |mutant|
|
||||
Mutation::Evil.new(self, mutant)
|
||||
def generate_mutations(emitter)
|
||||
emitter << noop_mutation
|
||||
Mutator.each(node) do |mutant|
|
||||
emitter << Mutation::Evil.new(self, mutant)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -24,4 +24,7 @@ RSpec.configure do |config|
|
|||
config.include(CompressHelper)
|
||||
config.include(ParserHelper)
|
||||
config.include(Mutant::NodeHelpers)
|
||||
config.mock_with :rspec do |config|
|
||||
config.syntax = [:expect, :should]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,31 +8,41 @@ describe Mutant::Runner::Config, '#subjects' do
|
|||
let(:config) do
|
||||
double(
|
||||
'Config',
|
||||
:subjects => [mutation_subject],
|
||||
:class => Mutant::Config,
|
||||
:subjects => [subject_a, subject_b],
|
||||
:strategy => strategy,
|
||||
:reporter => reporter
|
||||
)
|
||||
end
|
||||
|
||||
let(:reporter) { double('Reporter') }
|
||||
let(:strategy) { double('Strategy') }
|
||||
let(:mutation_subject) { double('Mutation subject') }
|
||||
let(:subject_runner) { double('Subject runner') }
|
||||
|
||||
class DummySubjectRunner
|
||||
include Concord::Public.new(:config, :mutation)
|
||||
|
||||
def self.run(*args); new(*args); end
|
||||
end
|
||||
let(:reporter) { double('Reporter') }
|
||||
let(:strategy) { double('Strategy') }
|
||||
let(:subject_a) { double('Subject A') }
|
||||
let(:subject_b) { double('Subject B') }
|
||||
let(:runner_a) { double('Runner A', :stop? => stop_a) }
|
||||
let(:runner_b) { double('Runner B', :stop? => stop_b) }
|
||||
|
||||
before do
|
||||
strategy.stub(:setup)
|
||||
strategy.stub(:teardown)
|
||||
reporter.stub(:report => reporter)
|
||||
stub_const('Mutant::Runner::Subject', DummySubjectRunner)
|
||||
Mutant::Runner.stub(:run).with(config, subject_a).and_return(runner_a)
|
||||
Mutant::Runner.stub(:run).with(config, subject_b).and_return(runner_b)
|
||||
end
|
||||
|
||||
it { should eql([DummySubjectRunner.new(config, mutation_subject)]) }
|
||||
context 'without earily stop' do
|
||||
let(:stop_a) { false }
|
||||
let(:stop_b) { false }
|
||||
it { should eql([runner_a, runner_b]) }
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
||||
|
||||
context 'with earily stop' do
|
||||
let(:stop_a) { true }
|
||||
let(:stop_b) { false }
|
||||
it { should eql([runner_a]) }
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
|
|
@ -3,50 +3,45 @@ require 'spec_helper'
|
|||
describe Mutant::Runner::Config, '#success?' do
|
||||
subject { object.success? }
|
||||
|
||||
let(:object) { described_class.run(config) }
|
||||
let(:object) { described_class.new(config) }
|
||||
|
||||
let(:config) do
|
||||
double(
|
||||
'Config',
|
||||
:reporter => reporter,
|
||||
:strategy => strategy,
|
||||
:subjects => subjects
|
||||
:subjects => [subject_a, subject_b]
|
||||
)
|
||||
end
|
||||
|
||||
let(:reporter) { double('Reporter') }
|
||||
let(:strategy) { double('Strategy') }
|
||||
let(:subjects) { [subject_a, subject_b] }
|
||||
let(:subject_a) { double('Subject A', :fails? => false) }
|
||||
let(:subject_b) { double('Subject B', :fails? => false) }
|
||||
|
||||
class DummySubjectRunner
|
||||
include Concord::Public.new(:config, :subject)
|
||||
|
||||
def self.run(*args)
|
||||
new(*args)
|
||||
end
|
||||
|
||||
def failed?
|
||||
@subject.fails?
|
||||
end
|
||||
end
|
||||
let(:reporter) { double('Reporter') }
|
||||
let(:strategy) { double('Strategy') }
|
||||
let(:subject_a) { double('Subject A') }
|
||||
let(:subject_b) { double('Subject B') }
|
||||
let(:runner_a) { double('Runner A', :stop? => stop_a, :success? => success_a) }
|
||||
let(:runner_b) { double('Runner B', :stop? => stop_b, :success? => success_b) }
|
||||
|
||||
before do
|
||||
stub_const('Mutant::Runner::Subject', DummySubjectRunner)
|
||||
reporter.stub(:report => reporter)
|
||||
strategy.stub(:setup)
|
||||
strategy.stub(:teardown)
|
||||
Mutant::Runner.stub(:run).with(config, subject_a).and_return(runner_a)
|
||||
Mutant::Runner.stub(:run).with(config, subject_b).and_return(runner_b)
|
||||
end
|
||||
|
||||
context 'without failed subjects' do
|
||||
let(:stop_a) { false }
|
||||
let(:stop_b) { false }
|
||||
let(:success_a) { true }
|
||||
let(:success_b) { true }
|
||||
it { should be(true) }
|
||||
end
|
||||
|
||||
context 'with failing subjects' do
|
||||
before do
|
||||
subject_a.stub(:fails? => true)
|
||||
end
|
||||
let(:stop_a) { false }
|
||||
let(:stop_b) { false }
|
||||
let(:success_a) { false }
|
||||
let(:success_b) { true }
|
||||
|
||||
it { should be(false) }
|
||||
end
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Runner, '#failed?' do
|
||||
subject { object.failed? }
|
||||
|
||||
let(:object) { class_under_test.run(config) }
|
||||
|
||||
let(:config) { double('Config') }
|
||||
let(:class_under_test) do
|
||||
success = self.success
|
||||
|
||||
Class.new(described_class) do
|
||||
define_method :success? do
|
||||
success
|
||||
end
|
||||
|
||||
define_method :run do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when runner is successful' do
|
||||
let(:success) { true }
|
||||
|
||||
it { should be(false) }
|
||||
end
|
||||
|
||||
context 'when runner is NOT successful' do
|
||||
let(:success) { false }
|
||||
|
||||
it { should be(true) }
|
||||
end
|
||||
end
|
|
@ -6,15 +6,18 @@ describe Mutant::Runner::Mutation, '#killer' do
|
|||
let(:config) do
|
||||
double(
|
||||
'Config',
|
||||
:reporter => reporter,
|
||||
:strategy => strategy
|
||||
:fail_fast => fail_fast,
|
||||
:reporter => reporter,
|
||||
:strategy => strategy
|
||||
)
|
||||
end
|
||||
|
||||
let(:reporter) { double('Reporter') }
|
||||
let(:mutation) { double('Mutation') }
|
||||
let(:strategy) { double('Strategy') }
|
||||
let(:killer) { double('Killer') }
|
||||
let(:reporter) { double('Reporter') }
|
||||
let(:mutation) { double('Mutation', :class => Mutant::Mutation) }
|
||||
let(:strategy) { double('Strategy') }
|
||||
let(:killer) { double('Killer', :success? => success) }
|
||||
let(:fail_fast) { false }
|
||||
let(:success) { false }
|
||||
|
||||
subject { object.killer }
|
||||
|
||||
|
|
|
@ -3,47 +3,49 @@ require 'spec_helper'
|
|||
describe Mutant::Runner::Subject, '#success?' do
|
||||
subject { object.success? }
|
||||
|
||||
let(:object) { described_class.run(config, mutation_subject) }
|
||||
let(:object) { described_class.new(config, mutation_subject) }
|
||||
|
||||
let(:mutation_subject) {
|
||||
double(
|
||||
'Subject',
|
||||
:class => Mutant::Subject,
|
||||
:mutations => [mutation_a, mutation_b]
|
||||
)
|
||||
}
|
||||
|
||||
let(:reporter) { double('Reporter') }
|
||||
let(:mutation_subject) { double('Subject', :map => mutations) }
|
||||
let(:config) { double('Config', :reporter => reporter) }
|
||||
let(:mutation_a) { double('Mutation A', :failed? => false) }
|
||||
let(:mutation_b) { double('Mutation B', :failed? => false) }
|
||||
let(:mutations) { [mutation_a, mutation_b] }
|
||||
let(:mutation_a) { double('Mutation A') }
|
||||
let(:mutation_b) { double('Mutation B') }
|
||||
|
||||
let(:runner_a) { double('Runner A', :success? => success_a, :stop? => stop_a) }
|
||||
let(:runner_b) { double('Runner B', :success? => success_b, :stop? => stop_b) }
|
||||
|
||||
before do
|
||||
reporter.stub(:report => reporter)
|
||||
Mutant::Runner.stub(:run).with(config, mutation_a).and_return(runner_a)
|
||||
Mutant::Runner.stub(:run).with(config, mutation_b).and_return(runner_b)
|
||||
end
|
||||
|
||||
class DummyMutationRunner
|
||||
include Concord::Public.new(:config, :mutation)
|
||||
|
||||
def self.run(*args)
|
||||
new(*args)
|
||||
end
|
||||
|
||||
def failed?
|
||||
@mutation.failed?
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
stub_const('Mutant::Runner::Mutation', DummyMutationRunner)
|
||||
end
|
||||
|
||||
context 'without evil failed mutations' do
|
||||
it { should be(true) }
|
||||
end
|
||||
|
||||
context 'with failing noop mutation' do
|
||||
end
|
||||
|
||||
context 'with failing evil mutations' do
|
||||
before do
|
||||
mutation_a.stub(:failed? => true)
|
||||
end
|
||||
context 'with failing mutations' do
|
||||
let(:stop_a) { false }
|
||||
let(:stop_b) { false }
|
||||
let(:success_a) { false }
|
||||
let(:success_b) { true }
|
||||
|
||||
it { should be(false) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
||||
context 'without failing mutations' do
|
||||
let(:stop_a) { false }
|
||||
let(:stop_b) { false }
|
||||
let(:success_a) { true }
|
||||
let(:success_b) { true }
|
||||
|
||||
it { should be(true) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Subject, '#each' do
|
||||
subject { object.each { |item| yields << item } }
|
||||
|
||||
let(:class_under_test) do
|
||||
mutations = [mutation_a, mutation_b]
|
||||
Class.new(described_class) do
|
||||
define_method(:mutations) { mutations }
|
||||
end
|
||||
end
|
||||
|
||||
let(:object) { class_under_test.new(context, node) }
|
||||
let(:yields) { [] }
|
||||
let(:node) { double('Node') }
|
||||
let(:context) { double('Context') }
|
||||
let(:mutant) { double('Mutant') }
|
||||
let(:mutation_a) { double('Mutation A') }
|
||||
let(:mutation_b) { double('Mutation B') }
|
||||
|
||||
it_should_behave_like 'an #each method'
|
||||
|
||||
let(:neutral_mutation) do
|
||||
Mutant::Mutation::Neutral.new(object, node)
|
||||
end
|
||||
|
||||
it 'yields mutations' do
|
||||
expect { subject }.to change { yields.dup }.from([])
|
||||
.to([neutral_mutation, mutation_a, mutation_b])
|
||||
end
|
||||
end
|
23
spec/unit/mutant/subject/mutations_spec.rb
Normal file
23
spec/unit/mutant/subject/mutations_spec.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Subject, '#mutations' do
|
||||
subject { object.mutations }
|
||||
|
||||
let(:class_under_test) do
|
||||
mutation_a, mutation_b = self.mutation_a, self.mutation_b
|
||||
Class.new(described_class) do
|
||||
define_method(:generate_mutations) do |emitter|
|
||||
emitter << mutation_a
|
||||
emitter << mutation_b
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
let(:object) { class_under_test.new(context, node) }
|
||||
let(:node) { double('Node') }
|
||||
let(:context) { double('Context') }
|
||||
let(:mutation_a) { double('Mutation A') }
|
||||
let(:mutation_b) { double('Mutation B') }
|
||||
|
||||
it { should eql([mutation_a, mutation_b]) }
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue