mutant 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -2
  3. data/Changelog.md +6 -0
  4. data/Gemfile.devtools +1 -1
  5. data/bin/mutant +2 -1
  6. data/config/flay.yml +1 -1
  7. data/config/reek.yml +3 -0
  8. data/lib/mutant.rb +5 -3
  9. data/lib/mutant/cli.rb +2 -2
  10. data/lib/mutant/config.rb +1 -1
  11. data/lib/mutant/diff.rb +1 -1
  12. data/lib/mutant/env.rb +1 -1
  13. data/lib/mutant/expression/methods.rb +16 -0
  14. data/lib/mutant/isolation.rb +16 -2
  15. data/lib/mutant/mutator/node/const.rb +1 -1
  16. data/lib/mutant/mutator/node/generic.rb +1 -1
  17. data/lib/mutant/mutator/registry.rb +1 -1
  18. data/lib/mutant/reporter/cli.rb +1 -1
  19. data/lib/mutant/reporter/cli/format.rb +0 -12
  20. data/lib/mutant/reporter/cli/printer.rb +1 -1
  21. data/lib/mutant/result.rb +1 -1
  22. data/lib/mutant/runner.rb +50 -29
  23. data/lib/mutant/runner/collector.rb +10 -11
  24. data/lib/mutant/subject.rb +1 -4
  25. data/lib/mutant/subject/method.rb +11 -0
  26. data/lib/mutant/version.rb +1 -1
  27. data/meta/send.rb +13 -0
  28. data/mutant.gemspec +2 -2
  29. data/spec/spec_helper.rb +2 -9
  30. data/spec/support/corpus.rb +5 -5
  31. data/spec/support/rb_bug.rb +18 -0
  32. data/spec/unit/mutant/cli_spec.rb +2 -2
  33. data/spec/unit/mutant/expression/methods_spec.rb +7 -1
  34. data/spec/unit/mutant/isolation_spec.rb +22 -2
  35. data/spec/unit/mutant/matcher/method/instance_spec.rb +5 -5
  36. data/spec/unit/mutant/matcher/method/singleton_spec.rb +5 -5
  37. data/spec/unit/mutant/reporter/cli_spec.rb +13 -14
  38. data/spec/unit/mutant/runner/collector_spec.rb +198 -0
  39. data/spec/unit/mutant/runner_spec.rb +2 -3
  40. data/spec/unit/mutant/subject/method/instance_spec.rb +8 -0
  41. data/spec/unit/mutant/subject/method/singleton_spec.rb +8 -0
  42. data/spec/unit/mutant/warning_filter_spec.rb +1 -1
  43. data/test_app/Gemfile.devtools +1 -1
  44. data/test_app/lib/test_app.rb +4 -0
  45. metadata +22 -19
  46. data/lib/parser_extensions.rb +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fc9905b0bf6ff92620afd6cbef3c9286a1521f28
4
- data.tar.gz: 6f78e44b45a05bab109e9f92720eaaf79aa83895
3
+ metadata.gz: ee4fb487c413c8106f0b826458a2914efa842775
4
+ data.tar.gz: 005c891d8fbcd19a1e4ec806ce39a2d05defcdd3
5
5
  SHA512:
6
- metadata.gz: 0da2bfbf865e7a50b9841d58762e094e4d19086af88ab8fb87458480306e229c301c5c0d8027b9644e6a1f03f958bbb4271307efea5280661baec583a9d89b59
7
- data.tar.gz: 404636cb866a23785a453878afcafdc9387173e2609a6bebf759bf60954933356fbd5962b84903e3f41cb51c724b70a14a194a5b87f2f92b02f0a46433363883
6
+ metadata.gz: 537342f44d70f3c23696de4962157ec74b7a0383f587c6db897bf216b7e2fe7f22572e84a54e3c055434a5bb5cbd061109655f25d1e3d2418643f9ec716cb147
7
+ data.tar.gz: 77045f8132668b54c86989ff3c0fa8e75615ebd555bbba305d7da26bded1c7414d6503d0ef16a5ae52547a3fa0a92f142f01f43678762d46dccf57d1bb9e1318
@@ -6,11 +6,11 @@ rvm:
6
6
  - 1.9.3
