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.
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