mutant 0.2.4 → 0.2.5

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 (58) hide show
  1. data/.travis.yml +3 -3
  2. data/Changelog.md +21 -0
  3. data/Gemfile.devtools +1 -0
  4. data/Guardfile +1 -1
  5. data/README.md +48 -4
  6. data/config/flay.yml +2 -2
  7. data/config/flog.yml +1 -1
  8. data/config/site.reek +3 -1
  9. data/lib/mutant.rb +14 -2
  10. data/lib/mutant/cli.rb +38 -39
  11. data/lib/mutant/context/scope.rb +37 -32
  12. data/lib/mutant/killer/forking.rb +53 -0
  13. data/lib/mutant/killer/rspec.rb +1 -1
  14. data/lib/mutant/killer/static.rb +14 -0
  15. data/lib/mutant/matcher.rb +2 -0
  16. data/lib/mutant/matcher/method.rb +2 -2
  17. data/lib/mutant/matcher/method/singleton.rb +2 -1
  18. data/lib/mutant/matcher/object_space.rb +1 -1
  19. data/lib/mutant/matcher/scope_methods.rb +2 -0
  20. data/lib/mutant/mutation.rb +26 -0
  21. data/lib/mutant/mutation/filter/whitelist.rb +1 -1
  22. data/lib/mutant/mutator.rb +52 -9
  23. data/lib/mutant/mutator/node.rb +18 -19
  24. data/lib/mutant/mutator/node/arguments.rb +156 -0
  25. data/lib/mutant/mutator/node/block.rb +7 -20
  26. data/lib/mutant/mutator/node/define.rb +18 -1
  27. data/lib/mutant/mutator/node/iter_19.rb +26 -0
  28. data/lib/mutant/mutator/node/local_variable_assignment.rb +25 -0
  29. data/lib/mutant/mutator/node/noop.rb +4 -0
  30. data/lib/mutant/mutator/node/send.rb +24 -10
  31. data/lib/mutant/mutator/util.rb +28 -1
  32. data/lib/mutant/random.rb +1 -0
  33. data/lib/mutant/reporter.rb +28 -0
  34. data/lib/mutant/reporter/cli.rb +90 -19
  35. data/lib/mutant/reporter/null.rb +5 -3
  36. data/lib/mutant/reporter/stats.rb +65 -9
  37. data/lib/mutant/runner.rb +41 -2
  38. data/lib/mutant/strategy.rb +46 -5
  39. data/lib/mutant/strategy/rspec.rb +11 -4
  40. data/lib/mutant/strategy/rspec/example_lookup.rb +30 -30
  41. data/lib/mutant/subject.rb +11 -0
  42. data/mutant.gemspec +3 -2
  43. data/spec/integration/mutant/loader_spec.rb +4 -4
  44. data/spec/shared/mutator_behavior.rb +13 -1
  45. data/spec/unit/mutant/context/scope/root_spec.rb +20 -8
  46. data/spec/unit/mutant/context/scope/unqualified_name_spec.rb +2 -2
  47. data/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb +1 -1
  48. data/spec/unit/mutant/matcher/chain/each_spec.rb +6 -2
  49. data/spec/unit/mutant/mutator/node/define/mutation_spec.rb +76 -0
  50. data/spec/unit/mutant/mutator/node/send/mutation_spec.rb +80 -21
  51. data/spec/unit/mutant/strategy/rspec/example_lookup/spec_file_spec.rb +3 -3
  52. metadata +21 -10
  53. data/lib/mutant/inflector/defaults.rb +0 -64
  54. data/lib/mutant/inflector/inflections.rb +0 -211
  55. data/lib/mutant/inflector/methods.rb +0 -151
  56. data/lib/mutant/inflector/version.rb +0 -5
  57. data/locator.rb +0 -87
  58. data/spec/unit/mutant/context/scope/class_methods/build_spec.rb +0 -29
data/.travis.yml CHANGED
@@ -1,16 +1,16 @@
1
1
  before_install:
2
- - gem install bundler
2
+ - gem install mbj-inflector
3
3
  language: ruby
4
4
  script: 'bundle exec rake spec'
5
5
  rvm:
6
6
  - 1.8.7
7
7
  - 1.9.2
8
8
  - 1.9.3
9
- - rbx-18mode
10
9
  - rbx-19mode