7
7
  - 2.0.0
8
8
  - 2.1.2
9
+ - 2.1.3
9
10
  - rbx-2
10
11
  matrix:
11
12
  allow_failures:
12
- - rvm: 2.1.2 # still segfaults on stack overflow on define_method
13
- - rvm: rbx-2 # Travis does not take care about RBX
13
+ - rvm: rbx-2 # travis does not maintain rbx setup correctly
14
14
  notifications:
15
15
  irc:
16
16
  channels:
@@ -1,3 +1,9 @@
1
+ # v0.6.4 2014-10-xx
2
+
3
+ * Do not buffer report prints, speedup large report generation.
4
+ * Fix some cases where --fail-fast semantics stopped far to late.
5
+ * Fix crashing / stuckage from using parallel in a nested way.
6
+
1
7
  # v0.6.3 2014-09-22
2
8
 
3
9
  * Add support for rspec-3.1.
@@ -38,7 +38,7 @@ group :metrics do
38
38
  gem 'flay', '~> 2.5.0'
39
39
  gem 'flog', '~> 4.2.1'
40
40
  gem 'reek', '~> 1.3.7'
41
- gem 'rubocop', '~> 0.23.0'
41
+ gem 'rubocop', '~> 0.26.1'
42
42
  gem 'simplecov', '~> 0.7.1'
43
43
  gem 'yardstick', '~> 0.9.9'
44
44
 
data/bin/mutant CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  trap('INT') do |status|
4
- exit! 128 + status
4
+ effective_status = status ? status + 128 : 128
5
+ exit! effective_status
5
6
  end
6
7
 
7
8
  require 'mutant'
@@ -1,3 +1,3 @@
1
1
  ---
2
2
  threshold: 18
3
- total_score: 1105
3
+ total_score: 1126
@@ -64,6 +64,7 @@ NestedIterators:
64
64
  - Mutant#self.singleton_subclass_instance
65
65
  - Mutant::CLI#parse
66
66
  - Mutant::Integration::Rspec#run
67
+ - Mutant::Isolation::Fork#self.call
67
68
  - Mutant::Mutator::Util::Array::Element#dispatch
68
69
  - Mutant::Mutator::Node::Resbody#mutate_captures
69
70
  - Mutant::Mutator::Node::Arguments#emit_argument_mutations
@@ -95,6 +96,7 @@ TooManyMethods:
95
96
  - Mutant::Subject
96
97
  - Mutant::Mutator::Node
97
98
  - Mutant::Reporter::CLI
99
+ - Mutant::Runner
98
100
  - Mutant::Meta::Example::Verification
99
101
  max_methods: 10
100
102
  TooManyStatements:
@@ -102,6 +104,7 @@ TooManyStatements:
102
104
  exclude:
103
105
  - Mutant#self.singleton_subclass_instance
104
106
  - Mutant::Integration::Rspec#run
107
+ - Mutant::Isolation::Fork#self.call
105
108
  - Mutant::Reporter::CLI#colorized_diff
106
109
  - Mutant::Reporter::CLI::Printer::EnvProgress#run
107
110
  - Mutant::Reporter::CLI::Printer::Config#run
@@ -5,10 +5,8 @@ require 'ice_nine'
5
5
  require 'abstract_type'
6
6
  require 'equalizer'
7
7
  require 'digest/sha1'
8
- require 'inflecto'
9
8
  require 'parser'
10
9
  require 'parser/current'
11
- require 'parser_extensions'
12
10
  require 'unparser'
13
11
  require 'ice_nine'
14
12
  require 'diff/lcs'
@@ -19,6 +17,10 @@ require 'morpher'
19
17
  require 'parallel'
20
18
  require 'open3'
21
19
 
20
+ # This setting is done to make errors within the parallel
21
+ # reporter / execution visible in the main thread.
22
+ Thread.abort_on_exception = true
23
+
22
24
  # Library namespace
23
25
  module Mutant
