mutant 0.7.3 → 0.7.4

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/.travis.yml +3 -2
  4. data/Changelog.md +7 -2
  5. data/Gemfile +0 -1
  6. data/Gemfile.devtools +9 -37
  7. data/README.md +1 -1
  8. data/circle.yml +1 -1
  9. data/config/flay.yml +1 -1
  10. data/config/reek.yml +6 -19
  11. data/config/rubocop.yml +58 -63
  12. data/lib/mutant.rb +8 -4
  13. data/lib/mutant/ast.rb +1 -1
  14. data/lib/mutant/cli.rb +12 -6
  15. data/lib/mutant/env.rb +17 -1
  16. data/lib/mutant/isolation.rb +4 -2
  17. data/lib/mutant/loader.rb +4 -0
  18. data/lib/mutant/matcher/compiler.rb +2 -0
  19. data/lib/mutant/mutation.rb +2 -0
  20. data/lib/mutant/mutator/node/const.rb +1 -1
  21. data/lib/mutant/mutator/node/generic.rb +1 -1
  22. data/lib/mutant/mutator/node/if.rb +2 -0
  23. data/lib/mutant/parallel.rb +93 -0
  24. data/lib/mutant/{runner → parallel}/master.rb +90 -45
  25. data/lib/mutant/parallel/source.rb +73 -0
  26. data/lib/mutant/{runner → parallel}/worker.rb +13 -30
  27. data/lib/mutant/reporter/cli.rb +8 -11
  28. data/lib/mutant/reporter/cli/printer.rb +14 -8
  29. data/lib/mutant/result.rb +0 -10
  30. data/lib/mutant/runner.rb +49 -43
  31. data/lib/mutant/runner/{scheduler.rb → sink.rb} +9 -68
  32. data/lib/mutant/version.rb +1 -1
  33. data/lib/mutant/zombifier/file.rb +28 -9
  34. data/meta/if.rb +8 -0
  35. data/meta/match_current_line.rb +1 -0
  36. data/spec/integration/mutant/corpus_spec.rb +1 -1
  37. data/spec/integration/mutant/null_spec.rb +1 -1
  38. data/spec/integration/mutant/rspec_spec.rb +1 -1
  39. data/spec/integration/mutant/test_mutator_handles_types_spec.rb +1 -1
  40. data/spec/integration/mutant/zombie_spec.rb +1 -1
  41. data/spec/support/corpus.rb +2 -0
  42. data/spec/support/fake_actor.rb +20 -10
  43. data/spec/support/mutation_verifier.rb +2 -0
  44. data/spec/support/shared_context.rb +12 -19
  45. data/spec/unit/mutant/env_spec.rb +20 -2
  46. data/spec/unit/mutant/expression_spec.rb +4 -1
  47. data/spec/unit/mutant/parallel/master_spec.rb +339 -0
  48. data/spec/unit/mutant/parallel/source/array_spec.rb +47 -0
  49. data/spec/unit/mutant/{runner → parallel}/worker_spec.rb +23 -26
  50. data/spec/unit/mutant/parallel_spec.rb +16 -0
  51. data/spec/unit/mutant/reporter/cli_spec.rb +1 -1
  52. data/spec/unit/mutant/reporter/trace_spec.rb +9 -0
  53. data/spec/unit/mutant/result/env_spec.rb +0 -55
  54. data/spec/unit/mutant/runner/driver_spec.rb +26 -0
  55. data/spec/unit/mutant/runner/sink_spec.rb +162 -0
  56. data/spec/unit/mutant/runner_spec.rb +60 -63
  57. data/spec/unit/mutant/warning_filter_spec.rb +2 -1
  58. data/test_app/Gemfile.devtools +9 -37
  59. metadata +21 -11
  60. data/spec/unit/mutant/runner/master_spec.rb +0 -199
  61. data/spec/unit/mutant/runner/scheduler_spec.rb +0 -161
@@ -1,27 +1,23 @@
1
1
  module Mutant
2
- class Runner
3
- # Mutation killing worker receiving work from parent
2
+ module Parallel
3
+ # Parallel execution worker
4
4
  class Worker
5
- include Adamantium::Flat, Anima.new(:config, :id, :parent)
6
-
7
- private_class_method :new
5
+ include Adamantium::Flat, Anima.new(:actor, :processor, :parent)
8
6
 
