mutant 0.6.3 → 0.6.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 (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