mutant 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.gitignore +5 -0
  2. data/.rvmrc +1 -1
  3. data/.travis.yml +6 -0
  4. data/Readme.md +13 -0
  5. data/exe/mutate +6 -0
  6. data/lib/mutant.rb +21 -0
  7. data/lib/mutant/extensions.rb +8 -0
  8. data/lib/mutant/formatter.rb +19 -0
  9. data/lib/mutant/implementation.rb +70 -0
  10. data/lib/mutant/literal.rb +134 -0
  11. data/lib/mutant/method.rb +31 -0
  12. data/lib/mutant/mutatee.rb +61 -0
  13. data/lib/mutant/mutation.rb +24 -0
  14. data/lib/mutant/mutator.rb +49 -0
  15. data/lib/mutant/node.rb +26 -0
  16. data/lib/mutant/random.rb +37 -0
  17. data/lib/mutant/reporter.rb +38 -0
  18. data/lib/mutant/runners/rspec.rb +26 -4
  19. data/lib/mutant/version.rb +1 -1
  20. data/mutant.gemspec +5 -3
  21. data/spec/functional/class_spec.rb +46 -0
  22. data/spec/functional/instance_method/array_spec.rb +53 -0
  23. data/spec/functional/instance_method/boolean_spec.rb +101 -0
  24. data/spec/functional/instance_method/call_spec.rb +161 -0
  25. data/spec/functional/instance_method/fixnum_spec.rb +53 -0
  26. data/spec/functional/instance_method/float_spec.rb +53 -0
  27. data/spec/functional/instance_method/hash_spec.rb +53 -0
  28. data/spec/functional/instance_method/if_spec.rb +57 -0
  29. data/spec/functional/instance_method/range_spec.rb +53 -0
  30. data/spec/functional/instance_method/regex_spec.rb +55 -0
  31. data/spec/functional/instance_method/string_spec.rb +53 -0
  32. data/spec/functional/instance_method/symbol_spec.rb +53 -0
  33. data/spec/functional/reporter/method_loaded_spec.rb +62 -0
  34. data/spec/functional/reporter/running_mutations_spec.rb +60 -0
  35. data/spec/functional/runners/rspec_spec.rb +26 -0
  36. data/spec/functional/singleton_method/array_spec.rb +53 -0
  37. data/spec/functional/singleton_method/boolean_spec.rb +101 -0
  38. data/spec/functional/singleton_method/call_spec.rb +161 -0
  39. data/spec/functional/singleton_method/fixnum_spec.rb +53 -0
  40. data/spec/functional/singleton_method/float_spec.rb +53 -0
  41. data/spec/functional/singleton_method/hash_spec.rb +53 -0
  42. data/spec/functional/singleton_method/if_spec.rb +57 -0
  43. data/spec/functional/singleton_method/range_spec.rb +53 -0
  44. data/spec/functional/singleton_method/regex_spec.rb +55 -0
  45. data/spec/functional/singleton_method/string_spec.rb +53 -0
  46. data/spec/functional/singleton_method/symbol_spec.rb +53 -0
  47. data/spec/mutant/extensions_spec.rb +13 -0
  48. data/spec/mutant/implementation_spec.rb +223 -0
  49. data/spec/mutant/literal_spec.rb +129 -0
  50. data/spec/mutant/mutatee_spec.rb +28 -0
  51. data/spec/mutant/node_spec.rb +41 -0
  52. data/spec/mutant/random_spec.rb +33 -0
  53. data/spec/mutant/reporter_spec.rb +17 -0
  54. data/spec/mutant_spec.rb +28 -0
  55. data/spec/spec_helper.rb +11 -3
  56. data/spec/support/example_group_helpers.rb +11 -0
  57. data/spec/support/example_helpers.rb +5 -0
  58. metadata +149 -81
  59. data/spec/functional/boolean_spec.rb +0 -49
data/.gitignore CHANGED
@@ -1,6 +1,11 @@
1
+ .DS_Store
1
2
  *.gem
2
3
  .bundle
3
4
  Gemfile.lock