11
10
  matrix:
12
11
  allow_failures:
13
- - rvm: rbx-19mode
12
+ # No mutators for 1.8 specifc AST nodes
13
+ - rvm: rbx-18mode
14
14
  - rvm: 1.8.7
15
15
  notifications:
16
16
  email:
data/Changelog.md CHANGED
@@ -1,11 +1,32 @@
1
+ # v0.2.5 xxxx
2
+
3
+ * [feature] Add --debug flag for showing killer output and mutation
4
+ * [feature] Run noop mutation per subject to guard against initial failing specs
5
+ * [feature] Mutate default into required arguments
6
+ * [feature] Mutate default literals
7
+ * [feature] Mutate unwinding of pattern args |(a, b), c] => |a, b, c|
8
+ * [feature] Mutate define and block arguments
9
+ * [feature] Mutate block arguments, inklusive pattern args
10
+ * [feature] Recurse into block bodies
11
+ * [change] Unvendor inflector use mbj-inflector from rubygems
12
+ * [fixed] Insert mutations at correct constant scope
13
+ * [fixed] Crash on mutating yield, added a noop for now
14
+ * [fixed] Crash on singleton methods defined on other than constants or self
15
+
16
+ [Compare v0.2.4..v0.2.5](https://github.com/mbj/mutant/compare/v0.2.4...v0.2.5)
17
+
1
18
  # v0.2.4 2012-12-12
2
19
 
3
20
  * [fixed] Correctly vendor inflector
4
21
 
22
+ [Compare v0.2.3..v0.2.4](https://github.com/mbj/mutant/compare/v0.2.3...v0.2.4)
23
+
5
24
  # v0.2.3 2012-12-08
6
25
 
7
26
  * [fixed] Prepend extra elements to hash and array instead of append. This fixes unkillable mutators in parallel assignments!
8
27
 
28
+ [Compare v0.2.2..v0.2.3](https://github.com/mbj/mutant/compare/v0.2.2...v0.2.3)
29
+
9
30
  # v0.2.2 2012-12-07
10
31
 
11
32
  * [feature] Add a shitload of operator expansions for dm2 strategy
data/Gemfile.devtools CHANGED
@@ -8,6 +8,7 @@ group :guard do
8
8
  gem 'guard', '~> 1.5.4'
9
9
  gem 'guard-bundler', '~> 1.0.0'
10
10
  gem 'guard-rspec', '~> 2.1.1'
11
+ gem 'rb-inotify', :git => 'https://github.com/mbj/rb-inotify'
11
12
  end
12
13
 
13
14
  group :benchmarks do
data/Guardfile CHANGED
@@ -4,7 +4,7 @@ guard :bundler do
4
4
  watch('Gemfile')
5
5
  end
6
6
 
7
- guard :rspec, :all_on_start => false do
7
+ guard :rspec, :all_on_start => false, :all_after_pass => false do
8
8
  # run all specs if the spec_helper or supporting files files are modified
9
9
  watch('spec/spec_helper.rb') { 'spec/unit' }
10
10
  watch(%r{\Aspec/(?:lib|support|shared)/.+\.rb\z}) { 'spec/unit' }
data/README.md CHANGED
@@ -6,9 +6,14 @@ mutant
6
6
  [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/mbj/mutant)
7
7
 
8
8
  Mutant is a mutation testing tool for ruby that aims to be better than existing mutation testers.
9
- The idea is that if code can be changed and your tests don't notice, either that code isn't being covered or it doesn't do anything.
10
9
 
11
- Mutant does currently only support 1.9 mode under rubinius or mri.
10
+ The idea is that if code can be changed and your tests do not notice, either that code isn't being covered
11
+ or it doesn't do anything (useful).
12
+
13
+ Mutant does currently only support 1.9 mode under rubinius or mri. It is a young project but already
14
+ used in the DataMapper-2.0 project.
15
+
16
+ See this [ASCII-Cast](http://ascii.io/a/1707) for mutant in action! (v0.2.1)
12
17
 
13
18
  Installation
14
19
  ------------
@@ -19,10 +24,49 @@ Examples
19
24
  --------
20
25
 
21
26
  ```
22
- cd your_lib
23
- # Run mutant on virtus (that uses the dm-2 style spec layout)
27
+ cd virtus
28
+ # Run mutant on virtus namespace (that uses the dm-2 style spec layout)
24
29
  mutant -I lib -r virtus --rspec-dm2 ::Virtus
30
+ # Run mutant on specific virtus class
31
+ mutant -I lib -r virtus --rspec-dm2 ::Virtus::Attribute
32
+ # Run mutant on specific virtus class method
33
+ mutant -I lib -r virtus --rspec-dm2 ::Virtus::Attribute.build
34
+ # Run mutant on specific virtus instance method
35
+ mutant -I lib -r virtus --rspec-dm2 ::Virtus::Attribute#name
36
+ ```
37
+
38
+ Strategies
39
+ ----------
40
+
41
+ Mutation testing is slow. To make it fast the selection of the correct set of tests to run is the key.
42
+ Mutant currently supports the following buildin strategies:
43
+
44
+ ### --rspec-dm2
45
+
46
+ This strategy is the *fastest* but requires discipline in spec file naming.
47
+
48
+ The following specs are executed to kill a mutation on:
25
49
  ```
50
+ Public instance methods: spec/unit/#{namespace}/#{class_name}/#{method_name}_spec.rb
51
+ Public singleton methods: spec/unit/#{namespace}/#{class_name}/class_methods/#{method_name}_spec.rb
52
+ Public instance methods: spec/unit/#{namespace}/#{class_name}/
53
+ Public singleton methods: spec/unit/#{namespace}/#{class_name}/class_methods
54
+ ```
55
+
56
+ ### --rspec-unit
57
+
58
+ This strategy executes all specs under ``./spec/unit`` for each mutation.
59
+
60
+ ### --rspec-integration
61
+
62
+ This strategy executes all specs under ``./spec/integration`` for each mutation.
63
+
64
+ ### --rspec-full
65
+
66
+ This strategy executes all specs under ``./spec`` for each mutation.
67
+
68
+ It is also plannned to allow explicit selections on specs to run and to support other test frameworks.
69
+ Custom project specific strategies are also on the roadmap.
26
70
 
27
71
  Credits
28
72
  -------
data/config/flay.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  ---
2
- threshold: 18
3
- total_score: 750
2
+ threshold: 34 # Todo bring down to ~20
3
+ total_score: 913
data/config/flog.yml CHANGED
@@ -1,2 +1,2 @@
1
1
  ---
2
- threshold: 14.4
2
+ threshold: 47.0
data/config/site.reek CHANGED
@@ -11,6 +11,7 @@ LargeClass:
11
11
  max_methods: 10
12
12
  exclude:
13
13
  - "Mutant::Matcher::Method" # 13 methods
14
+ - "Mutant::Reporter::CLI" # 16 methods TODO Reduce!
14
15
  enabled: true
15
16
  max_instance_variables: 3
16
17
  UncommunicativeMethodName:
@@ -40,7 +41,8 @@ IrresponsibleModule:
40
41
  exclude: []
41
42
  enabled: true
42
43
  UncommunicativeModuleName:
43
- accept: []
44
+ accept:
45
+ - Mutant::Strategy::Rspec::DM2
44
46
  exclude: []
45
47
  enabled: true
46
48
  reject:
data/lib/mutant.rb CHANGED
@@ -6,13 +6,24 @@ require 'descendants_tracker'
6
6
  require 'securerandom'
7
7
  require 'equalizer'
8
8
  require 'digest/sha1'
9
+ require 'inflector'
9
10
  require 'to_source'
10
11
  require 'ice_nine'
11
- require 'ice_nine/core_ext/object'
12
12
  require 'diff/lcs'
13
13
  require 'diff/lcs/hunk'
14
14
  require 'rspec'
15
15
 
16
+ module IceNine
17
+ class Freezer
18
+ class Rubinius
19
+ class AST < IceNine::Freezer::Object
20
+ class Node < IceNine::Freezer::Object
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
16
27
  # Library namespace
17
28
  module Mutant
18
29
 
@@ -73,6 +84,8 @@ require 'mutant/mutator/node/send'
73
84
  require 'mutant/mutator/node/arguments'
74
85
  require 'mutant/mutator/node/define'
75
86
  require 'mutant/mutator/node/return'
87
+ require 'mutant/mutator/node/local_variable_assignment'
88
+ require 'mutant/mutator/node/iter_19'
76
89
  require 'mutant/mutator/node/if_statement'
77
90
  require 'mutant/mutator/node/receiver_case'
78
91
  require 'mutant/loader'
@@ -102,4 +115,3 @@ require 'mutant/reporter'
102
115
  require 'mutant/reporter/stats'
103
116
  require 'mutant/reporter/null'
104
117
  require 'mutant/reporter/cli'
105
- require 'mutant/inflector'
data/lib/mutant/cli.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Mutant
2
2
  # Comandline parser
3
3
  class CLI
4
- include Adamantium::Flat, Equalizer.new(:matcher, :filter, :killer)
4
+ include Adamantium::Flat, Equalizer.new(:matcher, :filter, :strategy, :reporter)
5
5
 
6
6
  # Error raised when CLI argv is inalid
7
7
  Error = Class.new(RuntimeError)
@@ -44,6 +44,20 @@ module Mutant
44
44
  end
45
45
  memoize :matcher
46
46
 
47
+ # Test for running in debug mode
48
+ #
49
+ # @return [true]
50
+ # if debug mode is active
51
+ #
52
+ # @return [false]
53
+ # otherwise
54
+ #
55
+ # @api private
56
+ #
57
+ def debug?
58
+ !!@debug
59
+ end
60
+
47
61
  # Return mutation filter
48
62
  #
49
63
  # @return [Mutant::Matcher]
@@ -67,7 +81,9 @@ module Mutant
67
81
  #
68
82
  def strategy
69
83
  @strategy || raise(Error, 'no strategy was set!')
84
+ @strategy.new(self)
70
85
  end
86
+ memoize :strategy
71
87
 
72
88
  # Return reporter
73
89
  #
@@ -76,7 +92,7 @@ module Mutant
76
92
  # @api private
77
93
  #
78
94
  def reporter
79
- Mutant::Reporter::CLI.new($stdout)
95
+ Mutant::Reporter::CLI.new(self)
80
96
  end
81
97
  memoize :reporter
82
98
 
@@ -88,7 +104,8 @@ module Mutant
88
104
  '--include' => [:add_load_path ],
89
105
  '-r' => [:require_library ],
90
106
  '--require' => [:require_library ],
91
- #'--killer-fork' => [:enable_killer_fork ],
107
+ '--debug' => [:set_debug ],
108
+ '-d' => [:set_debug ],
92
109
  '--rspec-unit' => [:set_strategy, Strategy::Rspec::Unit ],
93
110
  '--rspec-full' => [:set_strategy, Strategy::Rspec::Full ],
94
111
  '--rspec-dm2' => [:set_strategy, Strategy::Rspec::DM2 ],
@@ -98,33 +115,6 @@ module Mutant
98
115
 
99
116
  OPTION_PATTERN = %r(\A-(?:-)?[a-zA-Z0-9\-]+\z).freeze
100
117
 
101
- # Return selected killer
102
- #
103
- # @return [Killer]
104
- #
105
- # @api private
106
- #
107
- def selected_killer
108
- unless @rspec
109
- raise Error, "Only rspec is supported currently use --rspec switch"
110
- end
111
-
112
- Mutant::Killer::Rspec
113
- end
114
- memoize :selected_killer
115
-
116
- # Return option for argument with index
117
- #
118
- # @param [Fixnum] index
119
- #
120
- # @return [String]
121
- #
122
- # @api private
123
- #
124
- def option(index)
125
- @arguments.fetch(index+1)
126
- end
127
-
128
118
  # Initialize CLI
129
119
  #
130
120
  # @param [Array<String>] arguments
@@ -146,6 +136,18 @@ module Mutant
146
136
  matcher
147
137
  end
148
138
 
139
+ # Return option for argument with index
140
+ #
141
+ # @param [Fixnum] index
142
+ #
143
+ # @return [String]
144
+ #
145
+ # @api private
146
+ #
147
+ def option(index)
148
+ @arguments.fetch(index+1)
149
+ end
150
+
149
151
  # Return current argument
150
152
  #
151
153
  # @return [String]
@@ -233,7 +235,7 @@ module Mutant
233
235
 
234
236
  # Add mutation filter
235
237
  #
236
- # @param [Class<Mutant::Filter>]
238
+ # @param [Class<Mutant::Filter>] klass
237
239
  #
238
240
  # @return [undefined]
239
241
  #
@@ -268,22 +270,20 @@ module Mutant
268
270
  @rspec = true
269
271
  end
270
272
 
271
- # Enable killer forking
273
+ # Set debug mode
272
274
  #
273
275
  # @api private
274
276
  #
275
- # @return [self]
276
- #
277
- # @api private
277
+ # @return [undefined]
278
278
  #
279
- def enable_killer_fork
279
+ def set_debug
280
280
  consume(1)
281
- @forking = true
281
+ @debug = true
282
282
  end
283
283
 
284
284
  # Set strategy
285
285
  #
286
- # @param [Strategy]
286
+ # @param [Strategy] strategy
287
287
  #
288
288
  # @api private
289
289
  #
@@ -304,6 +304,5 @@ module Mutant
304
304
  require(current_option_value)
305
305
  consume(2)
306
306
  end
307
-
308
307
  end
309
308
  end
@@ -2,54 +2,59 @@ module Mutant
2
2
  class Context
3
3
  # Scope context for mutation (Class or Module)
4
4
  class Scope < self
5
- include Adamantium::Flat, AbstractType, Equalizer.new(:scope, :source_path)
5
+ include Adamantium::Flat, Equalizer.new(:scope, :source_path)
6
6
 
7
- # Class context for mutation
8
- class Class < self
9
- SCOPE_CLASS = Rubinius::AST::ClassScope
10
- KEYWORD = 'class'.freeze
11
- end
12
-
13
- # Module context for mutation
14
- class Module < self
15
- SCOPE_CLASS = Rubinius::AST::ModuleScope
16
- KEYWORD = 'module'.freeze
7
+ # Return AST wrapping mutated node
8
+ #
9
+ # @return [Rubinius::AST::Script]
10
+ #
11
+ # @api private
12
+ #
13
+ def root(node)
14
+ nesting.reverse.inject(node) do |current, scope|
15
+ self.class.wrap(scope, current)
16
+ end
17
17
  end
18
18
 
19
- TABLE = {
20
- ::Module => Module,
21
- ::Class => Class
22
- }.freeze
23
-
24
- # Build scope context from class or module
19
+ # Wrap node into ast node
25
20
  #
26
- # @param [String] source_path
21
+ # @param [Class, Module] scope
22
+ # @param [Rubinius::AST::Node] node
27
23
  #
28
- # @param [::Class|::Module] scope
24
+ # @return [Rubinius::AST::Class]
25
+ # if scope is of kind Class
29
26
  #
30
- # @return [Context::Scope]
27
+ # @return [Rubinius::AST::Module]
28
+ # if scope is of kind module
31
29
  #
32
30
  # @api private
33
31
  #
34
- def self.build(scope, source_path)
35
- scope_class = scope.class
36
- klass = TABLE.fetch(scope_class) do
37
- raise ArgumentError, "Can only build mutation scope from class or module got: #{scope_class.inspect}"
38
- end.new(scope, source_path)
32
+ def self.wrap(scope, node)
33
+ name = scope.name.split('::').last
34
+ case scope
35
+ when ::Class
36
+ ::Rubinius::AST::Class.new(0, name.to_sym, nil, node)
37
+ when ::Module
38
+ ::Rubinius::AST::Module.new(0, name.to_sym, node)
39
+ else
40
+ raise "Cannot wrap scope: #{scope.inspect}"
41
+ end
39
42
  end
40
43
 
41
- # Return AST wrapping mutated node
44
+ # Return nesting
42
45
  #
43
- # @return [Rubinius::AST::Script]
46
+ # @return [Enumerable<Class,Module>]
44
47
  #
45
48
  # @api private
46
49
  #
47
- def root(node)
48
- root = root_ast
49
- block = Rubinius::AST::Block.new(1, [node])
50
- root.body = scope_class.new(1, root.name, block)
51
- root
50
+ def nesting
51
+ const = ::Object
52
+ name_nesting.each_with_object([]) do |name, nesting|
53
+ const = const.const_get(name)
54
+ nesting << const
55
+ end
52
56
  end
57
+ memoize :nesting
53
58
 
54
59
  # Return unqualified name of scope
55
60
  #