mutiny 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rspec +3 -0
- data/.rubocop.yml +18 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +113 -0
- data/LICENSE.txt +22 -0
- data/README.md +16 -0
- data/RELEASES.md +8 -0
- data/Rakefile +23 -0
- data/TODO.md +55 -0
- data/bin/mutiny +35 -0
- data/examples/buggy_calculator/.rspec +2 -0
- data/examples/buggy_calculator/lib/calculator/max.rb +9 -0
- data/examples/buggy_calculator/lib/calculator.rb +4 -0
- data/examples/buggy_calculator/spec/calculator/max_spec.rb +17 -0
- data/examples/buggy_calculator/spec/spec_helper.rb +89 -0
- data/examples/calculator/.rspec +2 -0
- data/examples/calculator/lib/calculator/max.rb +9 -0
- data/examples/calculator/lib/calculator/min.rb +9 -0
- data/examples/calculator/lib/calculator.rb +5 -0
- data/examples/calculator/spec/calculator/max_spec.rb +17 -0
- data/examples/calculator/spec/calculator/min_spec.rb +17 -0
- data/examples/calculator/spec/spec_helper.rb +89 -0
- data/examples/untested_calculator/.rspec +2 -0
- data/examples/untested_calculator/lib/calculator/max.rb +9 -0
- data/examples/untested_calculator/lib/calculator.rb +4 -0
- data/examples/untested_calculator/spec/spec_helper.rb +89 -0
- data/lib/mutiny/configuration.rb +25 -0
- data/lib/mutiny/integration/rspec/context.rb +26 -0
- data/lib/mutiny/integration/rspec/parser.rb +36 -0
- data/lib/mutiny/integration/rspec/runner.rb +58 -0
- data/lib/mutiny/integration/rspec/test.rb +16 -0
- data/lib/mutiny/integration/rspec/test_set.rb +17 -0
- data/lib/mutiny/integration/rspec.rb +25 -0
- data/lib/mutiny/mode/check.rb +62 -0
- data/lib/mutiny/mode/mutate.rb +29 -0
- data/lib/mutiny/mode.rb +19 -0
- data/lib/mutiny/mutants/mutant.rb +32 -0
- data/lib/mutiny/mutants/mutant_set.rb +42 -0
- data/lib/mutiny/mutants/mutation/method/binary_arithmetic_operator_replacement.rb +15 -0
- data/lib/mutiny/mutants/mutation/method/conditional_operator_deletion.rb +19 -0
- data/lib/mutiny/mutants/mutation/method/conditional_operator_insertion.rb +25 -0
- data/lib/mutiny/mutants/mutation/method/conditional_operator_replacement.rb +19 -0
- data/lib/mutiny/mutants/mutation/method/helpers/infix_operator_replacement.rb +17 -0
- data/lib/mutiny/mutants/mutation/method/helpers/operator_replacement.rb +81 -0
- data/lib/mutiny/mutants/mutation/method/logical_operator_deletion.rb +19 -0
- data/lib/mutiny/mutants/mutation/method/logical_operator_insertion.rb +24 -0
- data/lib/mutiny/mutants/mutation/method/logical_operator_replacement.rb +15 -0
- data/lib/mutiny/mutants/mutation/method/relational_expression_replacement.rb +19 -0
- data/lib/mutiny/mutants/mutation/method/relational_operator_replacement.rb +15 -0
- data/lib/mutiny/mutants/mutation/method/shortcut_assignment_operator_replacement.rb +23 -0
- data/lib/mutiny/mutants/mutation/method/unary_arithmetic_operator_deletion.rb +29 -0
- data/lib/mutiny/mutants/mutation/method/unary_arithmetic_operator_insertion.rb +29 -0
- data/lib/mutiny/mutants/mutation.rb +10 -0
- data/lib/mutiny/mutants/mutation_set.rb +24 -0
- data/lib/mutiny/mutants/ruby.rb +42 -0
- data/lib/mutiny/pattern.rb +17 -0
- data/lib/mutiny/reporter/stdout.rb +9 -0
- data/lib/mutiny/subjects/environment/type.rb +54 -0
- data/lib/mutiny/subjects/environment.rb +25 -0
- data/lib/mutiny/subjects/subject.rb +32 -0
- data/lib/mutiny/subjects/subject_set.rb +17 -0
- data/lib/mutiny/subjects.rb +3 -0
- data/lib/mutiny/tests/test.rb +12 -0
- data/lib/mutiny/tests/test_run.rb +18 -0
- data/lib/mutiny/tests/test_set.rb +44 -0
- data/lib/mutiny/tests.rb +3 -0
- data/lib/mutiny/version.rb +3 -0
- data/lib/mutiny.rb +4 -0
- data/mutiny.gemspec +32 -0
- data/spec/integration/check_spec.rb +39 -0
- data/spec/integration/mutate_spec.rb +35 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/support/aruba.rb +12 -0
- data/spec/support/in_example_project.rb +17 -0
- data/spec/support/shared_examples/shared_examples_for_an_operator_replacement_mutation.rb +26 -0
- data/spec/unit/integration/rspec/parser_spec.rb +23 -0
- data/spec/unit/integration/rspec/runner_spec.rb +47 -0
- data/spec/unit/mutants/mutant_set_spec.rb +57 -0
- data/spec/unit/mutants/mutations/method/binary_operator_replacement_spec.rb +11 -0
- data/spec/unit/mutants/mutations/method/conditional_operator_deletion_spec.rb +17 -0
- data/spec/unit/mutants/mutations/method/conditional_operator_insertion_spec.rb +17 -0
- data/spec/unit/mutants/mutations/method/conditional_operator_replacement_spec.rb +16 -0
- data/spec/unit/mutants/mutations/method/logical_operator_deletion_spec.rb +17 -0
- data/spec/unit/mutants/mutations/method/logical_operator_insertion_spec.rb +17 -0
- data/spec/unit/mutants/mutations/method/logical_operator_replacement_spec.rb +11 -0
- data/spec/unit/mutants/mutations/method/relational_expression_replacement_spec.rb +18 -0
- data/spec/unit/mutants/mutations/method/relational_operator_replacement_spec.rb +11 -0
- data/spec/unit/mutants/mutations/method/shortcut_assignment_operator_replacement_spec.rb +12 -0
- data/spec/unit/mutants/mutations/method/unary_arithmetic_operator_deletion_spec.rb +21 -0
- data/spec/unit/mutants/mutations/method/unary_arithmetic_operator_insertion_spec.rb +21 -0
- data/spec/unit/pattern_spec.rb +28 -0
- data/spec/unit/subjects/environment/type_spec.rb +54 -0
- data/spec/unit/subjects/environment_spec.rb +71 -0
- data/spec/unit/subjects/subject_spec.rb +25 -0
- data/spec/unit/subjects/test_set_spec.rb +75 -0
- metadata +310 -0
@@ -0,0 +1,89 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
4
|
+
# this file to always be loaded, without a need to explicitly require it in any
|
5
|
+
# files.
|
6
|
+
#
|
7
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
8
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
9
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
10
|
+
# individual file that may not need all of that loaded. Instead, consider making
|
11
|
+
# a separate helper file that requires the additional dependencies and performs
|
12
|
+
# the additional setup, and require it from the spec files that actually need
|
13
|
+
# it.
|
14
|
+
#
|
15
|
+
# The `.rspec` file also contains a few flags that are not defaults but that
|
16
|
+
# users commonly want.
|
17
|
+
#
|
18
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
19
|
+
RSpec.configure do |config|
|
20
|
+
# rspec-expectations config goes here. You can use an alternate
|
21
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
22
|
+
# assertions if you prefer.
|
23
|
+
config.expect_with :rspec do |expectations|
|
24
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
25
|
+
# and `failure_message` of custom matchers include text for helper methods
|
26
|
+
# defined using `chain`, e.g.:
|
27
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
28
|
+
# # => "be bigger than 2 and smaller than 4"
|
29
|
+
# ...rather than:
|
30
|
+
# # => "be bigger than 2"
|
31
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
32
|
+
end
|
33
|
+
|
34
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
35
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
36
|
+
config.mock_with :rspec do |mocks|
|
37
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
38
|
+
# a real object. This is generally recommended, and will default to
|
39
|
+
# `true` in RSpec 4.
|
40
|
+
mocks.verify_partial_doubles = true
|
41
|
+
end
|
42
|
+
|
43
|
+
# The settings below are suggested to provide a good initial experience
|
44
|
+
# with RSpec, but feel free to customize to your heart's content.
|
45
|
+
# # These two settings work together to allow you to limit a spec run
|
46
|
+
# # to individual examples or groups you care about by tagging them with
|
47
|
+
# # `:focus` metadata. When nothing is tagged with `:focus`, all examples
|
48
|
+
# # get run.
|
49
|
+
# config.filter_run :focus
|
50
|
+
# config.run_all_when_everything_filtered = true
|
51
|
+
#
|
52
|
+
# # Limits the available syntax to the non-monkey patched syntax that is
|
53
|
+
# # recommended. For more details, see:
|
54
|
+
# # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
|
55
|
+
# # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
56
|
+
# # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
|
57
|
+
# config.disable_monkey_patching!
|
58
|
+
#
|
59
|
+
# # This setting enables warnings. It's recommended, but in some cases may
|
60
|
+
# # be too noisy due to issues in dependencies.
|
61
|
+
# config.warnings = true
|
62
|
+
#
|
63
|
+
# # Many RSpec users commonly either run the entire suite or an individual
|
64
|
+
# # file, and it's useful to allow more verbose output when running an
|
65
|
+
# # individual spec file.
|
66
|
+
# if config.files_to_run.one?
|
67
|
+
# # Use the documentation formatter for detailed output,
|
68
|
+
# # unless a formatter has already been configured
|
69
|
+
# # (e.g. via a command-line flag).
|
70
|
+
# config.default_formatter = 'doc'
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# # Print the 10 slowest examples and example groups at the
|
74
|
+
# # end of the spec run, to help surface which specs are running
|
75
|
+
# # particularly slow.
|
76
|
+
# config.profile_examples = 10
|
77
|
+
#
|
78
|
+
# # Run specs in random order to surface order dependencies. If you find an
|
79
|
+
# # order dependency and want to debug it, you can fix the order by providing
|
80
|
+
# # the seed, which is printed after each run.
|
81
|
+
# # --seed 1234
|
82
|
+
# config.order = :random
|
83
|
+
#
|
84
|
+
# # Seed global randomization in this process using the `--seed` CLI option.
|
85
|
+
# # Setting this allows you to use `--seed` to deterministically reproduce
|
86
|
+
# # test failures related to randomization by passing the same `--seed` value
|
87
|
+
# # as the one that triggered the failure.
|
88
|
+
# Kernel.srand config.seed
|
89
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative 'pattern'
|
2
|
+
require_relative 'reporter/stdout'
|
3
|
+
require_relative 'integration/rspec'
|
4
|
+
require_relative 'mutants/ruby'
|
5
|
+
|
6
|
+
module Mutiny
|
7
|
+
class Configuration
|
8
|
+
attr_reader :loads, :requires, :patterns, :reporter, :integration, :mutator
|
9
|
+
|
10
|
+
def initialize(loads: [], requires: [], patterns: [])
|
11
|
+
@loads = loads
|
12
|
+
@requires = requires
|
13
|
+
@patterns = patterns
|
14
|
+
@patterns.map!(&Pattern.method(:new))
|
15
|
+
|
16
|
+
@reporter = Reporter::Stdout.new
|
17
|
+
@integration = Integration::RSpec.new
|
18
|
+
@mutator = Mutants::Ruby.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_paths
|
22
|
+
loads.map(&File.method(:expand_path))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "rspec/core"
|
2
|
+
require "stringio"
|
3
|
+
|
4
|
+
module Mutiny
|
5
|
+
class Integration
|
6
|
+
class RSpec
|
7
|
+
# This code originally based on Markus Schirp's implementation of Mutant::Integration::Rspec
|
8
|
+
# https://github.com/mbj/mutant/blob/master/lib/mutant/integration/rspec.rb
|
9
|
+
class Context
|
10
|
+
# NB: the --fail-fast option can be used in order to find only the first failing test
|
11
|
+
# CLI_OPTIONS = %w(spec --fail-fast)
|
12
|
+
CLI_OPTIONS = %w(spec)
|
13
|
+
|
14
|
+
attr_reader :runner, :world, :configuration, :output
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@output = StringIO.new
|
18
|
+
@world = ::RSpec.world
|
19
|
+
@configuration = ::RSpec.configuration
|
20
|
+
@runner = ::RSpec::Core::Runner.new(::RSpec::Core::ConfigurationOptions.new(CLI_OPTIONS))
|
21
|
+
@runner.setup($stderr, @output)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative "test"
|
2
|
+
require_relative "test_set"
|
3
|
+
|
4
|
+
module Mutiny
|
5
|
+
class Integration
|
6
|
+
class RSpec
|
7
|
+
# This code originally based on Markus Schirp's implementation of Mutant::Integration::Rspec
|
8
|
+
# https://github.com/mbj/mutant/blob/master/lib/mutant/integration/rspec.rb
|
9
|
+
class Parser
|
10
|
+
EXPRESSION_DELIMITER = " "
|
11
|
+
|
12
|
+
def initialize(context = Context.new)
|
13
|
+
@world = context.world
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
TestSet.new(all_examples.map(&method(:parse_example)))
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def all_examples
|
23
|
+
@world.example_groups.flat_map(&:descendants).flat_map(&:examples)
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse_example(example)
|
27
|
+
metadata = example.metadata
|
28
|
+
location = metadata.fetch(:location)
|
29
|
+
expression = metadata.fetch(:full_description).split(EXPRESSION_DELIMITER, 2).first
|
30
|
+
|
31
|
+
Test.new(location: location, expression: expression, example: example)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Mutiny
|
4
|
+
class Integration
|
5
|
+
class RSpec
|
6
|
+
# This code originally based on Markus Schirp's implementation of Mutant::Integration::Rspec
|
7
|
+
# https://github.com/mbj/mutant/blob/master/lib/mutant/integration/rspec.rb
|
8
|
+
class Runner
|
9
|
+
extend Forwardable
|
10
|
+
def_delegators :@context, :world, :runner, :configuration, :output
|
11
|
+
|
12
|
+
def initialize(test_set, context = Context.new)
|
13
|
+
@test_set = test_set
|
14
|
+
@context = context
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
reset
|
19
|
+
prepare
|
20
|
+
run
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def reset
|
26
|
+
@failed_examples = []
|
27
|
+
end
|
28
|
+
|
29
|
+
def prepare
|
30
|
+
filter_examples
|
31
|
+
configuration.reporter.register_listener(self, :example_failed)
|
32
|
+
end
|
33
|
+
|
34
|
+
def run
|
35
|
+
start = Time.now
|
36
|
+
runner.run_specs(world.ordered_example_groups)
|
37
|
+
output.rewind
|
38
|
+
Tests::TestRun.new(
|
39
|
+
tests: @test_set,
|
40
|
+
failed_tests: @test_set.subset_for_examples(@failed_examples),
|
41
|
+
output: output.read,
|
42
|
+
runtime: Time.now - start
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def example_failed(notification)
|
47
|
+
@failed_examples << notification.example
|
48
|
+
end
|
49
|
+
|
50
|
+
def filter_examples
|
51
|
+
world.filtered_examples.each_value do |example|
|
52
|
+
example.keep_if(&@test_set.examples.method(:include?))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative "../../tests"
|
2
|
+
|
3
|
+
module Mutiny
|
4
|
+
class Integration
|
5
|
+
class RSpec
|
6
|
+
class TestSet < Tests::TestSet
|
7
|
+
def examples
|
8
|
+
@tests.map(&:example)
|
9
|
+
end
|
10
|
+
|
11
|
+
def subset_for_examples(examples)
|
12
|
+
subset { |test| examples.include?(test.example) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative "rspec/context"
|
2
|
+
require_relative "rspec/parser"
|
3
|
+
require_relative "rspec/runner"
|
4
|
+
|
5
|
+
module Mutiny
|
6
|
+
class Integration
|
7
|
+
# This code originally based on Markus Schirp's implementation of Mutant::Integration::Rspec
|
8
|
+
# https://github.com/mbj/mutant/blob/master/lib/mutant/integration/rspec.rb
|
9
|
+
class RSpec
|
10
|
+
def tests
|
11
|
+
Parser.new(context).call
|
12
|
+
end
|
13
|
+
|
14
|
+
def run(test_set)
|
15
|
+
Runner.new(test_set, context).call
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def context
|
21
|
+
@context ||= Context.new
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require_relative "../integration/rspec"
|
2
|
+
|
3
|
+
module Mutiny
|
4
|
+
class Mode
|
5
|
+
class Check < self
|
6
|
+
def run
|
7
|
+
report "Checking..."
|
8
|
+
|
9
|
+
if test_set.empty?
|
10
|
+
report_invalid
|
11
|
+
|
12
|
+
elsif test_run.passed?
|
13
|
+
report_valid
|
14
|
+
|
15
|
+
else
|
16
|
+
report_warning
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def report_invalid
|
23
|
+
report " No relevant tests found (for modules matching '#{pattern_string}')"
|
24
|
+
report "Either your mutiny configuration is wrong, or you're missing some tests!"
|
25
|
+
end
|
26
|
+
|
27
|
+
def report_warning
|
28
|
+
report " At least one relevant test found (#{test_set.size} in total)"
|
29
|
+
report " Not all relevant tests passed. The failing tests are:\n"
|
30
|
+
|
31
|
+
failed_test_locations.each do |location|
|
32
|
+
report " #{location}"
|
33
|
+
end
|
34
|
+
|
35
|
+
report ""
|
36
|
+
report "Looks ok, but note that mutiny is most effective when all tests pass."
|
37
|
+
end
|
38
|
+
|
39
|
+
def report_valid
|
40
|
+
report " At least one relevant test found (#{test_set.size} in total)"
|
41
|
+
report " All relevant tests passed"
|
42
|
+
report "Looks good!"
|
43
|
+
end
|
44
|
+
|
45
|
+
def pattern_string
|
46
|
+
configuration.patterns.join(',')
|
47
|
+
end
|
48
|
+
|
49
|
+
def failed_test_locations
|
50
|
+
test_run.failed_tests.locations
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_set
|
54
|
+
@test_set ||= configuration.integration.tests.for(environment.subjects)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_run
|
58
|
+
@test_run ||= configuration.integration.run(test_set)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Mutiny
|
2
|
+
class Mode
|
3
|
+
class Mutate < self
|
4
|
+
def run
|
5
|
+
report "Mutating..."
|
6
|
+
report_mutant_summary
|
7
|
+
store_mutants
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def report_mutant_summary
|
13
|
+
report "Generated #{mutant_set.size} mutants:"
|
14
|
+
mutant_set.group_by_subject.sort_by { |s, _| s.relative_path }.each do |subject, mutants|
|
15
|
+
report " * #{subject.relative_path} - #{mutants.size} mutants"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def store_mutants
|
20
|
+
mutant_set.store
|
21
|
+
report "Check the '.mutants' directory to browse the generated mutants."
|
22
|
+
end
|
23
|
+
|
24
|
+
def mutant_set
|
25
|
+
@mutant_set ||= configuration.mutator.mutants_for(environment.subjects)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/mutiny/mode.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative "mode/check"
|
2
|
+
require_relative "mode/mutate"
|
3
|
+
|
4
|
+
module Mutiny
|
5
|
+
class Mode
|
6
|
+
attr_reader :configuration, :environment
|
7
|
+
|
8
|
+
def initialize(configuration)
|
9
|
+
@configuration = configuration
|
10
|
+
@environment = Subjects::Environment.new(configuration)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def report(message)
|
16
|
+
configuration.reporter.report(message)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Mutiny
|
4
|
+
module Mutants
|
5
|
+
class Mutant
|
6
|
+
attr_reader :subject, :code
|
7
|
+
|
8
|
+
def initialize(subject:, code:)
|
9
|
+
@subject = subject
|
10
|
+
@code = code
|
11
|
+
end
|
12
|
+
|
13
|
+
def store(directory, index)
|
14
|
+
filename = subject.relative_path.sub(/\.rb$/, ".#{index}.rb")
|
15
|
+
path = File.join(directory, filename)
|
16
|
+
|
17
|
+
FileUtils.mkdir_p(File.dirname(path))
|
18
|
+
File.open(path, 'w') { |f| f.write(code) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def eql?(other)
|
22
|
+
is_a?(other.class) && other.subject == subject && other.code == code
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method "==", "eql?"
|
26
|
+
|
27
|
+
def hash
|
28
|
+
[subject, code].hash
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative "mutant"
|
2
|
+
|
3
|
+
module Mutiny
|
4
|
+
module Mutants
|
5
|
+
class MutantSet
|
6
|
+
def initialize
|
7
|
+
@mutants_by_subject = Hash.new([])
|
8
|
+
end
|
9
|
+
|
10
|
+
def add(subject, mutated_code)
|
11
|
+
mutants = mutated_code.map { |code| create_mutant(subject, code) }
|
12
|
+
@mutants_by_subject[subject] = @mutants_by_subject[subject] + mutants
|
13
|
+
end
|
14
|
+
|
15
|
+
def size
|
16
|
+
mutants.size
|
17
|
+
end
|
18
|
+
|
19
|
+
def group_by_subject
|
20
|
+
@mutants_by_subject.dup
|
21
|
+
end
|
22
|
+
|
23
|
+
def store(mutant_directory = ".mutants")
|
24
|
+
group_by_subject.each do |_, mutants|
|
25
|
+
mutants.each_with_index { |mutant, index| mutant.store(mutant_directory, index) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def create_mutant(subject, code)
|
32
|
+
Mutant.new(subject: subject, code: code)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def mutants
|
38
|
+
@mutants_by_subject.values.flatten
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative "helpers/infix_operator_replacement"
|
2
|
+
|
3
|
+
module Mutiny
|
4
|
+
module Mutants
|
5
|
+
class Mutation
|
6
|
+
module Method
|
7
|
+
class BinaryArithmeticOperatorReplacement < Helpers::InfixOperatorReplacement
|
8
|
+
def operator_names
|
9
|
+
%i(+ - * / %)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "mutiny/mutants/mutation"
|
2
|
+
|
3
|
+
module Mutiny
|
4
|
+
module Mutants
|
5
|
+
class Mutation
|
6
|
+
module Method
|
7
|
+
class ConditionalOperatorDeletion < Mutation
|
8
|
+
def pattern
|
9
|
+
builder.literal!(:send, builder.VAL, :!)
|
10
|
+
end
|
11
|
+
|
12
|
+
def replacement
|
13
|
+
builder.derivation!(:val)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "mutiny/mutants/mutation"
|
2
|
+
|
3
|
+
module Mutiny
|
4
|
+
module Mutants
|
5
|
+
class Mutation
|
6
|
+
module Method
|
7
|
+
class ConditionalOperatorInsertion < Mutation
|
8
|
+
def pattern
|
9
|
+
builder.either!(
|
10
|
+
builder.true,
|
11
|
+
builder.false,
|
12
|
+
builder.literal!(:send, nil, builder.VAL)
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def replacement
|
17
|
+
builder.derivation! :& do |root|
|
18
|
+
builder.literal!(:send, root, :!)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative "helpers/operator_replacement"
|
2
|
+
|
3
|
+
module Mutiny
|
4
|
+
module Mutants
|
5
|
+
class Mutation
|
6
|
+
module Method
|
7
|
+
class ConditionalOperatorReplacement < Helpers::OperatorReplacement
|
8
|
+
def infix_operator_names
|
9
|
+
%i(^)
|
10
|
+
end
|
11
|
+
|
12
|
+
def prefix_operator_names
|
13
|
+
%i(and or)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative "operator_replacement"
|
2
|
+
|
3
|
+
module Mutiny
|
4
|
+
module Mutants
|
5
|
+
class Mutation
|
6
|
+
module Method
|
7
|
+
module Helpers
|
8
|
+
class InfixOperatorReplacement < OperatorReplacement
|
9
|
+
def operators
|
10
|
+
operator_names.map { |name| InfixOperator.new(name) }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require "mutiny/mutants/mutation"
|
2
|
+
|
3
|
+
module Mutiny
|
4
|
+
module Mutants
|
5
|
+
class Mutation
|
6
|
+
module Method
|
7
|
+
module Helpers
|
8
|
+
class OperatorReplacement < Mutation
|
9
|
+
def pattern
|
10
|
+
builder.either!(
|
11
|
+
*operators.map { |ot| ot.build_pattern(builder) }
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
def replacement
|
16
|
+
builder.derivation! :left, :right, :& do |left, right, root|
|
17
|
+
builder.either!(*mutations_for(left, operator_name_from(root), right))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def operator_name_from(root)
|
24
|
+
# the operator is the root element when prefix (2 children)
|
25
|
+
# and is the middle child when infix (3 children)
|
26
|
+
root.children.size == 2 ? root : root.children[1]
|
27
|
+
end
|
28
|
+
|
29
|
+
def mutations_for(left, original_operator, right)
|
30
|
+
operators
|
31
|
+
.reject { |o| o.name == original_operator.name }
|
32
|
+
.map { |o| o.build_literal(builder, left, right) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def operators
|
36
|
+
infix_operator_names.map { |op| InfixOperator.new(op, infix_operator_root) } +
|
37
|
+
prefix_operator_names.map { |op| PrefixOperator.new(op) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def infix_operator_root
|
41
|
+
:send
|
42
|
+
end
|
43
|
+
|
44
|
+
class PrefixOperator
|
45
|
+
attr_reader :name
|
46
|
+
|
47
|
+
def initialize(name)
|
48
|
+
@name = name
|
49
|
+
end
|
50
|
+
|
51
|
+
def build_pattern(builder)
|
52
|
+
builder.literal!(name, builder.LEFT, builder.RIGHT)
|
53
|
+
end
|
54
|
+
|
55
|
+
def build_literal(builder, left, right)
|
56
|
+
builder.literal!(name, left, right)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class InfixOperator
|
61
|
+
attr_reader :name, :root
|
62
|
+
|
63
|
+
def initialize(name, root = :send)
|
64
|
+
@name = name
|
65
|
+
@root = root
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_pattern(builder)
|
69
|
+
builder.literal!(root, builder.LEFT, name, builder.RIGHT)
|
70
|
+
end
|
71
|
+
|
72
|
+
def build_literal(builder, left, right)
|
73
|
+
builder.literal!(root, left, builder.literal!(name), right)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "mutiny/mutants/mutation"
|
2
|
+
|
3
|
+
module Mutiny
|
4
|
+
module Mutants
|
5
|
+
class Mutation
|
6
|
+
module Method
|
7
|
+
class LogicalOperatorDeletion < Mutation
|
8
|
+
def pattern
|
9
|
+
builder.literal!(:send, builder.VAL, :~)
|
10
|
+
end
|
11
|
+
|
12
|
+
def replacement
|
13
|
+
builder.derivation!(:val)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|