mutant 0.6.0 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b4c622c635f57a57ea3d7463d71da71c84450a8e
4
- data.tar.gz: 4147047e1dd38d1115f415e5cb8e2eebba932c6d
3
+ metadata.gz: 001bd8f243257cdce0da86ebe626d0e1efbef248
4
+ data.tar.gz: 9b1bc583e1321da8437ec18716a6e9b5911018dc
5
5
  SHA512:
6
- metadata.gz: fd80ff4f0469e2bf182c5060fe4696ce48373c014dd76dc083fc1a210812908c201c344afa6f34ec066c5714e0ad1a6f94ca436eb5c31fb9cf92d76b072c8467
7
- data.tar.gz: 54779c5e90ebc0f5f909ae8b485eecb92da095e0abaf9f28d214d500ee2f0ea4d8e836b8cc17d9abe8a9b034b89de5651487ceeb25dd347c1e0a9864d5c444cb
6
+ metadata.gz: 006d6d7657e04e6e23fdaef408516abf6119ce24c1eaaacda8f2f8d3e20993168c450391adf8f9bda5e78f13d6ca9e765a2a83db04db1fcf4c233389e4585727
7
+ data.tar.gz: 343f8f579a4db3aa5b49c96b2e16a61cf963f92dc7781a7d1ef514a670885f745912450ec264659a46e96ae471772366f36ae6f92ee4389a428f63d6df92dcd3
data/Changelog.md CHANGED
@@ -1,4 +1,14 @@
1
- # v0.6.0 2014-07-17
1
+ # v0.6.2 2014-08-16
2
+
3
+ * Fix matcher to ignore metaprogrammed defines [#254](https://github.com/mbj/mutant/issues/254)
4
+ * Add rescue resbody body concat-promotion mutation
5
+ * Add rescue else body concat-promotion mutation #245
6
+
7
+ # v0.6.1 2014-08-16
8
+
9
+ * Incorrectly released on partial state. Yanked.
10
+
11
+ # v0.6.0 2014-08-11
2
12
 
3
13
  * Parallel execution / reporting.
4
14
  * Add -j, --jobs flag to control concurrency.
data/README.md CHANGED
@@ -7,19 +7,27 @@ mutant
7
7
  [![Inline docs](http://inch-ci.org/github/mbj/mutant.png)](http://inch-ci.org/github/mbj/mutant)
8
8
  [![Gem Version](https://img.shields.io/gem/v/mutant.svg)](https://rubygems.org/gems/mutant)
9
9
 
10
- Mutant is a mutation testing tool for ruby.
10
+ Mutant is a mutation testing tool for Ruby.
11
11
 
12
12
  The idea is that if code can be changed and your tests do not notice, either that code isn't being covered
13
13
  or it does not have a speced side effect.
14
14
 
15
- Mutant supports MRI and RBX 1.9, 2.0 and 2.1, while support for jruby is planned.
16
- It should also work under any ruby engine that supports POSIX-fork(2) semantics.
15
+ Mutant supports MRI and RBX 1.9, 2.0 and 2.1, while support for JRuby is planned.
16
+ It should also work under any Ruby engine that supports POSIX-fork(2) semantics.
17
17
 
18
- Mutant uses a pure ruby [parser](https://github.com/whitequark/parser) and an [unparser](https://github.com/mbj/unparser)
18
+ Mutant uses a pure Ruby [parser](https://github.com/whitequark/parser) and an [unparser](https://github.com/mbj/unparser)
19
19
  to do its magic.
20
20
 
21
21
  Mutant does not have really good "getting started" documentation currently so please refer to presentations and blog posts below.
22
22
 
23
+ Mutation-Operators:
24
+ -------------------
25
+
26
+ Mutant supports a wide range of mutation operators. An exhaustive list can be found in the [mutant-meta](https://github.com/mbj/mutant/tree/master/meta).
27
+ The `mutant-meta` is arranged to the AST-Node-Types of parser. Refer to parsers [AST documentation](https://github.com/whitequark/parser/blob/master/doc/AST_FORMAT.md) in doubt.
28
+
29
+ There is no easy and universal way to count the number of mutation operators a tool supports.
30
+
23
31
  Presentations
24
32
  -------------
25
33
 
@@ -39,7 +47,7 @@ Blog-Posts
39
47
  Projects using Mutant
40
48
  ---------------------
41
49
 
42
- The following projects adopted mutant, and aim 100% mutation coverage:
50
+ The following projects adopted mutant, and aim for 100% mutation coverage:
43
51
 
44
52
  * [axiom](https://github.com/dkubb/axiom)
45
53
  * [axiom-types](https://github.com/dkubb/axiom-types)
@@ -65,8 +73,8 @@ Install the gem `mutant` via your preferred method.
65
73
  gem install mutant
66
74
  ```
67
75
 
68
- If you plan to use the rspec integration you'll have to install `mutant-rspec` also.
69
- Please add an explicit dependency to `rspec-core` for the rspec version you want to use.
76
+ If you plan to use the RSpec integration you'll have to install `mutant-rspec` also.
77
+ Please add an explicit dependency to `rspec-core` for the RSpec version you want to use.
70
78
 
71
79
  ```ruby
72
80
  gem install mutant-rspec
@@ -74,38 +82,6 @@ gem install mutant-rspec
74
82
 
75
83
  The minitest integration is still in the works.
76
84
 
77
- Mutations
78
- ---------
79
-
80
- Mutant supports a very wide range of mutation operators. Listing them all in detail would blow this document up.
81
-
82
- It is planned to parse a list of mutation operators from the source. In the meantime please refer to the
83
- [code](https://github.com/mbj/mutant/tree/master/lib/mutant/mutator/node) each subclass of `Mutant::Mutator::Node`
84
- emits around 3-6 mutations.
85
-
86
- Currently mutant covers the majority of ruby's complex nodes that often occur in method bodies.
87
-
88
- NOTE: The textbook examples you find on mutation testing are intentionally not implemented. This is subjected to change.
89
-
90
- Some stats from the [axiom](https://github.com/dkubb/axiom) library:
91
-
92
- ```
93
- Subjects: 424 # Amount of subjects being mutated (currently only methods)
94
- Mutations: 6760 # Amount of mutations mutant generated (~13 mutations per method)
95
- Kills: 6664 # Amount of successfully killed mutations
96
- Runtime: 5123.13s # Total runtime
97
- Killtime: 5092.63s # Time spend killing mutations
98
- Overhead: 0.60%
99
- Coverage: 98.58% # Coverage score
100
- Alive: 96 # Amount of alive mutations.
101
- ```
102
-
103
-
104
- Nodes still missing a dedicated mutator are handled via the
105
- [Generic](https://github.com/mbj/mutant/blob/master/lib/mutant/mutator/node/generic.rb) mutator.
106
- The goal is to remove this mutator and have dedicated mutator for every type of node and removing
107
- the Generic handler altogether.
108
-
109
85
  Examples
110
86
  --------
111
87
 
@@ -139,6 +115,24 @@ Example for a subject like `Foo::Bar#baz` it will run all example groups with de
139
115
  `Foo::Bar#baz`, `Foo::Bar` and `Foo`. The order is important, so if mutant finds example groups in the
140
116
  current prefix level, these example groups *must* kill the mutation.
141
117
 
118
+ Rails
119
+ -------
120
+
121
+ Assuming you are using rspec, you can mutation test Rails models by adding the following lines to your Gemfile:
122
+
123
+ ```ruby
124
+ group :test do
125
+ gem 'mutant'
126
+ gem 'mutant-rspec'
127
+ end
128
+ ```
129
+
130
+ Next, run bundle and comment out ```require 'rspec/autorun'``` from your spec_helper.rb file. Having done so you should be able to use commands like the following:
131
+
132
+ ```sh
133
+ RAILS_ENV=test bundle exec mutant -r ./config/environment --use rspec User
134
+ ```
135
+
142
136
  Support
143
137
  -------
144
138
 
@@ -150,7 +144,7 @@ Your options:
150
144
  * Ping me on [twitter](https://twitter.com/_m_b_j_)
151
145
 
152
146
  There is also the [#mutant] channel on freenode. As my OSS time budged is very limited I cannot
153
- join it often. Please prefer to use github issues with a 'Question: ' prefix in title.
147
+ join it often. Please prefer to use GitHub issues with a 'Question: ' prefix in title.
154
148
 
155
149
  Credits
156
150
  -------
data/config/flay.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  ---
2
2
  threshold: 18
3
- total_score: 1069
3
+ total_score: 1098
data/config/reek.yml CHANGED
@@ -37,6 +37,7 @@ FeatureEnvy:
37
37
  - Mutant::Matcher::Method::Singleton#receiver?
38
38
  - Mutant::Mutation::Evil#success?
39
39
  - Mutant::Mutation::Neutral#success?
40
+ - Mutant::Mutator::Node#children_indices
40
41
  # False positive, its a utility
41
42
  - Mutant::Meta::Example::Verification#format_mutation
42
43
  - Mutant::Reporter::CLI#subject_results
@@ -67,7 +68,7 @@ NestedIterators:
67
68
  - Mutant::Mutator::Node::Resbody#mutate_captures
68
69
  - Mutant::Mutator::Node::Arguments#emit_argument_mutations
69
70
  - Mutant::RequireHighjack#infect
70
- - Mutant::RequireHighjack#desinfect
71
+ - Mutant::RequireHighjack#disinfect
71
72
  - Mutant::Subject#tests
72
73
  - Parser::Lexer#self.new
73
74
  max_allowed_nesting: 1
data/lib/mutant/ast.rb CHANGED
@@ -4,7 +4,8 @@ module Mutant
4
4
 
5
5
  # Walk all ast nodes
6
6
  #
7
- # @param [Parser::AST::Node]
7
+ # @param [Parser::AST::Node] root
8
+ # @param [Array<Parser::AST::Node>] stack
8
9
  #
9
10
  # @yield [Parser::AST::Node]
10
11
  # all nodes recursively including root
@@ -13,21 +14,24 @@ module Mutant
13
14
  #
14
15
  # @api private
15
16
  #
16
- def self.walk(node, &block)
17
+ def self.walk(node, stack, &block)
17
18
  raise ArgumentError, 'block expected' unless block_given?
18
19
 
19
- block.call(node)
20
+ block.call(node, stack)
20
21
  node.children.grep(Parser::AST::Node).each do |child|
21
- walk(child, &block)
22
+ stack.push(child)
23
+ walk(child, stack, &block)
24
+ stack.pop
22
25
  end
23
26
 
24
27
  self
25
28
  end
29
+ private_class_method :walk
26
30
 
27
- # Find last node satisfing predicate (as block)
31
+ # Find last node satisfying predicate (as block)
28
32
  #
29
33
  # @return [Parser::AST::Node]
30
- # if satisfing node is found
34
+ # if satisfying node is found
31
35
  #
32
36
  # @yield [Parser::AST::Node]
33
37
  #
@@ -39,13 +43,15 @@ module Mutant
39
43
  #
40
44
  # @api private
41
45
  #
42
- def self.find_last(node, &predicate)
46
+ def self.find_last_path(node, &predicate)
43
47
  raise ArgumentError, 'block expected' unless block_given?
44
- neddle = nil
45
- walk(node) do |candidate|
46
- neddle = candidate if predicate.call(candidate, &predicate)
48
+ path = []
49
+ walk(node, [node]) do |candidate, stack|
50
+ if predicate.call(candidate, &predicate)
51
+ path = stack.dup
52
+ end
47
53
  end
48
- neddle
54
+ path
49
55
  end
50
56
 
51
57
  end # AST
@@ -17,14 +17,42 @@ module Mutant
17
17
  REGISTRY.fetch(node.type, Generic).new(node)
18
18
  end
19
19
 
20
- # Generic metadata for send nodes
20
+ # Mixin to define meta nodes
21
+ class Node < Module
22
+ include Concord.new(:type)
23
+
24
+ CONCORD = Concord.new(:node)
25
+
26
+ # Hook called when module gets included
27
+ #
28
+ # @param [Class, Module] host
29
+ #
30
+ # @return [undefined]
31
+ #
32
+ # @api private
33
+ #
34
+ def included(host)
35
+ REGISTRY[type] = host
36
+ host.class_eval do
37
+ include CONCORD, NamedChildren
38
+ end
39
+ end
40
+
41
+ end # Node
42
+
43
+ # Metadata for resbody nods
44
+ class Resbody
45
+ include Node.new(:resbody)
46
+
47
+ children :captures, :assignment, :body
48
+ end # Resbody
49
+
50
+ # Metadata for send nodes
21
51
  class Send
22
- include Concord.new(:node), NamedChildren
52
+ include Node.new(:send)
23
53
 
24
54
  children :receiver, :selector
25
55
 
26
- REGISTRY[:send] = self
27
-
28
56
  INDEX_ASSIGNMENT_SELECTOR = :[]=
29
57
  ATTRIBUTE_ASSIGNMENT_SELECTOR_SUFFIX = '='.freeze
30
58
 
@@ -100,7 +128,7 @@ module Mutant
100
128
 
101
129
  end # Send
102
130
 
103
- # Generic node metatada
131
+ # Generic node metadata
104
132
  class Generic
105
133
  include Adamantium, Concord.new(:node)
106
134
 
data/lib/mutant/diff.rb CHANGED
@@ -99,7 +99,7 @@ module Mutant
99
99
  # @api private
100
100
  #
101
101
  def minimized_hunks
102
- head, *tail = hunks()
102
+ head, *tail = hunks
103
103
 
104
104
  tail.each_with_object([head]) do |right, aggregate|
105
105
  left = aggregate.last
@@ -2,8 +2,8 @@ module Mutant
2
2
  class Matcher
3
3
  # Matcher for subjects that are a specific method
4
4
  class Method < self
5
- include Adamantium::Flat, Concord::Public.new(:env, :scope, :method)
6
- include Equalizer.new(:identification)
5
+ include Adamantium::Flat, Concord::Public.new(:env, :scope, :target_method)
6
+ include AST::NodePredicates, Equalizer.new(:identification)
7
7
 
8
8
  # Methods within rbx kernel directory are precompiled and their source
9
9
  # cannot be accessed via reading source location. Same for methods created by eval.
@@ -40,7 +40,10 @@ module Mutant
40
40
  def skip?
41
41
  location = source_location
42
42
  if location.nil? || BLACKLIST.match(location.first)
43
- env.warn(format('%s does not have valid source location unable to emit matcher', method.inspect))
43
+ env.warn(format('%s does not have valid source location unable to emit subject', target_method.inspect))
44
+ true
45
+ elsif matched_node_path.any?(&method(:n_block?))
46
+ env.warn(format('%s is defined from a 3rd party lib unable to emit subject', target_method.inspect))
44
47
  true
45
48
  else
46
49
  false
@@ -54,7 +57,7 @@ module Mutant
54
57
  # @api private
55
58
  #
56
59
  def method_name
57
- method.name
60
+ target_method.name
58
61
  end
59
62
 
60
63
  # Return context
@@ -104,7 +107,7 @@ module Mutant
104
107
  # @api private
105
108
  #
106
109
  def source_location
107
- method.source_location
110
+ target_method.source_location
108
111
  end
109
112
 
110
113
  # Return subject
@@ -118,27 +121,22 @@ module Mutant
118
121
  # @api private
119
122
  #
120
123
  def subject
121
- node = matched_node
124
+ node = matched_node_path.last
122
125
  return unless node
123
126
  self.class::SUBJECT_CLASS.new(env.config, context, node)
124
127
  end
125
128
  memoize :subject
126
129
 
127
- # Return matched node
128
- #
129
- # @return [Parser::AST::Node]
130
- # if node could be found
130
+ # Return matched node path
131
131
  #
132
- # @return [nil]
133
- # otherwise
132
+ # @return [Array<Parser::AST::Node>]
134
133
  #
135
134
  # @api private
136
135
  #
137
- def matched_node
138
- AST.find_last(ast) do |node|
139
- match?(node)
140
- end
136
+ def matched_node_path
137
+ AST.find_last_path(ast, &method(:match?))
141
138
  end
139
+ memoize :matched_node_path
142
140
 
143
141
  end # Method
144
142
  end # Matcher
@@ -15,10 +15,10 @@ module Mutant
15
15
  #
16
16
  # @api private
17
17
  #
18
- def self.build(env, scope, method)
19
- name = method.name
18
+ def self.build(env, scope, target_method)
19
+ name = target_method.name
20
20
  if scope.ancestors.include?(::Memoizable) && scope.memoized?(name)
21
- return Memoized.new(env, scope, method)
21
+ return Memoized.new(env, scope, target_method)
22
22
  end
23
23
  super
24
24
  end
@@ -68,7 +68,7 @@ module Mutant
68
68
  # @api private
69
69
  #
70
70
  def source_location
71
- scope.unmemoized_instance_method(method.name).source_location
71
+ scope.unmemoized_instance_method(method_name).source_location
72
72
  end
73
73
 
74
74
  end # Memoized
@@ -29,7 +29,7 @@ module Mutant
29
29
 
30
30
  private
31
31
 
32
- # Test scope if name matches expresion
32
+ # Test scope if name matches expression
33
33
  #
34
34
  # @param [Module, Class] scope
35
35
  #
data/lib/mutant/meta.rb CHANGED
@@ -16,7 +16,8 @@ module Mutant
16
16
  # @api private
17
17
  #
18
18
  def self.add(&block)
19
- ALL << DSL.run(block)
19
+ file = caller.first.split(':in', 2).first
20
+ ALL << DSL.run(file, block)
20
21
  end
21
22
 
22
23
  Pathname.glob(Pathname.new(__FILE__).parent.parent.parent.join('meta', '**/*.rb'))
@@ -1,7 +1,7 @@
1
1
  module Mutant
2
2
  module Meta
3
3
  class Example
4
- include Adamantium, Concord::Public.new(:node, :mutations)
4
+ include Adamantium, Concord::Public.new(:file, :node, :mutations)
5
5
 
6
6
  # Return a verification instance
7
7
  #
@@ -26,7 +26,7 @@ module Mutant
26
26
 
27
27
  # Return generated mutations
28
28
  #
29
- # @return [Emumerable<Mutant::Mutation>]
29
+ # @return [Enumerable<Mutant::Mutation>]
30
30
  #
31
31
  # @api private
32
32
  #
@@ -97,7 +97,7 @@ module Mutant
97
97
  def mutation_report
98
98
  original_node = example.node
99
99
  [
100
- 'Original-AST:',
100
+ "#{example.file}, Original-AST:",
101
101
  original_node.inspect,
102
102
  'Original-Source:',
103
103
  example.source,
@@ -12,8 +12,8 @@ module Mutant
12
12
  #
13
13
  # @api private
14
14
  #
15
- def self.run(block)
16
- instance = new
15
+ def self.run(file, block)
16
+ instance = new(file)
17
17
  instance.instance_eval(&block)
18
18
  instance.example
19
19
  end
@@ -24,7 +24,8 @@ module Mutant
24
24
  #
25
25
  # @api private
26
26
  #
27
- def initialize
27
+ def initialize(file)
28
+ @file = file
28
29
  @source = nil
29
30
  @expected = []
30
31
  end
@@ -40,7 +41,7 @@ module Mutant
40
41
  #
41
42
  def example
42
43
  raise 'source not defined' unless @source
43
- Example.new(@source, @expected)
44
+ Example.new(@file, @source, @expected)
44
45
  end
45
46
 
46
47
  private
@@ -8,6 +8,8 @@ module Mutant
8
8
  include AbstractType, Unparser::Constants
9
9
  include AST::NamedChildren, AST::NodePredicates, AST::Sexp, AST::Nodes
10
10
 
11
+ TAUTOLOGY = ->(_input) { true }
12
+
11
13
  # Helper to define a named child
12
14
  #
13
15
  # @param [Parser::AST::Node] node
@@ -79,7 +81,7 @@ module Mutant
79
81
  # @api private
80
82
  #
81
83
  def mutate_child(index, mutator = Mutator, &block)
82
- block ||= ->(_node) { true }
84
+ block ||= TAUTOLOGY
83
85
  child = children.at(index)
84
86
  mutator.each(child, self) do |mutation|
85
87
  next unless block.call(mutation)
@@ -211,6 +213,20 @@ module Mutant
211
213
  AST::Types::OP_ASSIGN.include?(parent_type) && parent.node.children.first.equal?(node)
212
214
  end
213
215
 
216
+ # Return children indices
217
+ #
218
+ # @param [Range] range
219
+ #
220
+ # @return [Enumerable<Fixnum>]
221
+ #
222
+ # @api pirvate
223
+ #
224
+ def children_indices(range)
225
+ range_end = range.end
226
+ last_index = range_end >= 0 ? range_end : children.length + range_end
227
+ range.begin.upto(last_index)
228
+ end
229
+
214
230
  end # Node
215
231
  end # Mutator
216
232
  end # Mutant
@@ -28,5 +28,5 @@ module Mutant
28
28
  end # Boolean
29
29
  end # Literal
30
30
  end # Node
31
- end # Mutatork
31
+ end # Mutator
32
32
  end # Mutant
@@ -2,10 +2,86 @@ module Mutant
2
2
  class Mutator
3
3
  class Node
4
4
  # Mutator for rescue nodes
5
- class Rescue < Generic
5
+ class Rescue < self
6
6
 
7
7
  handle :rescue
8
8
 
9
+ children :body
10
+
11
+ define_named_child(:else_body, -1)
12
+
13
+ RESCUE_INDICES = (1..-2).freeze
14
+
15
+ # Emit mutations
16
+ #
17
+ # @return [undefined]
18
+ #
19
+ # @api private
20
+ #
21
+ def dispatch
22
+ mutate_body
23
+ mutate_rescue_bodies
24
+ mutate_else_body
25
+ end
26
+
27
+ private
28
+
29
+ # Mutate child by name
30
+ #
31
+ # @return [undefined]
32
+ #
33
+ # @api private
34
+ #
35
+ def mutate_rescue_bodies
36
+ children_indices(RESCUE_INDICES).each do |index|
37
+ rescue_body = children.at(index)
38
+ next unless rescue_body
39
+ mutate_child(index)
40
+ resbody_body = AST::Meta.for(rescue_body).body
41
+ emit_concat(resbody_body) if resbody_body
42
+ end
43
+ end
44
+
45
+ # Emit concatenation with body
46
+ #
47
+ # @param [Parser::AST::Node] child
48
+ #
49
+ # @return [undefined]
50
+ #
51
+ # @api private
52
+ #
53
+ def emit_concat(child)
54
+ if body
55
+ emit(s(:begin, body, child))
56
+ else
57
+ emit(child)
58
+ end
59
+ end
60
+
61
+ # Emit body mutations
62
+ #
63
+ # @return [undefined]
64
+ #
65
+ # @api private
66
+ #
67
+ def mutate_body
68
+ return unless body
69
+ emit_body_mutations
70
+ emit(body)
71
+ end
72
+
73
+ # Emit else body mutations
74
+ #
75
+ # @return [undefined]
76
+ #
77
+ # @api private
78
+ #
79
+ def mutate_else_body
80
+ return unless else_body
81
+ emit_else_body_mutations
82
+ emit_concat(else_body)
83
+ end
84
+
9
85
  end # Rescue
10
86
  end # Node
11
87
  end # Mutator
@@ -35,7 +35,7 @@ module Mutant
35
35
  # @api private
36
36
  #
37
37
  def mutate_indices
38
- INDEX_RANGE.begin.upto(children.length + INDEX_RANGE.end).each do |index|
38
+ children_indices(INDEX_RANGE).each do |index|
39
39
  delete_child(index)
40
40
  mutate_child(index)
41
41
  end
@@ -22,7 +22,7 @@ module Mutant
22
22
  yield
23
23
  self
24
24
  ensure
25
- desinfect
25
+ disinfect
26
26
  end
27
27
 
28
28
  # Infect kernel with highjack
@@ -43,13 +43,13 @@ module Mutant
43
43
  end
44
44
  end
45
45
 
46
- # Imperfectly desinfect kernel from highjack
46
+ # Imperfectly disinfect kernel from highjack
47
47
  #
48
48
  # @return [self]
49
49
  #
50
50
  # @api private
51
51
  #
52
- def desinfect
52
+ def disinfect
53
53
  original = @original
54
54
  target.module_eval do
55
55
  undef :require
data/lib/mutant/runner.rb CHANGED
@@ -106,7 +106,7 @@ module Mutant
106
106
 
107
107
  # Handle exit if needed
108
108
  #
109
- # @param [Result::Mutation] mutation
109
+ # @param [Result::Mutation] result
110
110
  #
111
111
  # @return [undefined]
112
112
  #
@@ -30,10 +30,6 @@ module Mutant
30
30
 
31
31
  # Return tests for mutation
32
32
  #
33
- # TODO: This logic is now centralized but still fucked.
34
- #
35
- # @param [Mutation] mutation
36
- #
37
33
  # @return [Array<Test>]
38
34
  #
39
35
  # @api private
@@ -1,4 +1,4 @@
1
1
  module Mutant
2
2
  # The current mutant version
3
- VERSION = '0.6.0'.freeze
3
+ VERSION = '0.6.2'.freeze
4
4
  end # Mutant
data/meta/def.rb CHANGED
@@ -13,6 +13,36 @@ Mutant::Meta::Example.add do
13
13
  mutation 'def foo; nil; rescue; end'
14
14
  mutation 'def foo; self; rescue; end'
15
15
  mutation 'def foo; end'
16
+
17
+ # Promote rescue resbody bodies
18
+ mutation 'def foo; foo; end'
19
+ end
20
+
21
+ Mutant::Meta::Example.add do
22
+ source 'def a; foo; rescue; bar; else; baz; end'
23
+
24
+ # Mutate all bodies
25
+ mutation 'def a; nil; rescue; bar; else; baz; end'
26
+ mutation 'def a; self; rescue; bar; else; baz; end'
27
+ mutation 'def a; foo; rescue; nil; else; baz; end'
28
+ mutation 'def a; foo; rescue; self; else; baz; end'
29
+ mutation 'def a; foo; rescue; bar; else; nil; end'
30
+ mutation 'def a; foo; rescue; bar; else; self; end'
31
+
32
+ # Promote and concat rescue resbody bodies
33
+ mutation 'def a; foo; bar; end'
34
+
35
+ # Promote and concat else body
36
+ mutation 'def a; foo; baz; end'
37
+
38
+ # Promote rescue body
39
+ mutation 'def a; foo; end'
40
+
41
+ # Empty body
42
+ mutation 'def a; end'
43
+
44
+ # Failing body
45
+ mutation 'def a; raise; end'
16
46
  end
17
47
 
18
48
  Mutant::Meta::Example.add do
data/meta/if.rb CHANGED
@@ -14,7 +14,7 @@ Mutant::Meta::Example.add do
14
14
  # Deleted else branch
15
15
  mutation 'if condition; true end'
16
16
 
17
- # Deleted if branch resuting in unless rendering
17
+ # Deleted if branch resulting in unless rendering
18
18
  mutation 'unless condition; false; end'
19
19
 
20
20
  # Deleted if branch with promoting else branch to if branch
data/meta/rescue.rb CHANGED
@@ -9,6 +9,8 @@ Mutant::Meta::Example.add do
9
9
  mutation 'begin; rescue ExceptionA, self => error; true; end'
10
10
  mutation 'begin; rescue ExceptionA, ExceptionB => error; false; end'
11
11
  mutation 'begin; rescue ExceptionA, ExceptionB => error; nil; end'
12
+ mutation 'begin; true; end'
13
+
12
14
  end
13
15
 
14
16
  Mutant::Meta::Example.add do
@@ -19,6 +21,7 @@ Mutant::Meta::Example.add do
19
21
  mutation 'begin; rescue SomeException => error; false; end'
20
22
  mutation 'begin; rescue SomeException => error; nil; end'
21
23
  mutation 'begin; rescue self => error; true; end'
24
+ mutation 'begin; true; end'
22
25
  end
23
26
 
24
27
  Mutant::Meta::Example.add do
@@ -28,6 +31,7 @@ Mutant::Meta::Example.add do
28
31
  mutation 'begin; rescue => error; false; end'
29
32
  mutation 'begin; rescue => error; nil; end'
30
33
  mutation 'begin; rescue; true; end'
34
+ mutation 'begin; true; end'
31
35
  end
32
36
 
33
37
  Mutant::Meta::Example.add do
@@ -36,4 +40,5 @@ Mutant::Meta::Example.add do
36
40
  singleton_mutations
37
41
  mutation 'begin; rescue; false; end'
38
42
  mutation 'begin; rescue; nil; end'
43
+ mutation 'begin; true end'
39
44
  end
data/meta/super.rb CHANGED
@@ -10,7 +10,7 @@ Mutant::Meta::Example.add do
10
10
  source 'super()'
11
11
 
12
12
  singleton_mutations
13
- # this is zsuper a totally differend node thant super()
13
+ # this is zsuper a totally different node than super()
14
14
  mutation 'super'
15
15
  end
16
16
 
data/mutant.gemspec CHANGED
@@ -26,6 +26,7 @@ Gem::Specification.new do |gem|
26
26
  gem.add_runtime_dependency('parser', '~> 2.1')
27
27
  gem.add_runtime_dependency('ast', '~> 2.0')
28
28
  gem.add_runtime_dependency('diff-lcs', '~> 1.2')
29
+ gem.add_runtime_dependency('parallel', '~> 1.3')
29
30
  gem.add_runtime_dependency('morpher', '~> 0.2.3')
30
31
  gem.add_runtime_dependency('procto', '~> 0.0.2')
31
32
  gem.add_runtime_dependency('abstract_type', '~> 0.0.7')
@@ -37,7 +38,6 @@ Gem::Specification.new do |gem|
37
38
  gem.add_runtime_dependency('inflecto', '~> 0.0.2')
38
39
  gem.add_runtime_dependency('anima', '~> 0.2.0')
39
40
  gem.add_runtime_dependency('concord', '~> 0.1.5')
40
- gem.add_runtime_dependency('parallel', '~> 1.2.0')
41
41
 
42
42
  gem.add_development_dependency('bundler', '~> 1.3', '>= 1.3.5')
43
43
  end
@@ -17,7 +17,7 @@ RSpec.shared_examples_for 'a method matcher' do
17
17
  end
18
18
 
19
19
  it 'should have correct line number' do
20
- expect(node.location.expression.line - base).to eql(method_line)
20
+ expect(node.location.expression.line).to eql(method_line)
21
21
  end
22
22
 
23
23
  it 'should have correct arity' do
@@ -0,0 +1,38 @@
1
+ RSpec.describe Mutant::AST do
2
+ let(:object) { described_class }
3
+
4
+ describe '.find_last_path' do
5
+ subject { object.find_last_path(root, &block) }
6
+
7
+ let(:root) { s(:root, parent) }
8
+ let(:child_a) { s(:child_a) }
9
+ let(:child_b) { s(:child_b) }
10
+ let(:parent) { s(:parent, child_a, child_b) }
11
+
12
+ def path
13
+ subject.map(&:type)
14
+ end
15
+
16
+ context 'when no node matches' do
17
+ let(:block) { ->(_) { false } }
18
+
19
+ it { should eql([]) }
20
+ end
21
+
22
+ context 'when one node matches' do
23
+ let(:block) { ->(node) { node.equal?(child_a) } }
24
+
25
+ it 'returns the full path' do
26
+ expect(path).to eql([:root, :parent, :child_a])
27
+ end
28
+ end
29
+
30
+ context 'when two nodes match' do
31
+ let(:block) { ->(node) { node.equal?(child_a) || node.equal?(child_b) } }
32
+
33
+ it 'returns the last full path' do
34
+ expect(path).to eql([:root, :parent, :child_b])
35
+ end
36
+ end
37
+ end
38
+ end
@@ -11,10 +11,10 @@ RSpec.describe Mutant::Matcher::Method::Instance do
11
11
  let(:method) { scope.instance_method(method_name) }
12
12
  let(:yields) { [] }
13
13
  let(:namespace) { self.class }
14
- let(:scope) { self.class::Foo }
15
14
  let(:type) { :def }
16
- let(:method_name) { :bar }
15
+ let(:method_name) { :foo }
17
16
  let(:method_arity) { 0 }
17
+ let(:base) { TestApp::InstanceMethodTests }
18
18
 
19
19
  def name
20
20
  node.children[0]
@@ -36,109 +36,79 @@ RSpec.describe Mutant::Matcher::Method::Instance do
36
36
  it 'does warn' do
37
37
  subject
38
38
  expect(reporter.warn_calls.last).to(
39
- eql("#{method.inspect} does not have valid source location unable to emit matcher")
39
+ eql("#{method.inspect} does not have valid source location unable to emit subject")
40
40
  )
41
41
  end
42
42
  end
43
43
 
44
44
  context 'when method is defined once' do
45
- let(:base) { __LINE__ }
46
- class self::Foo
47
- def bar; end
48
- end
49
-
50
- let(:method_line) { 2 }
45
+ let(:scope) { base::DefinedOnce }
46
+ let(:method_line) { 7 }
51
47
 
52
48
  it_should_behave_like 'a method matcher'
53
49
  end
54
50
 
55
51
  context 'when method is defined once with a memoizer' do
56
- let(:base) { __LINE__ }
57
- class self::Foo
58
- def bar; end
59
- include Adamantium
60
- memoize :bar
61
- end
62
-
63
- let(:method_line) { 2 }
52
+ let(:scope) { base::WithMemoizer }
53
+ let(:method_line) { 12 }
64
54
 
65
55
  it_should_behave_like 'a method matcher'
66
56
  end
67
57
 
68
58
  context 'when method is defined multiple times' do
69
59
  context 'on different lines' do
70
- let(:base) { __LINE__ }
71
- class self::Foo
72
- def bar
73
- end
74
-
75
- def bar(_arg)
76
- end
77
- end
78
-
79
- let(:method_line) { 5 }
80
- let(:method_arity) { 1 }
60
+ let(:scope) { base::DefinedMultipleTimes::DifferentLines }
61
+ let(:method_line) { 21 }
62
+ let(:method_arity) { 1 }
81
63
 
82
64
  it_should_behave_like 'a method matcher'
83
65
  end
84
66
 
85
67
  context 'on the same line' do
86
- let(:base) { __LINE__ }
87
- class self::Foo
88
- def bar; end; def bar(_arg); end
89
- end
90
-
91
- let(:method_line) { 2 }
92
- let(:method_arity) { 1 }
68
+ let(:scope) { base::DefinedMultipleTimes::SameLineSameScope }
69
+ let(:method_line) { 26 }
70
+ let(:method_arity) { 1 }
93
71
 
94
72
  it_should_behave_like 'a method matcher'
95
73
  end
96
74
 
97
75
  context 'on the same line with different scope' do
98
- let(:base) { __LINE__ }
99
- class self::Foo
100
- def self.bar; end; def bar(_arg); end
101
- end
102
-
103
- let(:method_line) { 2 }
104
- let(:method_arity) { 1 }
76
+ let(:scope) { base::DefinedMultipleTimes::SameLineDifferentScope }
77
+ let(:method_line) { 30 }
78
+ let(:method_arity) { 1 }
105
79
 
106
80
  it_should_behave_like 'a method matcher'
107
81
  end
108
82
 
109
- context 'when nested' do
110
- let(:pattern) { 'Foo::Bar#baz' }
83
+ context 'in module eval' do
84
+ let(:scope) { base::InModuleEval }
111
85
 
112
- context 'in class' do
113
- let(:base) { __LINE__ }
114
- class self::Foo
115
- class Bar
116
- def baz
117
- end
118
- end
119
- end
120
-
121
- let(:method_line) { 3 }
122
- let(:method_name) { :baz }
123
- let(:scope) { self.class::Foo::Bar }
86
+ it 'does not emit matcher' do
87
+ subject
88
+ expect(yields.length).to be(0)
89
+ end
124
90
 
125
- it_should_behave_like 'a method matcher'
91
+ it 'does warn' do
92
+ subject
93
+ expect(reporter.warn_calls.last).to(
94
+ eql("#{method.inspect} is defined from a 3rd party lib unable to emit subject")
95
+ )
126
96
  end
97
+ end
127
98
 
128
- context 'in module' do
129
- let(:base) { __LINE__ }
130
- module self::Foo
131
- class Bar
132
- def baz
133
- end
134
- end
135
- end
99
+ context 'in class eval' do
100
+ let(:scope) { base::InClassEval }
136
101
 
137
- let(:method_line) { 3 }
138
- let(:method_name) { :baz }
139
- let(:scope) { self.class::Foo::Bar }
102
+ it 'does not emit matcher' do
103
+ subject
104
+ expect(yields.length).to be(0)
105
+ end
140
106
 
141
- it_should_behave_like 'a method matcher'
107
+ it 'does warn' do
108
+ subject
109
+ expect(reporter.warn_calls.last).to(
110
+ eql("#{method.inspect} is defined from a 3rd party lib unable to emit subject")
111
+ )
142
112
  end
143
113
  end
144
114
  end
@@ -6,10 +6,10 @@ RSpec.describe Mutant::Matcher::Method::Singleton, '#each' do
6
6
  let(:method) { scope.method(method_name) }
7
7
  let(:env) { Fixtures::TEST_ENV }
8
8
  let(:yields) { [] }
9
- let(:namespace) { self.class }
10
- let(:scope) { self.class::Foo }
11
9
  let(:type) { :defs }
10
+ let(:method_name) { :foo }
12
11
  let(:method_arity) { 0 }
12
+ let(:base) { TestApp::SingletonMethodTests }
13
13
 
14
14
  def name
15
15
  node.children[1]
@@ -22,14 +22,8 @@ RSpec.describe Mutant::Matcher::Method::Singleton, '#each' do
22
22
  context 'on singleton methods' do
23
23
 
24
24
  context 'when also defined on lvar' do
25
- let(:base) { __LINE__ }
26
- class self::Foo
27
- a = Object.new
28
- def a.bar; end; def self.bar; end
29
- end
30
-
31
- let(:method_name) { :bar }
32
- let(:method_line) { 3 }
25
+ let(:scope) { base::AlsoDefinedOnLvar }
26
+ let(:method_line) { 63 }
33
27
 
34
28
  it_should_behave_like 'a method matcher'
35
29
 
@@ -42,13 +36,8 @@ RSpec.describe Mutant::Matcher::Method::Singleton, '#each' do
42
36
  end
43
37
 
44
38
  context 'when defined on self' do
45
- let(:base) { __LINE__ }
46
- class self::Foo
47
- def self.bar; end
48
- end
49
-
50
- let(:method_name) { :bar }
51
- let(:method_line) { 2 }
39
+ let(:scope) { base::DefinedOnSelf }
40
+ let(:method_line) { 58 }
52
41
 
53
42
  it_should_behave_like 'a method matcher'
54
43
  end
@@ -56,34 +45,15 @@ RSpec.describe Mutant::Matcher::Method::Singleton, '#each' do
56
45
  context 'when defined on constant' do
57
46
 
58
47
  context 'inside namespace' do
59
- let(:base) { __LINE__ }
60
- module self::Namespace
61
- class Foo
62
- def Foo.bar
63
- end
64
- end
65
- end
66
-
67
- let(:scope) { self.class::Namespace::Foo }
68
- let(:method_name) { :bar }
69
- let(:method_line) { 3 }
48
+ let(:scope) { base::DefinedOnConstant::InsideNamespace }
49
+ let(:method_line) { 68 }
70
50
 
71
51
  it_should_behave_like 'a method matcher'
72
52
  end
73
53
 
74
54
  context 'outside namespace' do
75
- let(:base) { __LINE__ }
76
- module self::Namespace
77
- class Foo
78
- end
79
-
80
- def Foo.bar
81
- end
82
- end
83
-
84
- let(:method_name) { :bar }
85
- let(:method_line) { 5 }
86
- let(:scope) { self.class::Namespace::Foo }
55
+ let(:method_line) { 75 }
56
+ let(:scope) { base::DefinedOnConstant::OutsideNamespace }
87
57
 
88
58
  it_should_behave_like 'a method matcher'
89
59
  end
@@ -91,21 +61,9 @@ RSpec.describe Mutant::Matcher::Method::Singleton, '#each' do
91
61
 
92
62
  context 'when defined multiple times in the same line' do
93
63
  context 'with method on different scope' do
94
- let(:base) { __LINE__ }
95
- module self::Namespace
96
- module Foo; end
97
- module Bar
98
- def self.baz
99
- end
100
- def Foo.baz(_arg)
101
- end
102
- end
103
- end
104
-
105
- let(:scope) { self.class::Namespace::Bar }
106
- let(:method_name) { :baz }
107
- let(:method_line) { 4 }
108
- let(:method_arity) { 0 }
64
+ let(:scope) { base::DefinedMultipleTimes::SameLine::DifferentScope }
65
+ let(:method_line) { 94 }
66
+ let(:method_arity) { 1 }
109
67
 
110
68
  it_should_behave_like 'a method matcher'
111
69
  end
@@ -2,6 +2,100 @@
2
2
 
3
3
  # Namespace for test application
4
4
  module TestApp
5
+ module InstanceMethodTests
6
+ module DefinedOnce
7
+ def foo; end
8
+ end
9
+
10
+ class WithMemoizer
11
+ include Adamantium
12
+ def foo; end
13
+ memoize :foo
14
+ end
15
+
16
+ module DefinedMultipleTimes
17
+ class DifferentLines
18
+ def foo
19
+ end
20
+
21
+ def foo(_arg)
22
+ end
23
+ end
24
+
25
+ class SameLineSameScope
26
+ def foo; end; def foo(_arg); end
27
+ end
28
+
29
+ class SameLineDifferentScope
30
+ def self.foo; end; def foo(_arg); end
31
+ end
32
+ end
33
+
34
+ class InClassEval
35
+ class_eval do
36
+ def foo
37
+ end
38
+ end
39
+ end
40
+
41
+ class InModuleEval
42
+ module_eval do
43
+ def foo
44
+ end
45
+ end
46
+ end
47
+
48
+ class InInstanceEval
49
+ module_eval do
50
+ def foo
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ module SingletonMethodTests
57
+ module DefinedOnSelf
58
+ def self.foo; end
59
+ end
60
+
61
+ module AlsoDefinedOnLvar
62
+ a = Object.new
63
+ def a.foo; end; def self.foo; end
64
+ end
65
+
66
+ module DefinedOnConstant
67
+ module InsideNamespace
68
+ def InsideNamespace.foo
69
+ end
70
+ end
71
+
72
+ module OutsideNamespace
73
+ end
74
+
75
+ def OutsideNamespace.foo
76
+ end
77
+ end
78
+
79
+ module DefinedMultipleTimes
80
+ module DifferentLines
81
+ def self.foo
82
+ end
83
+
84
+ def self.foo(_arg)
85
+ end
86
+ end
87
+
88
+ module SameLine
89
+ module SameScope
90
+ def self.foo; end; def self.foo(_arg); end;
91
+ end
92
+
93
+ module DifferentScope
94
+ def self.foo; end; def DifferentScope.foo(_arg); end
95
+ end
96
+ end
97
+ end
98
+ end
5
99
  end
6
100
 
7
101
  require 'test_app/literal'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mutant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Markus Schirp
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-11 00:00:00.000000000 Z
11
+ date: 2014-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parser
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '1.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: parallel
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.3'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.3'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: morpher
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -206,20 +220,6 @@ dependencies:
206
220
  - - "~>"
207
221
  - !ruby/object:Gem::Version
208
222
  version: 0.1.5
209
- - !ruby/object:Gem::Dependency
210
- name: parallel
211
- requirement: !ruby/object:Gem::Requirement
212
- requirements:
213
- - - "~>"
214
- - !ruby/object:Gem::Version
215
- version: 1.2.0
216
- type: :runtime
217
- prerelease: false
218
- version_requirements: !ruby/object:Gem::Requirement
219
- requirements:
220
- - - "~>"
221
- - !ruby/object:Gem::Version
222
- version: 1.2.0
223
223
  - !ruby/object:Gem::Dependency
224
224
  name: bundler
225
225
  requirement: !ruby/object:Gem::Requirement
@@ -465,6 +465,7 @@ files:
465
465
  - spec/support/mutation_verifier.rb
466
466
  - spec/support/rspec.rb
467
467
  - spec/support/test_app.rb
468
+ - spec/unit/mutant/ast_spec.rb
468
469
  - spec/unit/mutant/cli_spec.rb
469
470
  - spec/unit/mutant/context/root_spec.rb
470
471
  - spec/unit/mutant/context/scope/root_spec.rb
@@ -541,6 +542,7 @@ test_files:
541
542
  - spec/integration/mutant/rspec_spec.rb
542
543
  - spec/integration/mutant/test_mutator_handles_types_spec.rb
543
544
  - spec/integration/mutant/zombie_spec.rb
545
+ - spec/unit/mutant/ast_spec.rb
544
546
  - spec/unit/mutant/cli_spec.rb
545
547
  - spec/unit/mutant/context/root_spec.rb
546
548
  - spec/unit/mutant/context/scope/root_spec.rb
@@ -578,4 +580,3 @@ test_files:
578
580
  - spec/unit/mutant/warning_expectation.rb
579
581
  - spec/unit/mutant/warning_filter_spec.rb
580
582
  - spec/unit/mutant_spec.rb
581
- has_rdoc: