mutant 0.8.9 → 0.8.10

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +4 -0
  3. data/config/devtools.yml +1 -1
  4. data/config/flay.yml +1 -1
  5. data/lib/mutant.rb +1 -2
  6. data/lib/mutant/ast/types.rb +3 -2
  7. data/lib/mutant/context.rb +78 -6
  8. data/lib/mutant/diff.rb +7 -13
  9. data/lib/mutant/matcher/method.rb +2 -2
  10. data/lib/mutant/mutation.rb +1 -1
  11. data/lib/mutant/mutator/node/generic.rb +1 -1
  12. data/lib/mutant/reporter/cli/printer.rb +25 -1
  13. data/lib/mutant/reporter/sequence.rb +22 -0
  14. data/lib/mutant/result.rb +4 -9
  15. data/lib/mutant/version.rb +1 -1
  16. data/meta/dsym.rb +7 -7
  17. data/mutant.gemspec +5 -5
  18. data/spec/spec_helper.rb +0 -1
  19. data/spec/unit/mutant/ast/named_children_spec.rb +12 -2
  20. data/spec/unit/mutant/context_spec.rb +85 -1
  21. data/spec/unit/mutant/diff_spec.rb +9 -0
  22. data/spec/unit/mutant/mutation_spec.rb +55 -4
  23. data/spec/unit/mutant/reporter/cli/printer/mutation_result_spec.rb +3 -3
  24. data/spec/unit/mutant/reporter/cli/printer_spec.rb +27 -6
  25. data/spec/unit/mutant/reporter/null_spec.rb +5 -15
  26. data/spec/unit/mutant/reporter/sequence_spec.rb +29 -0
  27. data/spec/unit/mutant/result/class_methods_spec.rb +49 -0
  28. data/spec/unit/mutant/result/mutation_spec.rb +49 -0
  29. data/spec/unit/mutant/result/subject_spec.rb +84 -19
  30. data/spec/unit/mutant/result_spec.rb +18 -10
  31. data/spec/unit/mutant/subject/method/instance_spec.rb +3 -3
  32. data/spec/unit/mutant/subject/method/singleton_spec.rb +1 -1
  33. metadata +19 -22
  34. data/lib/mutant/context/scope.rb +0 -94
  35. data/lib/mutant/delegator.rb +0 -43
  36. data/spec/unit/mutant/context/root_spec.rb +0 -11
  37. data/spec/unit/mutant/context/scope/root_spec.rb +0 -32
  38. data/spec/unit/mutant/context/scope/unqualified_name_spec.rb +0 -25
  39. data/spec/unit/mutant/context/scope_spec.rb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6629a951cb8a81c688c0adc0acac1f3ff41f13b6
4
- data.tar.gz: b8e775063ca733fe989083a38c31ef680135cbc1
3
+ metadata.gz: 37c56c026986c92dd3afb4306adaea0d80b130f2
4
+ data.tar.gz: 0513911185fbe4a6967146e75ebec39e77537875
5
5
  SHA512:
6
- metadata.gz: f11084993d2875f56e6fd86174eaae8a1b8681feee2f81d4442914fc63ab9a7e8a9be395145b55b424519f5c7975f114bf5adf0c7e01ce1dca5b2aa091fd0364
7
- data.tar.gz: 44e651cc4312db4388d975f1955791966055c67fc4a648a297e34535d9f47cea0588e94921098416d2a0636d0d4d26e017a75377960beeb28dcfa8ad555032c0
6
+ metadata.gz: 838d0e823d5666c5b615aa4c3f94af69c90034873cf07bc113524dfad2b265cdda16b350d189e410ff0229657fc425ddb0d34d442f19091a11151f2757f10b06
7
+ data.tar.gz: 60493346edfd51f98857e94c286de02c6293933ad379331f4513f18ec82b35f5c93ae5b6f14f8a196ac69d071004ba8529bd0ef902acf8ea8a32eb471b41f4b3
@@ -1,3 +1,7 @@
1
+ # v0.8.10 2016-01-24
2
+
3
+ * Add support for parser 2.3 (via unparser 0.2.5)
4
+
1
5
  # v0.8.9 2016-01-05
2
6
 
