mutant 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
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
  #