mutant 0.6.7 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +10 -0
  3. data/README.md +1 -1
  4. data/config/flay.yml +1 -1
  5. data/config/reek.yml +11 -40
  6. data/config/rubocop.yml +1 -1
  7. data/lib/mutant.rb +10 -2
  8. data/lib/mutant/actor.rb +64 -0
  9. data/lib/mutant/actor/actor.rb +50 -0
  10. data/lib/mutant/actor/env.rb +35 -0
  11. data/lib/mutant/actor/mailbox.rb +53 -0
  12. data/lib/mutant/actor/receiver.rb +48 -0
  13. data/lib/mutant/actor/sender.rb +27 -0
  14. data/lib/mutant/ast/types.rb +1 -1
  15. data/lib/mutant/cli.rb +2 -0
  16. data/lib/mutant/config.rb +2 -1
  17. data/lib/mutant/env.rb +6 -2
  18. data/lib/mutant/expression/methods.rb +1 -1
  19. data/lib/mutant/integration.rb +11 -1
  20. data/lib/mutant/isolation.rb +1 -2
  21. data/lib/mutant/meta/example.rb +1 -1
  22. data/lib/mutant/mutation.rb +47 -21
  23. data/lib/mutant/mutator/node.rb +1 -1
  24. data/lib/mutant/mutator/node/literal/symbol.rb +1 -1
  25. data/lib/mutant/mutator/node/send.rb +4 -3
  26. data/lib/mutant/reporter/cli.rb +2 -0
  27. data/lib/mutant/reporter/cli/format.rb +23 -36
  28. data/lib/mutant/reporter/cli/printer.rb +66 -27
  29. data/lib/mutant/result.rb +45 -58
  30. data/lib/mutant/runner.rb +47 -154
  31. data/lib/mutant/runner/master.rb +174 -0
  32. data/lib/mutant/runner/scheduler.rb +141 -0
  33. data/lib/mutant/runner/worker.rb +93 -0
  34. data/lib/mutant/subject/method/instance.rb +1 -15
  35. data/lib/mutant/test.rb +2 -15
  36. data/lib/mutant/version.rb +1 -1
  37. data/meta/send.rb +16 -0
  38. data/mutant-rspec.gemspec +1 -1
  39. data/mutant.gemspec +1 -1
  40. data/spec/integration/mutant/rspec_spec.rb +0 -6
  41. data/spec/spec_helper.rb +9 -1
  42. data/spec/support/fake_actor.rb +93 -0
  43. data/spec/support/shared_context.rb +135 -0
  44. data/spec/unit/mutant/actor/actor_spec.rb +35 -0
  45. data/spec/unit/mutant/actor/binding_spec.rb +32 -0
  46. data/spec/unit/mutant/actor/env_spec.rb +49 -0
  47. data/spec/unit/mutant/actor/message_spec.rb +23 -0
  48. data/spec/unit/mutant/actor/receiver_spec.rb +60 -0
  49. data/spec/unit/mutant/actor/sender_spec.rb +22 -0
  50. data/spec/unit/mutant/cli_spec.rb +17 -4
  51. data/spec/unit/mutant/env_spec.rb +2 -2
  52. data/spec/unit/mutant/mailbox_spec.rb +33 -0
  53. data/spec/unit/mutant/mutation_spec.rb +52 -18
  54. data/spec/unit/mutant/mutator/registry_spec.rb +4 -4
  55. data/spec/unit/mutant/reporter/cli_spec.rb +131 -249
  56. data/spec/unit/mutant/result/env_spec.rb +55 -0
  57. data/spec/unit/mutant/result/subject_spec.rb +43 -0
  58. data/spec/unit/mutant/runner/master_spec.rb +199 -0
  59. data/spec/unit/mutant/runner/scheduler_spec.rb +161 -0
  60. data/spec/unit/mutant/runner/worker_spec.rb +73 -0
  61. data/spec/unit/mutant/runner_spec.rb +60 -118
  62. data/spec/unit/mutant/subject/method/instance_spec.rb +18 -31
  63. data/spec/unit/mutant/warning_filter_spec.rb +1 -1
  64. metadata +39 -14
  65. data/lib/mutant/runner/collector.rb +0 -133
  66. data/lib/mutant/warning_expectation.rb +0 -47
  67. data/spec/unit/mutant/runner/collector_spec.rb +0 -198
  68. data/spec/unit/mutant/test_spec.rb +0 -23
  69. data/spec/unit/mutant/warning_expectation_spec.rb +0 -80
  70. 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
- expected_warnings =
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, Concord::Public.new(:integration, :expression)
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
- def identification
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
@@ -1,4 +1,4 @@
1
1
  module Mutant
2
2
  # The current mutant version
3
- VERSION = '0.6.7'.freeze
3
+ VERSION = '0.7.1'.freeze
4
4
  end # Mutant
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', '>= 2.14.1', '< 3.2.0')
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.0')
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')
@@ -38,12 +38,6 @@ RSpec.describe 'rspec integration' do
38
38
  end
39
39
  end
40
40
 
41
- context 'RSpec 2' do
42
- let(:gemfile) { 'Gemfile.rspec2' }
43
-
44
- it_behaves_like 'rspec integration'
45
- end
46
-
47
41
  context 'RSpec 3.0' do
48
42
  let(:gemfile) { 'Gemfile.rspec3.0' }
49
43
 
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 97.64 # TODO: raise this to 100, then mutation test
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