3
7
  * Add mutation from `Hash#[]` to `Hash#key?` (#511)
@@ -1,2 +1,2 @@
1
1
  ---
2
- unit_test_timeout: 10
2
+ unit_test_timeout: 10.0
@@ -1,3 +1,3 @@
1
1
  ---
2
2
  threshold: 18
3
- total_score: 1173
3
+ total_score: 1171
@@ -61,7 +61,6 @@ require 'mutant/actor/sender'
61
61
  require 'mutant/actor/mailbox'
62
62
  require 'mutant/actor/env'
63
63
  require 'mutant/parser'
64
- require 'mutant/delegator'
65
64
  require 'mutant/isolation'
66
65
  require 'mutant/parallel'
67
66
  require 'mutant/parallel/master'
@@ -131,7 +130,6 @@ require 'mutant/mutator/node/rescue'
131
130
  require 'mutant/mutator/node/match_current_line'
132
131
  require 'mutant/loader'
133
132
  require 'mutant/context'
134
- require 'mutant/context/scope'
135
133
  require 'mutant/scope'
136
134
  require 'mutant/subject'
137
135
  require 'mutant/subject/method'
@@ -168,6 +166,7 @@ require 'mutant/runner/sink'
168
166
  require 'mutant/result'
169
167
  require 'mutant/reporter'
170
168
  require 'mutant/reporter/null'
169
+ require 'mutant/reporter/sequence'
171
170
  require 'mutant/reporter/cli'
172
171
  require 'mutant/reporter/cli/printer'
173
172
  require 'mutant/reporter/cli/printer/config'
@@ -41,8 +41,9 @@ module Mutant
41
41
  # Nodes that are NOT generated by parser but used by mutant / unparser.
42
42
  EXTRA = symbolset.(%w[empty])
43
43
 
44
- # Nodes that are currently missing from parser META
45
- MISSING = symbolset.(%w[rational])
44
+ # Nodes that are currently missing from Parser::Meta::NODE_TYPES
45
+ # * https://github.com/whitequark/parser/pull/251
46
+ MISSING = symbolset.(%w[csend])
46
47
 
47
48
  # All node types mutant handles
48
49
  ALL = symbolset.((Parser::Meta::NODE_TYPES + EXTRA + MISSING) - BLACKLIST)
@@ -1,19 +1,91 @@
1
1
  module Mutant
2
2
  # An abstract context where mutations can be applied to.
3
3
  class Context
4
- include Adamantium::Flat, AbstractType, Concord::Public.new(:source_path)
4
+ include Adamantium::Flat, Concord::Public.new(:scope, :source_path)
5
+ extend AST::Sexp
5
6
 
6
- # Root ast node
7
- #
8
- # @param [Parser::AST::Node] node
7
+ NAMESPACE_DELIMITER = '::'.freeze
8
+
9
+ # Return root node for mutation
9
10
  #
10
11
  # @return [Parser::AST::Node]
11
- abstract_method :root
12
+ def root(node)
13
+ nesting.reverse.reduce(node) do |current, scope|
14
+ self.class.wrap(scope, current)
15
+ end
16
+ end
12
17
 
13
18
  # Identification string
14
19
  #
15
20
  # @return [String]
16
- abstract_method :identification
21
+ def identification
22
+ scope.name
23
+ end
24
+
25
+ # Wrap node into ast node
26
+ #
27
+ # @param [Class, Module] scope
28
+ # @param [Parser::AST::Node] node
29
+ #
30
+ # @return [Parser::AST::Class]
31
+ # if scope is of kind Class
32
+ #
33
+ # @return [Parser::AST::Module]
34
+ # if scope is of kind module
35
+ def self.wrap(scope, node)
36
+ name = s(:const, nil, scope.name.split(NAMESPACE_DELIMITER).last.to_sym)
37
+ case scope
38
+ when Class
39
+ s(:class, name, nil, node)
40
+ when Module
41
+ s(:module, name, node)
42
+ end
43
+ end
44
+
45
+ # Nesting of scope
46
+ #
47
+ # @return [Enumerable<Class,Module>]
48
+ def nesting
49
+ const = Object
50
+ name_nesting.map do |name|
51
+ const = const.const_get(name)
52
+ end
53
+ end
54
+ memoize :nesting
55
+
56
+ # Unqualified name of scope
57
+ #
58
+ # @return [String]
59
+ def unqualified_name
60
+ name_nesting.last
61
+ end
62
+
63
+ # Match expressions for scope
64
+ #
65
+ # @return [Enumerable<Expression>]
66
+ def match_expressions
67
+ name_nesting.each_index.reverse_each.map do |index|
68
+ Expression::Namespace::Recursive.new(
69
+ scope_name: name_nesting.take(index.succ).join(NAMESPACE_DELIMITER)
70
+ )
71
+ end
72
+ end
73
+ memoize :match_expressions
74
+
75
+ # Scope wrapped by context
76
+ #
77
+ # @return [Module|Class]
78
+ attr_reader :scope
79
+
80
+ private
81
+
82
+ # Nesting of names in scope
83
+ #
84
+ # @return [Array<String>]
85
+ def name_nesting
86
+ scope.name.split(NAMESPACE_DELIMITER)
87
+ end
88
+ memoize :name_nesting
17
89
 
18
90
  end # Context
19
91
  end # Mutant
@@ -17,9 +17,7 @@ module Mutant
17
17
  def diff
18
18
  return if diffs.empty?
19
19
 
20
- minimized_hunks.map do |hunk|
21
- hunk.diff(:unified) << NEWLINE
22
- end.join
20
+ minimized_hunk.diff(:unified) << NEWLINE
23
21
  end
24
22
  memoize :diff
25
23
 
@@ -74,19 +72,15 @@ module Mutant
74
72
  end
75
73
  end
76
74
 
77
- # Minimized hunks
75
+ # Minimized hunk
78
76
  #
79
- # @return [Array<Diff::LCS::Hunk>]
80
- def minimized_hunks
77
+ # @return Diff::LCS::Hunk
78
+ def minimized_hunk
81
79
  head, *tail = hunks
82
80
 
83
- tail.each_with_object([head]) do |right, aggregate|
84
- left = aggregate.last
85
- if right.overlaps?(left)
86
- right.merge(left)
87
- aggregate.pop
88
- end
89
- aggregate << right
81
+ tail.reduce(head) do |left, right|
82
+ right.merge(left)
83
+ right
90
84
  end
91
85
  end
92
86
 
@@ -69,9 +69,9 @@ module Mutant
69
69
 
70
70
  # Target context
71
71
  #
72
- # @return [Context::Scope]
72
+ # @return [Context]
73
73
  def context
74
- Context::Scope.new(scope, source_path)
74
+ Context.new(scope, source_path)
75
75
  end
76
76
 
77
77
  # Root source node
@@ -40,7 +40,7 @@ module Mutant
40
40
 
41
41
  # Test if mutation is killed by test reports
42
42
  #
43
- # @param [Array<Report::Test>] test_reports
43
+ # @param [Result::Test] test_result
44
44
  #
45
45
  # @return [Boolean]
46
46
  def self.success?(test_result)
@@ -8,7 +8,7 @@ module Mutant
8
8
  # These nodes still need a dedicated mutator,
9
9
  # your contribution is that close!
10
10
  handle(
11
- :ensure, :redo, :regopt, :retry, :arg_expr,
11
+ :csend, :ensure, :redo, :regopt, :retry, :arg_expr,
12
12
  :kwrestarg, :kwoptarg, :kwarg, :undef, :module, :empty,
13
13
  :alias, :for, :xstr, :back_ref, :class,
14
14
  :sclass, :match_with_lvasgn, :while_post,
@@ -3,10 +3,34 @@ module Mutant
3
3
  class CLI
4
4
  # CLI runner status printer base class
5
5
  class Printer
6
- include AbstractType, Delegator, Adamantium::Flat, Concord.new(:output, :object), Procto.call(:run)
6
+ include AbstractType,
7
+ Adamantium::Flat,
8
+ Concord.new(:output, :object),
9
+ Procto.call(:run)
7
10
 
8
11
  private_class_method :new
9
12
 
13
+ # Create delegators to object
14
+ #
15
+ # @return [undefined]
16
+ def self.delegate(*names)
17
+ names.each(&method(:define_delegator))
18
+ end
19
+ private_class_method :delegate
20
+
21
+ # Create delegator to object
22
+ #
23
+ # @param [Symbol] name
24
+ #
25
+ # @return [undefined]
26
+ def self.define_delegator(name)
27
+ define_method(name) do
28
+ object.public_send(name)
29
+ end
30
+ private(name)
31
+ end
32
+ private_class_method :define_delegator
33
+
10
34
  delegate :success?
11
35
 
12
36
  NL = "\n".freeze
@@ -0,0 +1,22 @@
1
+ module Mutant
2
+ class Reporter
3
+ class Sequence < self
4
+ include Concord.new(:reporters)
5
+
6
+ %i[warn progress report start].each do |name|
7
+ define_method(name) do |value|
8
+ reporters.each do |reporter|
9
+ reporter.public_send(name, value)
10
+ end
11
+
12
+ self
13
+ end
14
+ end
15
+
16
+ def delay
17
+ reporters.map(&:delay).min
18
+ end
19
+
20
+ end # Sequence
21
+ end # Reporter
22
+ end # Mutant
@@ -136,14 +136,7 @@ module Mutant
136
136
  alive_mutation_results.empty?
137
137
  end
138
138
 
139
- # Test if runner should continue on subject
140
- #
141
- # @return [Boolean]
142
- def continue?
143
- mutation_results.all?(&:success?)
144
- end
145
-
146
- # Killed mutations
139
+ # Alive mutations
147
140
  #
148
141
  # @return [Array<Result::Mutation>]
149
142
  def alive_mutation_results
@@ -179,7 +172,9 @@ module Mutant
179
172
  alive_mutation_results.length
180
173
  end
181
174
 
182
- # Alive mutations
175
+ private
176
+
177
+ # Killed mutation results
183
178
  #
184
179
  # @return [Array<Result::Mutation>]
185
180
  def killed_mutation_results
@@ -1,4 +1,4 @@
1
1
  module Mutant
2
2
  # Current mutant version
3
- VERSION = '0.8.9'.freeze
3
+ VERSION = '0.8.10'.freeze
4
4
  end # Mutant
@@ -2,11 +2,11 @@ Mutant::Meta::Example.add do
2
2
  source ':"foo#{bar}baz"'
3
3
 
4
4
  singleton_mutations
5
- # TODO: Unparser imperfection two asts with same source.
6
- mutation s(:dsym, s(:nil), s(:begin, s(:send, nil, :bar)), s(:str, 'baz'))
7
- mutation s(:dsym, s(:self), s(:begin, s(:send, nil, :bar)), s(:str, 'baz'))
8
- mutation s(:dsym, s(:str, 'foo'), s(:begin, s(:self)), s(:str, 'baz'))
9
- mutation s(:dsym, s(:str, 'foo'), s(:begin, s(:nil)), s(:str, 'baz'))
10
- mutation s(:dsym, s(:str, 'foo'), s(:begin, s(:send, nil, :bar)), s(:nil))
11
- mutation s(:dsym, s(:str, 'foo'), s(:begin, s(:send, nil, :bar)), s(:self))
5
+
6
+ mutation ':"#{nil}#{bar}#{"baz"}"'
7
+ mutation ':"#{self}#{bar}#{"baz"}"'
8
+ mutation ':"#{"foo"}#{nil}#{"baz"}"'
9
+ mutation ':"#{"foo"}#{self}#{"baz"}"'
10
+ mutation ':"#{"foo"}#{bar}#{nil}"'
11
+ mutation ':"#{"foo"}#{bar}#{self}"'
12
12
  end
@@ -21,14 +21,14 @@ Gem::Specification.new do |gem|
21
21
 
22
22
  gem.required_ruby_version = '>= 2.1'
23
23
 
24
- gem.add_runtime_dependency('parser', '~> 2.2.2')
25
- gem.add_runtime_dependency('ast', '~> 2.1')
24
+ gem.add_runtime_dependency('parser', '~> 2.3.0')
25
+ gem.add_runtime_dependency('ast', '~> 2.2')
26
26
  gem.add_runtime_dependency('diff-lcs', '~> 1.2')
27
27
  gem.add_runtime_dependency('parallel', '~> 1.3')
28
- gem.add_runtime_dependency('morpher', '~> 0.2.5')
28
+ gem.add_runtime_dependency('morpher', '~> 0.2.6')
29
29
  gem.add_runtime_dependency('procto', '~> 0.0.2')
30
30
  gem.add_runtime_dependency('abstract_type', '~> 0.0.7')
31
- gem.add_runtime_dependency('unparser', '~> 0.2.4')
31
+ gem.add_runtime_dependency('unparser', '~> 0.2.5')
32
32
  gem.add_runtime_dependency('ice_nine', '~> 0.11.1')
33
33
  gem.add_runtime_dependency('adamantium', '~> 0.2.0')
34
34
  gem.add_runtime_dependency('memoizable', '~> 0.4.2')
@@ -36,7 +36,7 @@ Gem::Specification.new do |gem|
36
36
  gem.add_runtime_dependency('anima', '~> 0.3.0')
37
37
  gem.add_runtime_dependency('concord', '~> 0.1.5')
38
38
 
39
- gem.add_development_dependency('devtools', '~> 0.1.1')
39
+ gem.add_development_dependency('devtools', '~> 0.1.3')
40
40
  gem.add_development_dependency('bundler', '~> 1.10')
41
41
  gem.add_development_dependency('ffi', '~> 1.9.6')
42
42
  end
@@ -26,7 +26,6 @@ require 'devtools/spec_helper'
26
26
  require 'unparser/cli'
27
27
  require 'mutant'
28
28
  require 'mutant/meta'
29
- Devtools.init_spec_helper
30
29
 
31
30
  $LOAD_PATH << File.join(TestApp.root, 'lib')
32
31
 
@@ -42,8 +42,18 @@ RSpec.describe Mutant::AST::NamedChildren do
42
42
  end
43
43
 
44
44
  describe '#bar' do
45
- it 'returns named child bar' do
46
- expect(instance.bar).to be(node_bar)
45
+ context 'when node is present' do
46
+ it 'returns named child bar' do
47
+ expect(instance.bar).to be(node_bar)
48
+ end
49
+ end
50
+
51
+ context 'when node is NOT present' do
52
+ let(:node) { s(:node, node_foo) }
53
+
54
+ it 'returns nil' do
55
+ expect(instance.bar).to be(nil)
56
+ end
47
57
  end
48
58
  end
49
59
  end
@@ -1,5 +1,5 @@
1
1
  # rubocop:disable ClosingParenthesisIndentation
2
- RSpec.describe Mutant::Context::Scope do
2
+ RSpec.describe Mutant::Context do
3
3
  describe '.wrap' do
4
4
  subject { described_class.wrap(scope, node) }
5
5
 
@@ -32,4 +32,88 @@ RSpec.describe Mutant::Context::Scope do
32
32
  it { should eql(expected) }
33
33
  end
34
34
  end
35
+
36
+ let(:object) { described_class.new(scope, source_path) }
37
+ let(:scope_name) { instance_double(String) }
38
+ let(:source_path) { instance_double(Pathname) }
39
+ let(:scope) { TestApp::Literal }
40
+
41
+ describe '#identification' do
42
+ subject { object.identification }
43
+
44
+ it { should eql(scope.name) }
45
+ end
46
+
47
+ describe '#root' do
48
+ subject { object.root(node) }
49
+
50
+ let(:node) { s(:sym, :node) }
51
+
52
+ let(:expected_source) do
53
+ generate(parse(<<-RUBY))
54
+ module TestApp
55
+ class Literal
56
+ :node
57
+ end
58
+ end
59
+ RUBY
60
+ end
61
+
62
+ let(:generated_source) do
63
+ Unparser.unparse(subject)
64
+ end
65
+
66
+ let(:round_tripped_source) do
67
+ Unparser.unparse(parse(expected_source))
68
+ end
69
+
70
+ it 'should create correct source' do
71
+ expect(generated_source).to eql(expected_source)
72
+ end
73
+ end
74
+
75
+ describe '#unqualified_name' do
76
+ subject { object.unqualified_name }
77
+
78
+ let(:path) { instance_double(Pathname) }
79
+
80
+ context 'with top level constant name' do
81
+ let(:scope) { TestApp }
82
+
83
+ it 'should return the unqualified name' do
84
+ should eql('TestApp')
85
+ end
86
+
87
+ it_should_behave_like 'an idempotent method'
88
+ end
89
+
90
+ context 'with scoped constant name' do
91
+ it 'should return the unqualified name' do
92
+ should eql('Literal')
93
+ end
94
+
95
+ it_should_behave_like 'an idempotent method'
96
+ end
97
+ end
98
+
99
+ describe '#match_expressions' do
100
+ subject { object.match_expressions }
101
+
102
+ context 'on toplevel scope' do
103
+ let(:scope) { TestApp }
104
+
105
+ it { should eql([parse_expression('TestApp*')]) }
106
+ end
107
+
108
+ context 'on nested scope' do
109
+ specify do
110
+ should eql(
111
+ [
112
+ parse_expression('TestApp::Literal*'),
113
+ parse_expression('TestApp*')
114
+ ]
115
+ )
116
+ end
117
+ end
118
+ end
35
119
  end