mutant 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.rvmrc +1 -1
- data/.travis.yml +6 -0
- data/Readme.md +13 -0
- data/exe/mutate +6 -0
- data/lib/mutant.rb +21 -0
- data/lib/mutant/extensions.rb +8 -0
- data/lib/mutant/formatter.rb +19 -0
- data/lib/mutant/implementation.rb +70 -0
- data/lib/mutant/literal.rb +134 -0
- data/lib/mutant/method.rb +31 -0
- data/lib/mutant/mutatee.rb +61 -0
- data/lib/mutant/mutation.rb +24 -0
- data/lib/mutant/mutator.rb +49 -0
- data/lib/mutant/node.rb +26 -0
- data/lib/mutant/random.rb +37 -0
- data/lib/mutant/reporter.rb +38 -0
- data/lib/mutant/runners/rspec.rb +26 -4
- data/lib/mutant/version.rb +1 -1
- data/mutant.gemspec +5 -3
- data/spec/functional/class_spec.rb +46 -0
- data/spec/functional/instance_method/array_spec.rb +53 -0
- data/spec/functional/instance_method/boolean_spec.rb +101 -0
- data/spec/functional/instance_method/call_spec.rb +161 -0
- data/spec/functional/instance_method/fixnum_spec.rb +53 -0
- data/spec/functional/instance_method/float_spec.rb +53 -0
- data/spec/functional/instance_method/hash_spec.rb +53 -0
- data/spec/functional/instance_method/if_spec.rb +57 -0
- data/spec/functional/instance_method/range_spec.rb +53 -0
- data/spec/functional/instance_method/regex_spec.rb +55 -0
- data/spec/functional/instance_method/string_spec.rb +53 -0
- data/spec/functional/instance_method/symbol_spec.rb +53 -0
- data/spec/functional/reporter/method_loaded_spec.rb +62 -0
- data/spec/functional/reporter/running_mutations_spec.rb +60 -0
- data/spec/functional/runners/rspec_spec.rb +26 -0
- data/spec/functional/singleton_method/array_spec.rb +53 -0
- data/spec/functional/singleton_method/boolean_spec.rb +101 -0
- data/spec/functional/singleton_method/call_spec.rb +161 -0
- data/spec/functional/singleton_method/fixnum_spec.rb +53 -0
- data/spec/functional/singleton_method/float_spec.rb +53 -0
- data/spec/functional/singleton_method/hash_spec.rb +53 -0
- data/spec/functional/singleton_method/if_spec.rb +57 -0
- data/spec/functional/singleton_method/range_spec.rb +53 -0
- data/spec/functional/singleton_method/regex_spec.rb +55 -0
- data/spec/functional/singleton_method/string_spec.rb +53 -0
- data/spec/functional/singleton_method/symbol_spec.rb +53 -0
- data/spec/mutant/extensions_spec.rb +13 -0
- data/spec/mutant/implementation_spec.rb +223 -0
- data/spec/mutant/literal_spec.rb +129 -0
- data/spec/mutant/mutatee_spec.rb +28 -0
- data/spec/mutant/node_spec.rb +41 -0
- data/spec/mutant/random_spec.rb +33 -0
- data/spec/mutant/reporter_spec.rb +17 -0
- data/spec/mutant_spec.rb +28 -0
- data/spec/spec_helper.rb +11 -3
- data/spec/support/example_group_helpers.rb +11 -0
- data/spec/support/example_helpers.rb +5 -0
- metadata +149 -81
- data/spec/functional/boolean_spec.rb +0 -49
data/.gitignore
CHANGED
data/.rvmrc
CHANGED
@@ -1 +1 @@
|
|
1
|
-
rvm use rbx@mutant --create
|
1
|
+
rvm use rbx-head@mutant --create
|
data/.travis.yml
ADDED
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
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,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
|