24
26
  # The frozen empty string used within mutant
@@ -221,7 +223,7 @@ module Mutant
221
223
  isolation: Mutant::Isolation::Fork,
222
224
  reporter: Reporter::CLI.build($stdout),
223
225
  zombie: false,
224
- processes: Mutant.ci? ? CI_DEFAULT_PROCESSOR_COUNT : Parallel.processor_count,
226
+ jobs: Mutant.ci? ? CI_DEFAULT_PROCESSOR_COUNT : Parallel.processor_count,
225
227
  expected_coverage: 100.0
226
228
  )
227
229
  end # Config
@@ -112,8 +112,8 @@ module Mutant
112
112
  opts.on('-r', '--require NAME', 'Require file with NAME') do |name|
113
113
  add(:requires, name)
114
114
  end
115
- opts.on('-j', '--jobs NUMBER', 'Number of kill processes. Defaults to number of processors.') do |number|
116
- update(processes: Integer(number))
115
+ opts.on('-j', '--jobs NUMBER', 'Number of kill jobs. Defaults to number of processors.') do |number|
116
+ update(jobs: Integer(number))
117
117
  end
118
118
  end
119
119
 
@@ -10,7 +10,7 @@ module Mutant
10
10
  :reporter,
11
11
  :isolation,
12
12
  :fail_fast,
13
- :processes,
13
+ :jobs,
14
14
  :zombie,
15
15
  :expected_coverage
16
16
  )
@@ -64,7 +64,7 @@ module Mutant
64
64
  # @api private
65
65
  #
66
66
  def self.lines(source)
67
- source.lines.map { |line| line.chomp }
67
+ source.lines.map(&:chomp)
68
68
  end
69
69
  private_class_method :lines
70
70
 
@@ -115,7 +115,7 @@ module Mutant
115
115
  def expression(scope)
116
116
  name = scope_name(scope) or return
117
117
 
118
- unless name.kind_of?(String)
118
+ unless name.is_a?(String)
119
119
  warn("#{scope.class}#name from: #{scope.inspect} did not return a String or nil. Fix your lib to support normal ruby semantics!")
120
120
  return
121
121
  end
@@ -25,6 +25,22 @@ module Mutant
25
25
  MATCHERS.fetch(scope_symbol).new(env, scope)
26
26
  end
27
27
 
28
+ # Return length of match
29
+ #
30
+ # @param [Expression] expression
31
+ #
32
+ # @return [Fixnum]
33
+ #
34
+ # @api private
35
+ #
36
+ def match_length(expression)
37
+ if expression.syntax.start_with?(syntax)
38
+ syntax.length
39
+ else
40
+ 0
41
+ end
42
+ end
43
+
28
44
  private
29
45
 
30
46
  # Return scope name
@@ -38,8 +38,22 @@ module Mutant
38
38
  # @api private
39
39
  #
40
40
  def self.call(&block)
41
- Parallel.map([block], in_processes: 1, &block.method(:call)).first
42
- rescue Parallel::DeadWorker => exception
41
+ reader, writer = IO.pipe
42
+
43
+ pid = fork do
44
+ File.open('/dev/null', 'w') do |file|
45
+ $stderr.reopen(file)
46
+ reader.close
47
+ writer.write(Marshal.dump(block.call))
48
+ writer.close
49
+ end
50
+ end
51
+
52
+ writer.close
53
+ result = Marshal.load(reader.read)
54
+ Process.waitpid(pid)
55
+ result
56
+ rescue => exception
43
57
  fail Error, exception
44
58
  end
45
59
 
@@ -19,7 +19,7 @@ module Mutant
19
19
  emit_singletons unless parent_node && n_const?(parent_node)
20
20
  emit_type(nil, *children.drop(1))
21
21
  children.each_with_index do |child, index|
22
- mutate_child(index) if child.kind_of?(Parser::AST::Node)
22
+ mutate_child(index) if child.is_a?(Parser::AST::Node)
23
23
  end
24
24
  end
25
25
 
@@ -26,7 +26,7 @@ module Mutant
26
26
  #
27
27
  def dispatch