9
7
  # Run worker
10
8
  #
11
9
  # @param [Hash<Symbol, Object] attributes
12
10
  #
13
- # @return [Actor::Sender]
11
+ # @return [self]
14
12
  #
15
13
  # @api private
16
14
  #
17
15
  def self.run(attributes)
18
- attributes.fetch(:config).actor_env.spawn do |actor|
19
- worker = new(attributes)
20
- worker.__send__(:run, actor)
21
- end
16
+ new(attributes).run
17
+ self
22
18
  end
23
19
 
24
- private
20
+ private_class_method :new
25
21
 
26
22
  # Worker loop
27
23
  #
@@ -31,12 +27,14 @@ module Mutant
31
27
  #
32
28
  # rubocop:disable Lint/Loop
33
29
  #
34
- def run(actor)
30
+ def run
35
31
  begin
36
32
  parent.call(Actor::Message.new(:ready, actor.sender))
37
33
  end until handle(actor.receiver.call)
38
34
  end
39
35
 
36
+ private
37
+
40
38
  # Handle job
41
39
  #
42
40
  # @param [Message] message
@@ -67,25 +65,10 @@ module Mutant
67
65
  # @api private
68
66
  #
69
67
  def handle_job(job)
70
- parent.call(Actor::Message.new(:result, JobResult.new(job: job, result: run_mutation(job.mutation))))
71
- end
72
-
73
- # Run mutation
74
- #
75
- # @param [Mutation] mutation
76
- #
77
- # @return [Result::Mutation]
78
- #
79
- # @api private
80
- #
81
- def run_mutation(mutation)
82
- test_result = mutation.kill(config.isolation, config.integration)
83
- Result::Mutation.new(
84
- mutation: mutation,
85
- test_result: test_result
86
- )
68
+ result = processor.call(job.payload)
69
+ parent.call(Actor::Message.new(:result, JobResult.new(job: job, payload: result)))
87
70
  end
88
71
 
89
72
  end # Worker
90
- end # Runner
73
+ end # Parallel
91
74
  end # Mutant
@@ -14,13 +14,11 @@ module Mutant
14
14
  #
15
15
  def self.build(output)
16
16
  tty = output.respond_to?(:tty?) && output.tty?
17
- format =
18
- if !Mutant.ci? && tty && Tput::INSTANCE.available
19
- Format::Framed.new(tty: tty, tput: Tput::INSTANCE)
20
- else
21
- Format::Progressive.new(tty: tty)
22
- end
23
-
17
+ format = if !Mutant.ci? && tty && Tput::INSTANCE.available
18
+ Format::Framed.new(tty: tty, tput: Tput::INSTANCE)
19
+ else
20
+ Format::Progressive.new(tty: tty)
21
+ end
24
22
  new(output, format)
25
23
  end
26
24
 
@@ -39,15 +37,14 @@ module Mutant
39
37
 
40
38
  # Report progress object
41
39
  #
42
- # @param [Runner::Collector] collector
40
+ # @param [Parallel::Status] status
43
41
  #
44
42
  # @return [self]
45
43
  #
46
44
  # @api private
47
45
  #
48
- def progress(collector)
49
- write(format.progress(collector))
50
-
46
+ def progress(status)
47
+ write(format.progress(status))
51
48
  self
52
49
  end
53
50
 
@@ -137,7 +137,7 @@ module Mutant
137
137
  # Printer for runner status
138
138
  class Status < self
139
139
 
140
- delegate(:active_jobs, :env_result)
140
+ delegate(:active_jobs, :payload)
141
141
 
142
142
  # Print progress for collector
143
143
  #
@@ -146,7 +146,7 @@ module Mutant
146
146
  # @api private
147
147
  #
148
148
  def run
149
- visit(EnvProgress, object.env_result)
149
+ visit(EnvProgress, payload)
150
150
  info('Active subjects: %d', active_subject_results.length)
151
151
  visit_collection(SubjectProgress, active_subject_results)
152
152
  job_status
@@ -164,8 +164,8 @@ module Mutant
164
164
  def job_status
165
165
  return if active_jobs.empty?
166
166
  info('Active Jobs:')
