mutant 0.6.7 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|