mutant 0.6.7 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Changelog.md +10 -0
- data/README.md +1 -1
- data/config/flay.yml +1 -1
- data/config/reek.yml +11 -40
- data/config/rubocop.yml +1 -1
- data/lib/mutant.rb +10 -2
- data/lib/mutant/actor.rb +64 -0
- data/lib/mutant/actor/actor.rb +50 -0
- data/lib/mutant/actor/env.rb +35 -0
- data/lib/mutant/actor/mailbox.rb +53 -0
- data/lib/mutant/actor/receiver.rb +48 -0
- data/lib/mutant/actor/sender.rb +27 -0
- data/lib/mutant/ast/types.rb +1 -1
- data/lib/mutant/cli.rb +2 -0
- data/lib/mutant/config.rb +2 -1
- data/lib/mutant/env.rb +6 -2
- data/lib/mutant/expression/methods.rb +1 -1
- data/lib/mutant/integration.rb +11 -1
- data/lib/mutant/isolation.rb +1 -2
- data/lib/mutant/meta/example.rb +1 -1
- data/lib/mutant/mutation.rb +47 -21
- data/lib/mutant/mutator/node.rb +1 -1
- data/lib/mutant/mutator/node/literal/symbol.rb +1 -1
- data/lib/mutant/mutator/node/send.rb +4 -3
- data/lib/mutant/reporter/cli.rb +2 -0
- data/lib/mutant/reporter/cli/format.rb +23 -36
- data/lib/mutant/reporter/cli/printer.rb +66 -27
- data/lib/mutant/result.rb +45 -58
- data/lib/mutant/runner.rb +47 -154
- data/lib/mutant/runner/master.rb +174 -0
- data/lib/mutant/runner/scheduler.rb +141 -0
- data/lib/mutant/runner/worker.rb +93 -0
- data/lib/mutant/subject/method/instance.rb +1 -15
- data/lib/mutant/test.rb +2 -15
- data/lib/mutant/version.rb +1 -1
- data/meta/send.rb +16 -0
- data/mutant-rspec.gemspec +1 -1
- data/mutant.gemspec +1 -1
- data/spec/integration/mutant/rspec_spec.rb +0 -6
- data/spec/spec_helper.rb +9 -1
- data/spec/support/fake_actor.rb +93 -0
- data/spec/support/shared_context.rb +135 -0
- data/spec/unit/mutant/actor/actor_spec.rb +35 -0
- data/spec/unit/mutant/actor/binding_spec.rb +32 -0
- data/spec/unit/mutant/actor/env_spec.rb +49 -0
- data/spec/unit/mutant/actor/message_spec.rb +23 -0
- data/spec/unit/mutant/actor/receiver_spec.rb +60 -0
- data/spec/unit/mutant/actor/sender_spec.rb +22 -0
- data/spec/unit/mutant/cli_spec.rb +17 -4
- data/spec/unit/mutant/env_spec.rb +2 -2
- data/spec/unit/mutant/mailbox_spec.rb +33 -0
- data/spec/unit/mutant/mutation_spec.rb +52 -18
- data/spec/unit/mutant/mutator/registry_spec.rb +4 -4
- data/spec/unit/mutant/reporter/cli_spec.rb +131 -249
- data/spec/unit/mutant/result/env_spec.rb +55 -0
- data/spec/unit/mutant/result/subject_spec.rb +43 -0
- data/spec/unit/mutant/runner/master_spec.rb +199 -0
- data/spec/unit/mutant/runner/scheduler_spec.rb +161 -0
- data/spec/unit/mutant/runner/worker_spec.rb +73 -0
- data/spec/unit/mutant/runner_spec.rb +60 -118
- data/spec/unit/mutant/subject/method/instance_spec.rb +18 -31
- data/spec/unit/mutant/warning_filter_spec.rb +1 -1
- metadata +39 -14
- data/lib/mutant/runner/collector.rb +0 -133
- data/lib/mutant/warning_expectation.rb +0 -47
- data/spec/unit/mutant/runner/collector_spec.rb +0 -198
- data/spec/unit/mutant/test_spec.rb +0 -23
- data/spec/unit/mutant/warning_expectation_spec.rb +0 -80
- data/test_app/Gemfile.rspec2 +0 -6
@@ -0,0 +1,141 @@
|
|
1
|
+
module Mutant
|
2
|
+
class Runner
|
3
|
+
# Job scheduler
|
4
|
+
class Scheduler
|
5
|
+
include Concord.new(:env)
|
6
|
+
|
7
|
+
# Initialize object
|
8
|
+
#
|
9
|
+
# @return [undefined]
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
#
|
13
|
+
def initialize(*)
|
14
|
+
super
|
15
|
+
@index = 0
|
16
|
+
@start = Time.now
|
17
|
+
@active_jobs = Set.new
|
18
|
+
@subject_results = Hash.new do |_hash, subject|
|
19
|
+
Result::Subject.new(
|
20
|
+
subject: subject,
|
21
|
+
mutation_results: []
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return runner status
|
27
|
+
#
|
28
|
+
# @return [Status]
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
#
|
32
|
+
def status
|
33
|
+
Status.new(
|
34
|
+
env_result: env_result,
|
35
|
+
done: done?,
|
36
|
+
active_jobs: @active_jobs.dup
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return next job
|
41
|
+
#
|
42
|
+
# @return [Job]
|
43
|
+
# in case there is a next job
|
44
|
+
#
|
45
|
+
# @return [nil]
|
46
|
+
# otherwise
|
47
|
+
#
|
48
|
+
# @api private
|
49
|
+
def next_job
|
50
|
+
return unless next_mutation?
|
51
|
+
|
52
|
+
Job.new(
|
53
|
+
mutation: mutations.fetch(@index),
|
54
|
+
index: @index
|
55
|
+
).tap do |job|
|
56
|
+
@index += 1
|
57
|
+
@active_jobs << job
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Consume job result
|
62
|
+
#
|
63
|
+
# @param [JobResult] job_result
|
64
|
+
#
|
65
|
+
# @return [self]
|
66
|
+
#
|
67
|
+
# @api private
|
68
|
+
#
|
69
|
+
def job_result(job_result)
|
70
|
+
@active_jobs.delete(job_result.job)
|
71
|
+
mutation_result(job_result.result)
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
# Test if mutation run is done
|
78
|
+
#
|
79
|
+
# @return [Boolean]
|
80
|
+
#
|
81
|
+
# @api private
|
82
|
+
#
|
83
|
+
def done?
|
84
|
+
!env_result.continue? || (!next_mutation? && @active_jobs.empty?)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Handle mutation finish
|
88
|
+
#
|
89
|
+
# @param [Result::Mutation] mutation_result
|
90
|
+
#
|
91
|
+
# @return [self]
|
92
|
+
#
|
93
|
+
# @api private
|
94
|
+
#
|
95
|
+
def mutation_result(mutation_result)
|
96
|
+
mutation = mutation_result.mutation
|
97
|
+
|
98
|
+
original = @subject_results[mutation.subject]
|
99
|
+
|
100
|
+
@subject_results[mutation.subject] = original.update(
|
101
|
+
mutation_results: (original.mutation_results.dup << mutation_result)
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Test if a next mutation exist
|
106
|
+
#
|
107
|
+
# @return [Boolean]
|
108
|
+
#
|
109
|
+
# @api private
|
110
|
+
#
|
111
|
+
def next_mutation?
|
112
|
+
mutations.length > @index
|
113
|
+
end
|
114
|
+
|
115
|
+
# Return mutations
|
116
|
+
#
|
117
|
+
# @return [Array<Mutation>]
|
118
|
+
#
|
119
|
+
# @api private
|
120
|
+
#
|
121
|
+
def mutations
|
122
|
+
env.mutations
|
123
|
+
end
|
124
|
+
|
125
|
+
# Return current result
|
126
|
+
#
|
127
|
+
# @return [Result::Env]
|
128
|
+
#
|
129
|
+
# @api private
|
130
|
+
#
|
131
|
+
def env_result
|
132
|
+
Result::Env.new(
|
133
|
+
env: env,
|
134
|
+
runtime: Time.now - @start,
|
135
|
+
subject_results: @subject_results.values
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
end # Scheduler
|
140
|
+
end # Runner
|
141
|
+
end # Mutant
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Mutant
|
2
|
+
class Runner
|
3
|
+
# Mutation killing worker receiving work from parent
|
4
|
+
class Worker
|
5
|
+
include Adamantium::Flat, Anima.new(:config, :id, :parent)
|
6
|
+
|
7
|
+
private_class_method :new
|
8
|
+
|
9
|
+
# Run worker
|
10
|
+
#
|
11
|
+
# @param [Hash<Symbol, Object] attributes
|
12
|
+
#
|
13
|
+
# @return [Actor::Sender]
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
#
|
17
|
+
def self.run(attributes)
|
18
|
+
attributes.fetch(:config).actor_env.spawn do |actor|
|
19
|
+
worker = new(attributes)
|
20
|
+
worker.send(:run, actor)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# Worker loop
|
27
|
+
#
|
28
|
+
# @return [self]
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
#
|
32
|
+
# rubocop:disable Lint/Loop
|
33
|
+
#
|
34
|
+
def run(actor)
|
35
|
+
begin
|
36
|
+
parent.call(Actor::Message.new(:ready, actor.sender))
|
37
|
+
end until handle(actor.receiver.call)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Handle job
|
41
|
+
#
|
42
|
+
# @param [Message] message
|
43
|
+
#
|
44
|
+
# @return [Boolean]
|
45
|
+
#
|
46
|
+
# @api private
|
47
|
+
#
|
48
|
+
def handle(message)
|
49
|
+
type, payload = message.type, message.payload
|
50
|
+
case message.type
|
51
|
+
when :job
|
52
|
+
handle_job(payload)
|
53
|
+
nil
|
54
|
+
when :stop
|
55
|
+
true
|
56
|
+
else
|
57
|
+
fail Actor::ProtocolError, "Unknown command: #{type.inspect}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Handle mutation
|
62
|
+
#
|
63
|
+
# @param [Job] job
|
64
|
+
#
|
65
|
+
# @return [undefined]
|
66
|
+
#
|
67
|
+
# @api private
|
68
|
+
#
|
69
|
+
def handle_job(job)
|
70
|
+
parent.call(Actor::Message.new(:result, JobResult.new(job: job, result: run_mutation(job))))
|
71
|
+
end
|
72
|
+
|
73
|
+
# Run mutation
|
74
|
+
#
|
75
|
+
# @param [Mutation] mutation
|
76
|
+
#
|
77
|
+
# @return [Report::Mutation]
|
78
|
+
#
|
79
|
+
# @api private
|
80
|
+
#
|
81
|
+
def run_mutation(job)
|
82
|
+
mutation = job.mutation
|
83
|
+
test_result = mutation.kill(config.isolation, config.integration)
|
84
|
+
Result::Mutation.new(
|
85
|
+
index: job.index,
|
86
|
+
mutation: mutation,
|
87
|
+
test_result: test_result
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
end # Worker
|
92
|
+
end # Runner
|
93
|
+
end # Mutant
|
@@ -7,10 +7,6 @@ module Mutant
|
|
7
7
|
NAME_INDEX = 0
|
8
8
|
SYMBOL = '#'.freeze
|
9
9
|
|
10
|
-
# A list of methods that will warn when they are undefined
|
11
|
-
WARN_METHODS_UNDEFINED =
|
12
|
-
RUBY_ENGINE.eql?('ruby') ? [:initialize, :__send__, :object_id].freeze : EMPTY_ARRAY
|
13
|
-
|
14
10
|
# Test if method is public
|
15
11
|
#
|
16
12
|
# @return [Boolean]
|
@@ -22,8 +18,6 @@ module Mutant
|
|
22
18
|
end
|
23
19
|
memoize :public?
|
24
20
|
|
25
|
-
LINE_INCREMENT = defined?(Zombie) ? -11 : 5
|
26
|
-
|
27
21
|
# Prepare subject for mutation insertion
|
28
22
|
#
|
29
23
|
# @return [self]
|
@@ -31,15 +25,7 @@ module Mutant
|
|
31
25
|
# @api private
|
32
26
|
#
|
33
27
|
def prepare
|
34
|
-
|
35
|
-
if WARN_METHODS_UNDEFINED.include?(name)
|
36
|
-
["#{__FILE__}:#{__LINE__ + LINE_INCREMENT}: warning: undefining `#{name}' may cause serious problems\n"]
|
37
|
-
else
|
38
|
-
EMPTY_ARRAY
|
39
|
-
end
|
40
|
-
WarningExpectation.new(expected_warnings).execute do
|
41
|
-
scope.send(:undef_method, name)
|
42
|
-
end
|
28
|
+
scope.send(:undef_method, name)
|
43
29
|
self
|
44
30
|
end
|
45
31
|
|
data/lib/mutant/test.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Mutant
|
2
2
|
# Abstract base class for test that might kill a mutation
|
3
3
|
class Test
|
4
|
-
include Adamantium::Flat,
|
4
|
+
include Adamantium::Flat, Anima.new(:id, :expression)
|
5
5
|
|
6
6
|
# Return test identification
|
7
7
|
#
|
@@ -9,20 +9,7 @@ module Mutant
|
|
9
9
|
#
|
10
10
|
# @api private
|
11
11
|
#
|
12
|
-
|
13
|
-
"#{integration.name}:#{expression.syntax}"
|
14
|
-
end
|
15
|
-
memoize :identification
|
16
|
-
|
17
|
-
# Run test, return report
|
18
|
-
#
|
19
|
-
# @return [Report]
|
20
|
-
#
|
21
|
-
# @api private
|
22
|
-
#
|
23
|
-
def run
|
24
|
-
integration.run(self)
|
25
|
-
end
|
12
|
+
alias_method :identification, :id
|
26
13
|
|
27
14
|
end # Test
|
28
15
|
end # Mutant
|
data/lib/mutant/version.rb
CHANGED
data/meta/send.rb
CHANGED
@@ -5,6 +5,7 @@ Mutant::Meta::Example.add do
|
|
5
5
|
|
6
6
|
singleton_mutations
|
7
7
|
mutation 'a == b'
|
8
|
+
mutation 'a >= b'
|
8
9
|
mutation 'a.eql?(b)'
|
9
10
|
mutation 'a.equal?(b)'
|
10
11
|
mutation 'nil > b'
|
@@ -52,6 +53,7 @@ Mutant::Meta::Example.add do
|
|
52
53
|
|
53
54
|
singleton_mutations
|
54
55
|
mutation 'a == b'
|
56
|
+
mutation 'a <= b'
|
55
57
|
mutation 'a.eql?(b)'
|
56
58
|
mutation 'a.equal?(b)'
|
57
59
|
mutation 'nil < b'
|
@@ -187,12 +189,26 @@ Mutant::Meta::Example.add do
|
|
187
189
|
mutation 'self.gsub(a, b)'
|
188
190
|
end
|
189
191
|
|
192
|
+
Mutant::Meta::Example.add do
|
193
|
+
source 'foo.__send__(bar)'
|
194
|
+
|
195
|
+
singleton_mutations
|
196
|
+
mutation 'foo.__send__'
|
197
|
+
mutation 'foo.public_send(bar)'
|
198
|
+
mutation 'bar'
|
199
|
+
mutation 'foo'
|
200
|
+
mutation 'self.__send__(bar)'
|
201
|
+
mutation 'foo.__send__(nil)'
|
202
|
+
mutation 'foo.__send__(self)'
|
203
|
+
end
|
204
|
+
|
190
205
|
Mutant::Meta::Example.add do
|
191
206
|
source 'foo.send(bar)'
|
192
207
|
|
193
208
|
singleton_mutations
|
194
209
|
mutation 'foo.send'
|
195
210
|
mutation 'foo.public_send(bar)'
|
211
|
+
mutation 'foo.__send__(bar)'
|
196
212
|
mutation 'bar'
|
197
213
|
mutation 'foo'
|
198
214
|
mutation 'self.send(bar)'
|
data/mutant-rspec.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.extra_rdoc_files = %w[TODO LICENSE]
|
19
19
|
|
20
20
|
gem.add_runtime_dependency('mutant', "~> #{gem.version}")
|
21
|
-
gem.add_runtime_dependency('rspec-core', '>=
|
21
|
+
gem.add_runtime_dependency('rspec-core', '>= 3.0.0', '< 3.2.0')
|
22
22
|
|
23
23
|
gem.add_development_dependency('bundler', '~> 1.3', '>= 1.3.5')
|
24
24
|
end
|
data/mutant.gemspec
CHANGED
@@ -31,7 +31,7 @@ Gem::Specification.new do |gem|
|
|
31
31
|
gem.add_runtime_dependency('procto', '~> 0.0.2')
|
32
32
|
gem.add_runtime_dependency('abstract_type', '~> 0.0.7')
|
33
33
|
gem.add_runtime_dependency('unparser', '~> 0.1.16')
|
34
|
-
gem.add_runtime_dependency('ice_nine', '~> 0.11.
|
34
|
+
gem.add_runtime_dependency('ice_nine', '~> 0.11.1')
|
35
35
|
gem.add_runtime_dependency('adamantium', '~> 0.2.0')
|
36
36
|
gem.add_runtime_dependency('memoizable', '~> 0.4.2')
|
37
37
|
gem.add_runtime_dependency('equalizer', '~> 0.0.9')
|
data/spec/spec_helper.rb
CHANGED
@@ -12,7 +12,7 @@ if ENV['COVERAGE'] == 'true'
|
|
12
12
|
add_filter 'lib/mutant/zombifier'
|
13
13
|
add_filter 'lib/mutant/zombifier/*'
|
14
14
|
|
15
|
-
minimum_coverage
|
15
|
+
minimum_coverage 100
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -45,8 +45,16 @@ module ParserHelper
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
+
module MessageHelper
|
49
|
+
def message(*arguments)
|
50
|
+
Mutant::Actor::Message.new(*arguments)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
48
54
|
RSpec.configure do |config|
|
55
|
+
config.extend(SharedContext)
|
49
56
|
config.include(CompressHelper)
|
57
|
+
config.include(MessageHelper)
|
50
58
|
config.include(ParserHelper)
|
51
59
|
config.include(Mutant::AST::Sexp)
|
52
60
|
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'mutant/actor'
|
2
|
+
|
3
|
+
# A fake actor used from specs
|
4
|
+
module FakeActor
|
5
|
+
class Expectation
|
6
|
+
include Concord::Public.new(:name, :message)
|
7
|
+
end
|
8
|
+
|
9
|
+
class MessageSequence
|
10
|
+
include Adamantium::Flat, Concord.new(:messages)
|
11
|
+
|
12
|
+
def self.new
|
13
|
+
super([])
|
14
|
+
end
|
15
|
+
|
16
|
+
def add(name, *message_arguments)
|
17
|
+
messages << Expectation.new(name, Mutant::Actor::Message.new(*message_arguments))
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def sending(expectation)
|
22
|
+
raise "Unexpected send: #{expectation.inspect}" if messages.empty?
|
23
|
+
expected = messages.shift
|
24
|
+
unless expectation.eql?(expected)
|
25
|
+
raise "Got:\n#{expectation.inspect}\nExpected:\n#{expected.inspect}"
|
26
|
+
end
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def receiving(name)
|
31
|
+
raise "No message to read for #{name.inspect}" if messages.empty?
|
32
|
+
expected = messages.shift
|
33
|
+
raise "Unexpected message #{expected.inspect} for #{name.inspect}" unless expected.name.eql?(name)
|
34
|
+
expected.message
|
35
|
+
end
|
36
|
+
|
37
|
+
def consumed?
|
38
|
+
messages.empty?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Env
|
43
|
+
include Concord.new(:messages, :actor_names)
|
44
|
+
|
45
|
+
def spawn
|
46
|
+
name = @actor_names.shift
|
47
|
+
raise 'Tried to spawn actor when no name available' unless name
|
48
|
+
actor = actor(name)
|
49
|
+
yield actor if block_given?
|
50
|
+
actor.sender
|
51
|
+
end
|
52
|
+
|
53
|
+
def current
|
54
|
+
actor(:current)
|
55
|
+
end
|
56
|
+
|
57
|
+
def actor(name)
|
58
|
+
Actor.new(name, @messages)
|
59
|
+
end
|
60
|
+
end # Env
|
61
|
+
|
62
|
+
class Actor
|
63
|
+
include Concord.new(:name, :messages)
|
64
|
+
|
65
|
+
def receiver
|
66
|
+
Receiver.new(name, messages)
|
67
|
+
end
|
68
|
+
|
69
|
+
def sender
|
70
|
+
Sender.new(name, messages)
|
71
|
+
end
|
72
|
+
|
73
|
+
def bind(sender)
|
74
|
+
Mutant::Actor::Binding.new(self, sender)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class Sender
|
79
|
+
include Concord.new(:name, :messages)
|
80
|
+
|
81
|
+
def call(message)
|
82
|
+
messages.sending(Expectation.new(name, message))
|
83
|
+
end
|
84
|
+
end # Sender
|
85
|
+
|
86
|
+
class Receiver
|
87
|
+
include Concord::Public.new(:name, :messages)
|
88
|
+
|
89
|
+
def call
|
90
|
+
messages.receiving(name)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|