167
- object.active_jobs.sort_by(&:index).each do |job|
168
- info('%d: %s', job.index, job.mutation.identification)
167
+ active_jobs.sort_by(&:index).each do |job|
168
+ info('%d: %s', job.index, job.payload.identification)
169
169
  end
170
170
  end
171
171
 
@@ -176,9 +176,10 @@ module Mutant
176
176
  # @api private
177
177
  #
178
178
  def active_subject_results
179
- active_subjects = active_jobs.map(&:mutation).flat_map(&:subject).to_set
179
+ active_mutation_jobs = active_jobs.select { |job| job.payload.kind_of?(Mutant::Mutation) }
180
+ active_subjects = active_mutation_jobs.map(&:payload).flat_map(&:subject).to_set
180
181
 
181
- env_result.subject_results.select do |subject_result|
182
+ payload.subject_results.select do |subject_result|
182
183
  active_subjects.include?(subject_result.subject)
183
184
  end
184
185
  end
@@ -196,6 +197,8 @@ module Mutant
196
197
  #
197
198
  # @api private
198
199
  #
200
+ # rubocop:disable AbcSize
201
+ #
199
202
  def run
200
203
  info 'Mutant configuration:'
201
204
  info 'Matcher: %s', object.matcher_config.inspect
@@ -230,6 +233,8 @@ module Mutant
230
233
  #
231
234
  # @api private
232
235
  #
236
+ # rubocop:disable MethodLength
237
+ #
233
238
  def run
234
239
  visit(Config, env.config)
235
240
  info 'Subjects: %s', amount_subjects
@@ -385,7 +390,7 @@ module Mutant
385
390
  # @api private
386
391
  #
387
392
  def object
388
- super().env_result
393
+ super().payload
389
394
  end
390
395
  end
391
396
 
@@ -479,7 +484,8 @@ module Mutant
479
484
 
480
485
  delegate :mutation, :test_result
481
486
 
482
- DIFF_ERROR_MESSAGE = 'BUG: Mutation NOT resulted in exactly one diff hunk. Please report a reproduction!'.freeze
487
+ DIFF_ERROR_MESSAGE =
488
+ 'BUG: Mutation NOT resulted in exactly one diff hunk. Please report a reproduction!'.freeze
483
489
 
