mutiny 0.2.1 → 0.2.2
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.
- 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
|