28
28
  children.each_with_index do |child, index|
29
- mutate_child(index) if child.kind_of?(Parser::AST::Node)
29
+ mutate_child(index) if child.is_a?(Parser::AST::Node)
30
30
  end
31
31
  end
32
32
 
@@ -64,7 +64,7 @@ module Mutant
64
64
  # @api private
65
65
  #
66
66
  def self.assert_valid_type(type)
67
- unless AST::Types::ALL.include?(type) || type.kind_of?(Class)
67
+ unless AST::Types::ALL.include?(type) || type.is_a?(Class)
68
68
  raise InvalidTypeError, "invalid type registration: #{type}"
69
69
  end
70
70
  end
@@ -71,7 +71,7 @@ module Mutant
71
71
  # @api private
72
72
  #
73
73
  def report(env)
74
- write(format.report(env))
74
+ Printer::EnvResult.run(output, env)
75
75
  self
76
76
  end
77
77
 
@@ -25,18 +25,6 @@ module Mutant
25
25
  #
26
26
  abstract_method :progress
27
27
 
28
- # Format result
29
- #
30
- # @param [Result::Env] env
31
- #
32
- # @return [String]
33
- #
34
- # @api private
35
- #
36
- def report(env)
37
- format(Printer::EnvResult, env)
38
- end
39
-
40
28
  # Output abstraction to decouple tty? from buffer
41
29
  class Output
42
30
  include Concord.new(:tty, :buffer)
@@ -177,7 +177,7 @@ module Mutant
177
177
  info 'Matcher: %s', object.matcher_config.inspect
178
178
  info 'Integration: %s', object.integration.name
179
179
  info 'Expect Coverage: %0.2f%%', object.expected_coverage.inspect
180
- info 'Processes: %d', object.processes
180
+ info 'Jobs: %d', object.jobs
181
181
  info 'Includes: %s', object.includes.inspect
182
182
  info 'Requires: %s', object.requires.inspect
183
183
  self
@@ -105,7 +105,7 @@ module Mutant
105
105
 
106
106
  # Env result object
107
107
  class Env
108
- include Coverage, Result, Anima.new(:runtime, :env, :subject_results, :done)
108
+ include Coverage, Result, Anima.new(:runtime, :env, :subject_results)
109
109
 
110
110
  COVERAGE_PRECISION = 1
111
111
 
@@ -12,19 +12,21 @@ module Mutant
12
12
  def initialize(env)
13
13
  super
14
14
 
15
- @collector = Collector.new(env)
16
- @mutex = Mutex.new
17
- @mutations = env.mutations.dup
15
+ @collector = Collector.new(env)
16
+ @mutex = Mutex.new
17
+ @mutations = env.mutations.dup
18
+ @index = 0
19
+ @continue = true
18
20
 
19
21
  config.integration.setup
20
22
 
21
- config.reporter.start(env)
23
+ reporter.start(env)
22
24
 
23
25
  run
24
26
 
25
- @result = @collector.result.update(done: true)
27
+ @result = @collector.result
26
28
 
27
- config.reporter.report(result)
29
+ reporter.report(result)
28
30
  end
29
31
 
30
32
  # Return result
@@ -45,14 +47,36 @@ module Mutant
45
47
  #
46
48
  def run
47
49
  Parallel.map(
48
- @mutations,
49
- in_processes: config.processes,
50
- finish: method(:finish),
51
- start: method(:start),
50
+ method(:next),
51
+ in_threads: config.jobs,
52
+ finish: method(:finish),
53
+ start: method(:start),
52
54
  &method(:run_mutation)
53
55
  )
54
56
  end
55
57
 
58
+ # Return next mutation or stop
59
+ #
60
+ # @return [Mutation]
61
+ # in case there is a next mutation
62
+ #
63
+ # @return [Parallel::Stop]
64
+ # in case there is no next mutation or runner should stop early
65
+ #
66
+ #
67
+ # @api private
68
+ def next
69
+ @mutex.synchronize do
70
+ mutation = @mutations.at(@index)
71
+ if @continue && mutation
72
+ @index += 1
73
+ mutation
74
+ else
75
+ Parallel::Stop
76
+ end
77
+ end
78
+ end
79
+
56
80
  # Handle started mutation
