mutiny 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -3
- data/RELEASES.md +5 -0
- data/bin/mutiny +4 -0
- data/lib/mutiny/analysis/analyser.rb +1 -1
- data/lib/mutiny/analysis/results.rb +1 -1
- data/lib/mutiny/configuration.rb +4 -0
- data/lib/mutiny/mode/score.rb +25 -5
- data/lib/mutiny/mutants/mutant.rb +6 -2
- data/lib/mutiny/mutants/mutant_set.rb +14 -2
- data/lib/mutiny/mutants/mutation/error.rb +10 -0
- data/lib/mutiny/mutants/mutation.rb +4 -0
- data/lib/mutiny/mutants/mutation_set.rb +12 -7
- data/lib/mutiny/subjects/environment/type.rb +5 -4
- data/lib/mutiny/subjects/subject_set.rb +5 -5
- data/lib/mutiny/version.rb +1 -1
- data/lib/mutiny.rb +1 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/unit/mutants/mutation_set_spec.rb +78 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd2e01d59dd079e28a3fa98029de8c55d4d5b705
|
4
|
+
data.tar.gz: 4b7b13dcbe3563b19d9bf7317f85b2fcebf0509b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8182f9ad54d073ca14bdbe0ce8d583e175551602aaa161fa3de5d1ee02f4bb7cd699f92f5a04ffdb1ea9abb07a80170e35e5045bdd604108353dd4aae84ecc10
|
7
|
+
data.tar.gz: 0bd7c7bcf5372e7fbafae2c824206957c3864e717c3aab0bf23458d5d7ec4fbade7446775dc552d27294aa8095ae9b9664d06561d33693f3e21f612d9de6b45d
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
mutiny (0.2.
|
4
|
+
mutiny (0.2.2)
|
5
5
|
gli (~> 2.13.0)
|
6
6
|
metamorpher (~> 0.2.2)
|
7
7
|
parser (~> 2.2.2)
|
@@ -45,7 +45,7 @@ GEM
|
|
45
45
|
ffi (1.9.10)
|
46
46
|
gherkin (2.12.2)
|
47
47
|
multi_json (~> 1.3)
|
48
|
-
gli (2.13.
|
48
|
+
gli (2.13.2)
|
49
49
|
ice_nine (0.11.1)
|
50
50
|
json (1.8.3)
|
51
51
|
memoizable (0.4.2)
|
@@ -110,4 +110,4 @@ DEPENDENCIES
|
|
110
110
|
rubocop (~> 0.33.0)
|
111
111
|
|
112
112
|
BUNDLED WITH
|
113
|
-
1.10.
|
113
|
+
1.10.6
|
data/RELEASES.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# Release History
|
2
2
|
|
3
|
+
## v0.2.2 (20 January 2016)
|
4
|
+
* Add rudimentary support for stillborn mutants
|
5
|
+
* Better error reporting during mutant generation
|
6
|
+
* Reduce chance that irrelevant subjects are selected for mutation
|
7
|
+
|
3
8
|
## v0.2.1 (20 August 2015)
|
4
9
|
* Fix [#1](https://github.com/mutiny/mutiny/issues/1) - "Uninitialized constant" exception for all commands
|
5
10
|
|
data/bin/mutiny
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
lib = File.expand_path('../../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
2
5
|
require "gli"
|
3
6
|
require "mutiny"
|
4
7
|
|
5
8
|
include GLI::App
|
6
9
|
|
10
|
+
version Mutiny::VERSION
|
7
11
|
program_desc "A tiny mutation testing framework"
|
8
12
|
|
9
13
|
flag [:l, :loads], type: Array, default_value: ['lib']
|
data/lib/mutiny/configuration.rb
CHANGED
data/lib/mutiny/mode/score.rb
CHANGED
@@ -26,11 +26,31 @@ module Mutiny
|
|
26
26
|
|
27
27
|
def summarise(mutant)
|
28
28
|
identifier = mutant.identifier
|
29
|
-
status =
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
status = status_for_mutant(mutant)
|
30
|
+
[identifier, status] + summarise_tests(mutant)
|
31
|
+
end
|
32
|
+
|
33
|
+
def status_for_mutant(mutant)
|
34
|
+
if mutant.stillborn?
|
35
|
+
"stillborn"
|
36
|
+
elsif results.survived?(mutant)
|
37
|
+
"survived"
|
38
|
+
else
|
39
|
+
"killed"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def summarise_tests(mutant)
|
44
|
+
if mutant.stillborn?
|
45
|
+
number_of_tests = "n/a"
|
46
|
+
runtime = "n/a"
|
47
|
+
else
|
48
|
+
executed_count = results.test_run_for(mutant).executed_count
|
49
|
+
total_count = results.test_run_for(mutant).tests.size
|
50
|
+
runtime = results.test_run_for(mutant).runtime
|
51
|
+
number_of_tests = "#{executed_count} (of #{total_count})"
|
52
|
+
end
|
53
|
+
[number_of_tests, runtime]
|
34
54
|
end
|
35
55
|
|
36
56
|
def results
|
@@ -3,20 +3,24 @@ require 'fileutils'
|
|
3
3
|
module Mutiny
|
4
4
|
module Mutants
|
5
5
|
class Mutant
|
6
|
-
attr_reader :subject, :code
|
6
|
+
attr_reader :subject, :code, :stillborn
|
7
|
+
alias_method :stillborn?, :stillborn
|
7
8
|
|
8
9
|
def initialize(subject: nil, code:)
|
9
10
|
@subject = subject
|
10
11
|
@code = code
|
12
|
+
@stillborn = false
|
11
13
|
end
|
12
14
|
|
13
15
|
def apply
|
14
|
-
# Evaluate the
|
16
|
+
# Evaluate the mutated code, overidding any existing version.
|
15
17
|
# We evaluate in the context of TOPLEVEL_BINDING as we want
|
16
18
|
# the unit to be evaluated in its usual namespace.
|
17
19
|
# rubocop:disable Eval
|
18
20
|
eval(code, TOPLEVEL_BINDING)
|
19
21
|
# rubocop:enable Eval
|
22
|
+
rescue
|
23
|
+
@stillborn = true
|
20
24
|
end
|
21
25
|
|
22
26
|
def eql?(other)
|
@@ -7,8 +7,10 @@ module Mutiny
|
|
7
7
|
extend Forwardable
|
8
8
|
def_delegators :mutants, :size, :<<, :concat
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
attr_reader :mutants
|
11
|
+
|
12
|
+
def initialize(*mutants)
|
13
|
+
@mutants = mutants
|
12
14
|
end
|
13
15
|
|
14
16
|
def group_by_subject
|
@@ -27,6 +29,16 @@ module Mutiny
|
|
27
29
|
ordered.each { |m| m.store(mutant_directory) }
|
28
30
|
end
|
29
31
|
|
32
|
+
def eql?(other)
|
33
|
+
other.mutants == mutants
|
34
|
+
end
|
35
|
+
|
36
|
+
alias_method "==", "eql?"
|
37
|
+
|
38
|
+
def hash
|
39
|
+
mutants.hash
|
40
|
+
end
|
41
|
+
|
30
42
|
class OrderedMutant < SimpleDelegator
|
31
43
|
def initialize(mutant, number)
|
32
44
|
super(mutant)
|
@@ -1,8 +1,11 @@
|
|
1
1
|
require_relative "mutant_set"
|
2
|
+
require_relative "mutation/error"
|
2
3
|
|
3
4
|
module Mutiny
|
4
5
|
module Mutants
|
5
6
|
class MutationSet
|
7
|
+
attr_reader :mutations
|
8
|
+
|
6
9
|
def initialize(*mutations)
|
7
10
|
@mutations = mutations
|
8
11
|
end
|
@@ -12,19 +15,21 @@ module Mutiny
|
|
12
15
|
# several mutation operators could be matched simulatenously during a single AST traversal
|
13
16
|
def mutate(subjects)
|
14
17
|
MutantSet.new.tap do |mutants|
|
15
|
-
|
16
|
-
|
17
|
-
mutated_codes = mutation.mutate_file(subject.path)
|
18
|
-
mutants.concat(create_mutants(subject, mutated_codes))
|
19
|
-
end
|
18
|
+
subjects.product(mutations).each do |subject, mutation|
|
19
|
+
mutants.concat(mutate_one(subject, mutation))
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
24
|
private
|
25
25
|
|
26
|
-
def
|
27
|
-
|
26
|
+
def mutate_one(subject, mutation)
|
27
|
+
mutation
|
28
|
+
.mutate_file(subject.path)
|
29
|
+
.map { |code| Mutant.new(subject: subject, code: code) }
|
30
|
+
rescue
|
31
|
+
msg = "Error encountered whilst mutating file at '#{subject.path}' with #{mutation.name}"
|
32
|
+
raise Mutation::Error, msg
|
28
33
|
end
|
29
34
|
end
|
30
35
|
end
|
@@ -28,25 +28,26 @@ module Mutiny
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def loadable?
|
31
|
-
!
|
31
|
+
!source_files.empty?
|
32
32
|
end
|
33
33
|
|
34
34
|
def load_path
|
35
35
|
configuration.load_paths.detect do |load_path|
|
36
|
-
|
36
|
+
source_files.any? { |locs| locs.start_with?(load_path) }
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
def
|
40
|
+
def source_files
|
41
41
|
mod.instance_methods(false)
|
42
42
|
.map { |method_name| mod.instance_method(method_name).source_location }
|
43
43
|
.reject(&:nil?)
|
44
44
|
.map(&:first)
|
45
|
+
.select { |source_file| configuration.can_load?(source_file) }
|
45
46
|
.uniq
|
46
47
|
end
|
47
48
|
|
48
49
|
def absolute_path
|
49
|
-
|
50
|
+
source_files.first
|
50
51
|
end
|
51
52
|
end
|
52
53
|
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
module Mutiny
|
2
2
|
module Subjects
|
3
3
|
class SubjectSet
|
4
|
+
include Enumerable
|
5
|
+
extend Forwardable
|
6
|
+
def_delegators :@subjects, :each, :product
|
7
|
+
|
4
8
|
def initialize(subjects)
|
5
9
|
@subjects = subjects
|
6
10
|
end
|
7
11
|
|
8
12
|
def names
|
9
|
-
@names ||=
|
10
|
-
end
|
11
|
-
|
12
|
-
def each(&block)
|
13
|
-
@subjects.each(&block)
|
13
|
+
@names ||= map(&:name).sort
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
data/lib/mutiny/version.rb
CHANGED
data/lib/mutiny.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -25,6 +25,12 @@ RSpec.configure do |config|
|
|
25
25
|
# --seed 1234
|
26
26
|
config.order = 'random'
|
27
27
|
|
28
|
+
# Ensure that mocks responds only to messages that the real object would also
|
29
|
+
# respond to
|
30
|
+
config.mock_with :rspec do |mocks|
|
31
|
+
mocks.verify_doubled_constant_names = true
|
32
|
+
end
|
33
|
+
|
28
34
|
# Isolate mutiny-under-test from its own test suite
|
29
35
|
config.around(:example) do |example|
|
30
36
|
RSpec::Core::Sandbox.sandboxed do
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Mutiny
|
2
|
+
module Mutants
|
3
|
+
describe MutationSet do
|
4
|
+
it "produces one mutant for each result of mutation.mutate_file" do
|
5
|
+
mutation_set = MutationSet.new(mutation_for(:code1, :code2, :code3))
|
6
|
+
subjects = subjects_for(:path1)
|
7
|
+
|
8
|
+
expected = MutantSet.new(
|
9
|
+
Mutant.new(subject: subjects.first, code: :code1),
|
10
|
+
Mutant.new(subject: subjects.first, code: :code2),
|
11
|
+
Mutant.new(subject: subjects.first, code: :code3)
|
12
|
+
)
|
13
|
+
|
14
|
+
expect(mutation_set.mutate(subjects)).to eq(expected)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "produces mutants from each mutation" do
|
18
|
+
mutation_set = MutationSet.new(mutation_for(:code1), mutation_for(:code2))
|
19
|
+
subjects = subjects_for(:path1)
|
20
|
+
|
21
|
+
expected = MutantSet.new(
|
22
|
+
Mutant.new(subject: subjects.first, code: :code1),
|
23
|
+
Mutant.new(subject: subjects.first, code: :code2)
|
24
|
+
)
|
25
|
+
|
26
|
+
expect(mutation_set.mutate(subjects)).to eq(expected)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "produces mutants for each subject" do
|
30
|
+
mutation_set = MutationSet.new(mutation_for(:code1, :code2), mutation_for(:code3))
|
31
|
+
subjects = subjects_for(:path1, :path2)
|
32
|
+
|
33
|
+
expected = MutantSet.new(
|
34
|
+
Mutant.new(subject: subjects.first, code: :code1),
|
35
|
+
Mutant.new(subject: subjects.first, code: :code2),
|
36
|
+
Mutant.new(subject: subjects.first, code: :code3),
|
37
|
+
Mutant.new(subject: subjects.last, code: :code1),
|
38
|
+
Mutant.new(subject: subjects.last, code: :code2),
|
39
|
+
Mutant.new(subject: subjects.last, code: :code3)
|
40
|
+
)
|
41
|
+
|
42
|
+
expect(mutation_set.mutate(subjects)).to eq(expected)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "produces a mutiny error when mutation fails" do
|
46
|
+
mutation = instance_double(Mutation)
|
47
|
+
allow(mutation).to receive(:name) { "BuggyMutation" }
|
48
|
+
allow(mutation).to receive(:mutate_file).and_raise("boom")
|
49
|
+
mutation_set = MutationSet.new(mutation)
|
50
|
+
subjects = subjects_for(:path1)
|
51
|
+
|
52
|
+
expect { mutation_set.mutate(subjects) }.to raise_error do |error|
|
53
|
+
expected_message = "Error encountered whilst mutating file at 'path1' with BuggyMutation"
|
54
|
+
|
55
|
+
expect(error).to be_a(Mutation::Error)
|
56
|
+
expect(error.message).to eq(expected_message)
|
57
|
+
expect(error.cause.message).to eq("boom")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def mutation_for(*codes)
|
62
|
+
instance_double(Mutation).tap do |mutation|
|
63
|
+
allow(mutation).to receive(:mutate_file) { codes }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def subjects_for(*paths)
|
68
|
+
paths.map { |path| subject_for(path) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def subject_for(path)
|
72
|
+
instance_double(Subjects::Subject).tap do |subject|
|
73
|
+
allow(subject).to receive(:path) { path }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mutiny
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Louis Rose
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parser
|
@@ -210,6 +210,7 @@ files:
|
|
210
210
|
- lib/mutiny/mutants/mutant.rb
|
211
211
|
- lib/mutiny/mutants/mutant_set.rb
|
212
212
|
- lib/mutiny/mutants/mutation.rb
|
213
|
+
- lib/mutiny/mutants/mutation/error.rb
|
213
214
|
- lib/mutiny/mutants/mutation/method/binary_arithmetic_operator_replacement.rb
|
214
215
|
- lib/mutiny/mutants/mutation/method/conditional_operator_deletion.rb
|
215
216
|
- lib/mutiny/mutants/mutation/method/conditional_operator_insertion.rb
|
@@ -252,6 +253,7 @@ files:
|
|
252
253
|
- spec/unit/isolation_spec.rb
|
253
254
|
- spec/unit/mutants/mutant_set_spec.rb
|
254
255
|
- spec/unit/mutants/mutant_spec.rb
|
256
|
+
- spec/unit/mutants/mutation_set_spec.rb
|
255
257
|
- spec/unit/mutants/mutations/method/binary_operator_replacement_spec.rb
|
256
258
|
- spec/unit/mutants/mutations/method/conditional_operator_deletion_spec.rb
|
257
259
|
- spec/unit/mutants/mutations/method/conditional_operator_insertion_spec.rb
|
@@ -306,6 +308,7 @@ test_files:
|
|
306
308
|
- spec/unit/isolation_spec.rb
|
307
309
|
- spec/unit/mutants/mutant_set_spec.rb
|
308
310
|
- spec/unit/mutants/mutant_spec.rb
|
311
|
+
- spec/unit/mutants/mutation_set_spec.rb
|
309
312
|
- spec/unit/mutants/mutations/method/binary_operator_replacement_spec.rb
|
310
313
|
- spec/unit/mutants/mutations/method/conditional_operator_deletion_spec.rb
|
311
314
|
- spec/unit/mutants/mutations/method/conditional_operator_insertion_spec.rb
|