4
5
  pkg/*
5
6
  tmp
7
+ *.rbx
8
+ *.rbc
9
+ bin
6
10
  .rbx
11
+ test_spec.rb
data/.rvmrc CHANGED
@@ -1 +1 @@
1
- rvm use rbx@mutant --create
1
+ rvm use rbx-head@mutant --create
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ rvm:
2
+ - rbx-18mode
3
+ - rbx-19mode
4
+
5
+ bundler_args: "--binstubs"
6
+ script: "bin/rspec spec"
data/Readme.md ADDED
@@ -0,0 +1,13 @@
1
+ # mutant [![Build Status](https://secure.travis-ci.org/txus/mutant.png)](http://travis-ci.org/txus/mutant) [![Dependency Status](https://gemnasium.com/txus/mutant.png)](https://gemnasium.com/txus/mutant)
2
+
3
+ Mutant is a mutation tester. It modifies your code and runs your tests to make sure they fail. 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.
4
+
5
+ Largely based on [heckle](https://github.com/seattlerb/heckle), this is a rewrite on top of [Rubinius](http://rubini.us).
6
+
7
+ ## Development roadmap
8
+
9
+ As an experiment, I've set up a [public Trello board](https://trello.com/board/mutant/4f452510101d860b203b542d) with the development roadmap up to the 1.0 release. You can vote and comment cards to give constructive feedback to the project. Just have a look and leave a comment! :)
10
+
11
+ ## Who's this
12
+
13
+ This project was originally created by [@justinko](http://twitter.com/justinko) and is released under the MIT license. I am pleased to be the current maintainer :) I'm [@txustice](http://twitter.com/txustice) on twitter (where you should probably follow me!).
data/exe/mutate ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'mutant'
5
+
6
+ Mutant.run(ARGV)
data/lib/mutant.rb CHANGED
@@ -1,7 +1,28 @@
1
+ require 'mutant/extensions'
2
+ require 'mutant/formatter'
3
+ require 'mutant/implementation'
4
+ require 'mutant/literal'
5
+ require 'mutant/method'
6
+ require 'mutant/mutatee'
7
+ require 'mutant/mutation'
8
+ require 'mutant/mutator'
9
+ require 'mutant/node'
10
+ require 'mutant/random'
11
+ require 'mutant/reporter'
1
12
  require 'mutant/version'
2
13
 
3
14
  module Mutant
4
15
  module Runners
5
16
  autoload :RSpec, 'mutant/runners/rspec'
6
17
  end
18
+
19
+ def self.run(args)
20
+ Runners::RSpec.run(args)
21
+ end
22
+
23
+ def self.mutate(implementation)
24
+ implementation.mutatees.each do |mutatee|
25
+ Mutator.new(mutatee).mutate
26
+ end
27
+ end
7
28
  end
@@ -0,0 +1,8 @@
1
+ module ClassExtensions
2
+ def basename
3
+ name.split('::').last
4
+ end
5
+ end
6
+
7
+ Class.send :include, ClassExtensions
8
+ Rubinius::AST::Node.send :include, ClassExtensions
@@ -0,0 +1,19 @@
1
+ require 'to_source'
2
+
3
+ module Mutant
4
+ class Formatter
5
+ attr_reader :item
6
+
7
+ def initialize(item)
8
+ @item = item
9
+ end
10
+
11
+ def nested?
12
+ false
13
+ end
14
+
15
+ def to_s
16
+ item.to_source
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,70 @@
1
+ module Mutant
2
+ class Implementation
3
+ SINGLETON_SCOPE = '.'
4
+ INSTANCE_SCOPE = '#'
5
+ SCOPE_REGEXP = Regexp.union(SINGLETON_SCOPE, INSTANCE_SCOPE)
6
+
7
+ def initialize(str)
8
+ @str = str
9
+ end
10
+
11
+ def scope_type
12
+ { SINGLETON_SCOPE => :singleton, INSTANCE_SCOPE => :instance
13
+ }[method_scope]
14
+ end
15
+
16
+ def class_name
17
+ @str.split(SCOPE_REGEXP)[0]
18
+ end
19
+
20
+ def method_scope
21
+ @str[SCOPE_REGEXP]
22
+ end
23
+
24
+ def method_name
25
+ @str.split(SCOPE_REGEXP)[1] if method_scope
26
+ end
27
+
28
+ def constant
29
+ class_name.split(/::/).inject(Object) do |context, name|
30
+ context.const_get name
31
+ end
32
+ end
33
+
34
+ def all_implementations
35
+ all_methods.map {|formatted_method| self.class.new(formatted_method)}
36
+ end
37
+
38
+ def all_methods
39
+ all_singleton_methods + all_instance_methods
40
+ end
41
+
42
+ def all_singleton_methods
43
+ constant.singleton_methods(false).delete_if {|meth_name|
44
+ meth_name.to_s == '__class_init__' }.map {|meth_name|
45
+ format_method(meth_name, SINGLETON_SCOPE)}
46
+ end
47
+
48
+ def all_instance_methods
49
+ constant.instance_methods(false).map do |meth_name|
50
+ format_method(meth_name, INSTANCE_SCOPE)
51
+ end
52
+ end
53
+
54
+ def mutatees
55
+ Array(method_scope ? self : all_implementations).map do |impl|
56
+ Mutatee.new(impl)
57
+ end
58
+ end
59
+
60
+ def to_s
61
+ @str
62
+ end
63
+
64
+ private
65
+
66
+ def format_method(meth_name, method_scope_type)
67
+ class_name + method_scope_type + meth_name.to_s
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,134 @@
1
+ module Mutant
2
+ class Literal
3
+ def self.literal_class(node)
4
+ const_get(node.class.basename)
5
+ end
6
+
7
+ def initialize(node)
8
+ @node = node
9
+ @class = self.class.literal_class(node)
10
+ end
11
+
12
+ def swap
13
+ @class.new(@node).swap
14
+ end
15
+
16
+ class BaseLiteral
17
+ def initialize(node)
18
+ @node = node
19
+ end
20
+ end
21
+
22
+ class FalseLiteral < BaseLiteral
23
+ def swap
24
+ Rubinius::AST::TrueLiteral.new(@node.line)
25
+ end
26
+ end
27
+
28
+ class TrueLiteral < BaseLiteral
29
+ def swap
30
+ Rubinius::AST::FalseLiteral.new(@node.line)
31
+ end
32
+ end
33
+
34
+ class SymbolLiteral < BaseLiteral
35
+ def swap
36
+ @node.value = Random.symbol
37
+ @node
38
+ end
39
+ end
40
+
41
+ class StringLiteral < BaseLiteral
42
+ def swap
43
+ @node.string = Random.string
44
+ @node
45
+ end
46
+ end
47
+
48
+ class FixnumLiteral < BaseLiteral
49
+ def swap
50
+ @node.value = Random.fixnum
51
+ @node
52
+ end
53
+ end
54
+
55
+ class FloatLiteral < BaseLiteral
56
+ def swap
57
+ @node.value = Random.float
58
+ @node
59
+ end
60
+ end
61
+
62
+ class Range < BaseLiteral
63
+ def swap
64
+ range = Random.range
65
+ @node.start = Rubinius::AST::FixnumLiteral.new(@node.line, range.min)
66
+ @node.finish = Rubinius::AST::FixnumLiteral.new(@node.line, range.max)
67
+ @node
68
+ end
69
+ end
70
+
71
+ class RegexLiteral < BaseLiteral
72
+ def swap
73
+ @node.source = Regexp.escape(Random.string)
74
+ @node
75
+ end
76
+ end
77
+
78
+ class ArrayLiteral < BaseLiteral
79
+ def swap
80
+ @node.body = @node.body.reverse[1..-1]
81
+ @node
82
+ end
83
+ end
84
+
85
+ class HashLiteral < BaseLiteral
86
+ def swap
87
+ new_body = @node.array.each_slice(2).inject([]) do |body, (key, value)|
88
+ new_value = literal_class(value).new(value.clone).swap
89
+
90
+ body << key << new_value
91
+ end
92
+
93
+ @node.array = new_body
94
+ @node
95
+ end
96
+
97
+ private
98
+
99
+ def literal_class(value)
100
+ Module.nesting[1].literal_class(value)
101
+ end
102
+ end
103
+
104
+ class LocalVariableAssignment < BaseLiteral
105
+ def swap
106
+ @node.value = literal_class.new(@node.value.clone).swap
107
+ @node
108
+ end
109
+
110
+ private
111
+
112
+ def literal_class
113
+ Module.nesting[1].literal_class(@node.value)
114
+ end
115
+ end
116
+
117
+ class If < BaseLiteral
118
+ def swap
119
+ @node.body, @node.else = @node.else, @node.body
120
+ @node
121
+ end
122
+ end
123
+
124
+ class Send < BaseLiteral
125
+ def swap
126
+ line = @node.line
127
+ @node = Rubinius::AST::NilLiteral.new(line)
128
+ @node
129
+ end
130
+ end
131
+
132
+ SendWithArguments = Send
133
+ end
134
+ end
@@ -0,0 +1,31 @@
1
+ module Mutant
2
+ class Method < Rubinius::Melbourne
3
+ attr_reader :ast, :source_name, :source_file, :source_line
4
+
5
+ DEFAULT_LINE = 1
6
+
7
+ def initialize(method)
8
+ @ast = nil
9
+ @source_name = method.name.to_sym
10
+ @source_file, @source_line = method.source_location
11
+ super(source_file, DEFAULT_LINE)
12
+ end
13
+
14
+ # TODO: Check for edge cases where just the line won't work.
15
+ def match?(ast)
16
+ source_line == ast.line
17
+ end
18
+ end
19
+
20
+ class InstanceMethod < Method
21
+ def process_defn(*)
22
+ super.tap { |ast| @ast = ast if match?(ast) }
23
+ end
24
+ end
25
+
26
+ class SingletonMethod < Method
27
+ def process_defs(*)
28
+ super.tap { |ast| @ast = ast if match?(ast.body) }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,61 @@
1
+ require 'forwardable'
2
+
3
+ module Mutant
4
+ class Mutatee
5
+ extend Forwardable
6
+
7
+ attr_reader :implementation, :mutations
8
+
9
+ def initialize(implementation)
10
+ @implementation = implementation
11
+ @mutations = []
12
+ end
13
+
14
+ def_delegators :@implementation, :class_name, :method_name, :to_s
15
+
16
+ def clean
17
+ body.array.delete_if do |literal|
18
+ not Mutant::Literal.constants.map(&:to_sym).include?(literal.class.basename.to_sym)
19
+ end
20
+ end
21
+
22
+ def set_mutations
23
+ nodes.each do |node|
24
+ @mutations << Mutation.new(node, body.array)
25
+ end
26
+ end
27
+
28
+ def nodes
29
+ body.array.map {|item| Node.new(item) }
30
+ end
31
+
32
+ def mutations_remaining
33
+ @mutations.reject(&:mutated?)
34
+ end
35
+
36
+ def ast
37
+ @ast ||= rbx_method.parse_file && rbx_method.ast
38
+ end
39
+
40
+ def body
41
+ @body ||= rbx_method.is_a?(SingletonMethod) ? ast.body.body : ast.body
42
+
43
+ end
44
+
45
+ def rbx_method
46
+ @rbx_method ||=
47
+ case implementation.scope_type
48
+ when :singleton then SingletonMethod.new \
49
+ implementation.constant.method(method_name)
50
+ when :instance then InstanceMethod.new \
51
+ implementation.constant.instance_method(method_name)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def marshal(data)
58
+ Marshal.load(Marshal.dump(data))
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,24 @@
1
+ module Mutant
2
+ class Mutation
3
+ extend Forwardable
4
+
5
+ attr_reader :array
6
+
7
+ def initialize(node, array)
8
+ @node = node
9
+ @array = array
10
+ @mutated = false
11
+ end
12
+
13
+ def_delegators :@node, :line, :from, :to
14
+
15
+ def mutated?
16
+ @mutated
17
+ end
18
+
19
+ def mutate
20
+ @array[@array.index(@node.item)] = @node.swap
21
+ @mutated = true
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,49 @@
1
+ module Mutant
2
+ class Mutator
3
+ COMPILER = Rubinius::Compiler.new(:bytecode, :compiled_method)
4
+
5
+ def initialize(mutatee)
6
+ @mutatee = mutatee
7
+ end
8
+
9
+ def mutate
10
+ @mutatee.clean
11
+ @mutatee.set_mutations
12
+
13
+ if @mutatee.mutations.size.zero?
14
+ Reporter.no_mutations(@mutatee)
15
+ return
16
+ else
17
+ Reporter.method_loaded(@mutatee)
18
+ end
19
+
20
+ @mutatee.mutations.each do |mutation|
21
+ @mutatee.body.array = mutation.mutate && mutation.array
22
+ Reporter.mutating(mutation)
23
+ # setup the compiler
24
+ COMPILER.generator.input(root)
25
+ # compile the script and replace the original method
26
+ # with the mutated method
27
+ Rubinius.run_script(compiled_method)
28
+ end
29
+ end
30
+
31
+ def block
32
+ Rubinius::AST::Block.new(1, [ @mutatee.ast ])
33
+ end
34
+
35
+ def class_ast
36
+ Rubinius::AST::Class.new(1, @mutatee.class_name.to_sym, nil, block)
37
+ end
38
+
39
+ def root
40
+ Rubinius::AST::Script.new(class_ast).tap do |script|
41
+ script.file = @mutatee.rbx_method.source_file
42
+ end
43
+ end
44
+
45
+ def compiled_method
46
+ COMPILER.run.create_script.compiled_method
47
+ end
48
+ end
49
+ end