57
81
  #
58
82
  # @param [Mutation] mutation
@@ -79,7 +103,7 @@ module Mutant
79
103
  # @api private
80
104
  #
81
105
  def finish(mutation, index, result)
82
- return unless result.kind_of?(Mutant::Result::Mutation)
106
+ return unless result.is_a?(Mutant::Result::Mutation)
83
107
 
84
108
  test_results = result.test_results.zip(mutation.subject.tests).map do |test_result, test|
85
109
  test_result.update(test: test, mutation: mutation) if test_result
@@ -100,22 +124,9 @@ module Mutant
100
124
  #
101
125
  def process_result(result)
102
126
  @collector.finish(result)
103
- config.reporter.progress(@collector)
104
- handle_exit(result)
105
- end
106
-
107
- # Handle exit if needed
108
- #
109
- # @param [Result::Mutation] result
110
- #
111
- # @return [undefined]
112
- #
113
- # @api private
114
- #
115
- def handle_exit(result)
116
- return if !config.fail_fast || result.success?
117
-
118
- @mutations.clear
127
+ reporter.progress(@collector)
128
+ return unless config.fail_fast && !result.success?
129
+ @continue = false
119
130
  end
120
131
 
121
132
  # Run mutation
@@ -175,13 +186,23 @@ module Mutant
175
186
  end
176
187
  rescue Isolation::Error => exception
177
188
  Result::Test.new(
178
- test: nil,
179
- mutation: nil,
189
+ test: test,
190
+ mutation: mutation,
180
191
  runtime: Time.now - time,
181
192
  output: exception.message,
182
193
  passed: false
183
194
  )
184
195
  end
185
196
 
197
+ # Return reporter
198
+ #
199
+ # @return [Reporter]
200
+ #
201
+ # @api private
202
+ #
203
+ def reporter
204
+ config.reporter
205
+ end
206
+
186
207
  end # Runner
187
208
  end # Mutant
@@ -14,7 +14,7 @@ module Mutant
14
14
  super
15
15
  @start = Time.now
16
16
  @aggregate = Hash.new { |hash, key| hash[key] = [] }
17
- @activity = Hash.new(0)
17
+ @active = Set.new
18
18
  @last_mutation_result = nil
19
19
  end
20
20
 
@@ -50,12 +50,11 @@ module Mutant
50
50
  Result::Env.new(
51
51
  env: env,
52
52
  runtime: Time.now - @start,
53
- subject_results: subject_results,
54
- done: false
53
+ subject_results: subject_results
55
54
  )
56
55
  end
57
56
 
58
- # Handle mutation start
57
+ # Register mutation start
59
58
  #
60
59
  # @param [Mutation] mutation
61
60
  #
@@ -64,7 +63,7 @@ module Mutant
64
63
  # @api private
65
64
  #
66
65
  def start(mutation)
67
- @activity[mutation.subject] += 1
66
+ @active << mutation
68
67
  self
69
68
  end
70
69
 
@@ -79,10 +78,10 @@ module Mutant
79
78
  def finish(mutation_result)
80
79
  @last_mutation_result = mutation_result
81
80
 
82
- subject = mutation_result.mutation.subject
81
+ mutation = mutation_result.mutation
82
+ @active.delete(mutation)
83
83
 
84
- @activity[subject] -= 1
85
- @aggregate[subject] << mutation_result
84
+ @aggregate[mutation.subject] << mutation_result
86
85
 
87
86
  self
88
87
  end
@@ -106,9 +105,9 @@ module Mutant
106
105
  # @api private
107
106
  #
108
107
  def active_subjects
109
- @activity.select do |_subject, count|
110
- count > 0
111
- end.map(&:first)
108
+ @active.each_with_object(Set.new) do |mutation, subjects|
109
+ subjects << mutation.subject
110
+ end.sort_by(&:identification)
112
111
  end
113
112
 
114
113
  # Return current subject result