484
490
  MAP = {
485
491
  Mutant::Mutation::Evil => :evil_details,
@@ -98,16 +98,6 @@ module Mutant
98
98
  end
99
99
  memoize :success?
100
100
 
101
- # Test if runner should continue on env
102
- #
103
- # @return [Boolean]
104
- #
105
- # @api private
106
- #
107
- def continue?
108
- !env.config.fail_fast || subject_results.all?(&:success?)
109
- end
110
-
111
101
  # Return failed subject results
112
102
  #
113
103
  # @return [Array<Result::Subject>]
@@ -3,25 +3,6 @@ module Mutant
3
3
  class Runner
4
4
  include Adamantium::Flat, Concord.new(:env), Procto.call(:result)
5
5
 
6
- # Status of the runner execution
7
- class Status
8
- include Adamantium, Anima::Update, Anima.new(
9
- :env_result,
10
- :active_jobs,
11
- :done
12
- )
13
- end # Status
14
-
15
- # Job to push to workers
16
- class Job
17
- include Adamantium::Flat, Anima.new(:index, :mutation)
18
- end # Job
19
-
20
- # Job result object received from workers
21
- class JobResult
22
- include Adamantium::Flat, Anima.new(:job, :result)
23
- end
24
-
25
6
  # Initialize object
26
7
  #
27
8
  # @return [undefined]
@@ -32,37 +13,72 @@ module Mutant
32
13
  super
33
14
 
34
15
  reporter.start(env)
16
+
17
+ run_mutation_analysis
18
+ end
19
+
20
+ # Return result
21
+ #
22
+ # @return [Result::Env]
23
+ #
24
+ # @api private
25
+ #
26
+ attr_reader :result
27
+
28
+ private
29
+
30
+ # Run mutation analysis
31
+ #
32
+ # @return [undefined]
33
+ #
34
+ # @api private
35
+ #
36
+ def run_mutation_analysis
35
37
  config.integration.setup
36
38
 
37
- @master = config.actor_env.new_mailbox.bind(Master.call(env))
39
+ @result = run_driver(Parallel.async(mutation_test_config))
40
+ reporter.report(@result)
41
+ end
38
42
 
43
+ # Run driver
44
+ #
45
+ # @param [Driver] driver
46
+ #
47
+ # @return [Object]
48
+ # the last returned status payload
49
+ #
50
+ # @api private
51
+ #
52
+ def run_driver(driver)
39
53
  status = nil
40
54
 
41
55
  loop do
42
- status = current_status
43
- break if status.done
56
+ status = driver.status
44
57
  reporter.progress(status)
58
+ break if status.done
45
59
  Kernel.sleep(reporter.delay)
46
60
  end
47
61
 
48
- reporter.progress(status)
49
-
50
- @master.call(:stop)
51
-
52
- @result = status.env_result
62
+ driver.stop
53
63
 
54
- reporter.report(@result)
64
+ status.payload
55
65
  end
56
66
 
57
- # Return result
67
+ # Return mutation test config
58
68
  #
59
- # @return [Result::Env]
69
+ # @return [Parallell::Config]
60
70
  #
61
71
  # @api private
62
72
  #
63
- attr_reader :result
64
-
65
- private
73
+ def mutation_test_config
74
+ Parallel::Config.new(
75
+ env: config.actor_env,
76
+ jobs: config.jobs,
77
+ source: Parallel::Source::Array.new(env.mutations),
78
+ sink: Sink.new(env),
79
+ processor: env.method(:kill_mutation)
80
+ )
81
+ end
66
82
 
67
83
  # Return reporter
68
84
  #
@@ -84,15 +100,5 @@ module Mutant
84
100
  env.config
85
101
  end
86
102
 
87
- # Return current status
88
- #
89
- # @return [Status]
90
- #
91
- # @api private
92
- #
93
- def current_status
94
- @master.call(:status)
95
- end
96
-
97
103
  end # Runner
98
104
  end # Mutant
@@ -1,7 +1,7 @@
1
1
  module Mutant
2
2
  class Runner
3
- # Job scheduler
4
- class Scheduler
3
+ # Mutation result sink
4
+ class Sink
5
5
  include Concord.new(:env)
6
6
 
7
7
  # Initialize object
@@ -12,9 +12,7 @@ module Mutant
12
12
  #
13
13
  def initialize(*)
14
14
  super
15
- @index = 0
16
15
  @start = Time.now
17
- @active_jobs = Set.new
18
16
  @subject_results = Hash.new do |_hash, subject|
19
17
  Result::Subject.new(
20
18
  subject: subject,
@@ -30,58 +28,17 @@ module Mutant
30
28
  # @api private
31
29
  #
32
30
  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
31
+ env_result
59
32
  end
60
33
 
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
34
+ # Test if scheduling stopped
78
35
  #
79
36
  # @return [Boolean]
80
37
  #
81
38
  # @api private
82
39
  #
83
- def done?
84
- !env_result.continue? || (!next_mutation? && @active_jobs.empty?)
40
+ def stop?
41
+ env.config.fail_fast && !env_result.subject_results.all?(&:success?)
85
42
  end
86
43
 
87
44
  # Handle mutation finish
@@ -92,7 +49,7 @@ module Mutant
92
49
  #
93
50
  # @api private
94
51
  #
95
- def mutation_result(mutation_result)
52
+ def result(mutation_result)
96
53
  mutation = mutation_result.mutation
97
54
 
98
55
  original = @subject_results[mutation.subject]
@@ -100,27 +57,11 @@ module Mutant
100
57
  @subject_results[mutation.subject] = original.update(
101
58
  mutation_results: (original.mutation_results.dup << mutation_result)
102
59
  )
103
- end
104
60
 
105
- # Test if a next mutation exist
106
- #
107
- # @return [Boolean]
108
- #
109
- # @api private
110
- #
111
- def next_mutation?
112
- mutations.length > @index
61
+ self
113
62
  end
114
63
 
115
- # Return mutations
116
- #
117
- # @return [Array<Mutation>]
118
- #
119
- # @api private
120
- #
121
- def mutations
122
- env.mutations
123
- end
64
+ private
124
65
 
125
66
  # Return current result
126
67
  #