mutant 0.7.3 → 0.7.4

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