mutiny 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +18 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +6 -0
  7. data/Gemfile +5 -0
  8. data/Gemfile.lock +113 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +16 -0
  11. data/RELEASES.md +8 -0
  12. data/Rakefile +23 -0
  13. data/TODO.md +55 -0
  14. data/bin/mutiny +35 -0
  15. data/examples/buggy_calculator/.rspec +2 -0
  16. data/examples/buggy_calculator/lib/calculator/max.rb +9 -0
  17. data/examples/buggy_calculator/lib/calculator.rb +4 -0
  18. data/examples/buggy_calculator/spec/calculator/max_spec.rb +17 -0
  19. data/examples/buggy_calculator/spec/spec_helper.rb +89 -0
  20. data/examples/calculator/.rspec +2 -0
  21. data/examples/calculator/lib/calculator/max.rb +9 -0
  22. data/examples/calculator/lib/calculator/min.rb +9 -0
  23. data/examples/calculator/lib/calculator.rb +5 -0
  24. data/examples/calculator/spec/calculator/max_spec.rb +17 -0
  25. data/examples/calculator/spec/calculator/min_spec.rb +17 -0
  26. data/examples/calculator/spec/spec_helper.rb +89 -0
  27. data/examples/untested_calculator/.rspec +2 -0
  28. data/examples/untested_calculator/lib/calculator/max.rb +9 -0
  29. data/examples/untested_calculator/lib/calculator.rb +4 -0
  30. data/examples/untested_calculator/spec/spec_helper.rb +89 -0
  31. data/lib/mutiny/configuration.rb +25 -0
  32. data/lib/mutiny/integration/rspec/context.rb +26 -0
  33. data/lib/mutiny/integration/rspec/parser.rb +36 -0
  34. data/lib/mutiny/integration/rspec/runner.rb +58 -0
  35. data/lib/mutiny/integration/rspec/test.rb +16 -0
  36. data/lib/mutiny/integration/rspec/test_set.rb +17 -0
  37. data/lib/mutiny/integration/rspec.rb +25 -0
  38. data/lib/mutiny/mode/check.rb +62 -0
  39. data/lib/mutiny/mode/mutate.rb +29 -0
  40. data/lib/mutiny/mode.rb +19 -0
  41. data/lib/mutiny/mutants/mutant.rb +32 -0
  42. data/lib/mutiny/mutants/mutant_set.rb +42 -0
  43. data/lib/mutiny/mutants/mutation/method/binary_arithmetic_operator_replacement.rb +15 -0
  44. data/lib/mutiny/mutants/mutation/method/conditional_operator_deletion.rb +19 -0
  45. data/lib/mutiny/mutants/mutation/method/conditional_operator_insertion.rb +25 -0
  46. data/lib/mutiny/mutants/mutation/method/conditional_operator_replacement.rb +19 -0
  47. data/lib/mutiny/mutants/mutation/method/helpers/infix_operator_replacement.rb +17 -0
  48. data/lib/mutiny/mutants/mutation/method/helpers/operator_replacement.rb +81 -0
  49. data/lib/mutiny/mutants/mutation/method/logical_operator_deletion.rb +19 -0
  50. data/lib/mutiny/mutants/mutation/method/logical_operator_insertion.rb +24 -0
  51. data/lib/mutiny/mutants/mutation/method/logical_operator_replacement.rb +15 -0
  52. data/lib/mutiny/mutants/mutation/method/relational_expression_replacement.rb +19 -0
  53. data/lib/mutiny/mutants/mutation/method/relational_operator_replacement.rb +15 -0
  54. data/lib/mutiny/mutants/mutation/method/shortcut_assignment_operator_replacement.rb +23 -0
  55. data/lib/mutiny/mutants/mutation/method/unary_arithmetic_operator_deletion.rb +29 -0
  56. data/lib/mutiny/mutants/mutation/method/unary_arithmetic_operator_insertion.rb +29 -0
  57. data/lib/mutiny/mutants/mutation.rb +10 -0
  58. data/lib/mutiny/mutants/mutation_set.rb +24 -0
  59. data/lib/mutiny/mutants/ruby.rb +42 -0
  60. data/lib/mutiny/pattern.rb +17 -0
  61. data/lib/mutiny/reporter/stdout.rb +9 -0
  62. data/lib/mutiny/subjects/environment/type.rb +54 -0
  63. data/lib/mutiny/subjects/environment.rb +25 -0
  64. data/lib/mutiny/subjects/subject.rb +32 -0
  65. data/lib/mutiny/subjects/subject_set.rb +17 -0
  66. data/lib/mutiny/subjects.rb +3 -0
  67. data/lib/mutiny/tests/test.rb +12 -0
  68. data/lib/mutiny/tests/test_run.rb +18 -0
  69. data/lib/mutiny/tests/test_set.rb +44 -0
  70. data/lib/mutiny/tests.rb +3 -0
  71. data/lib/mutiny/version.rb +3 -0
  72. data/lib/mutiny.rb +4 -0
  73. data/mutiny.gemspec +32 -0
  74. data/spec/integration/check_spec.rb +39 -0
  75. data/spec/integration/mutate_spec.rb +35 -0
  76. data/spec/spec_helper.rb +38 -0
  77. data/spec/support/aruba.rb +12 -0
  78. data/spec/support/in_example_project.rb +17 -0
  79. data/spec/support/shared_examples/shared_examples_for_an_operator_replacement_mutation.rb +26 -0
  80. data/spec/unit/integration/rspec/parser_spec.rb +23 -0
  81. data/spec/unit/integration/rspec/runner_spec.rb +47 -0
  82. data/spec/unit/mutants/mutant_set_spec.rb +57 -0
  83. data/spec/unit/mutants/mutations/method/binary_operator_replacement_spec.rb +11 -0
  84. data/spec/unit/mutants/mutations/method/conditional_operator_deletion_spec.rb +17 -0
  85. data/spec/unit/mutants/mutations/method/conditional_operator_insertion_spec.rb +17 -0
  86. data/spec/unit/mutants/mutations/method/conditional_operator_replacement_spec.rb +16 -0
  87. data/spec/unit/mutants/mutations/method/logical_operator_deletion_spec.rb +17 -0
  88. data/spec/unit/mutants/mutations/method/logical_operator_insertion_spec.rb +17 -0
  89. data/spec/unit/mutants/mutations/method/logical_operator_replacement_spec.rb +11 -0
  90. data/spec/unit/mutants/mutations/method/relational_expression_replacement_spec.rb +18 -0
  91. data/spec/unit/mutants/mutations/method/relational_operator_replacement_spec.rb +11 -0
  92. data/spec/unit/mutants/mutations/method/shortcut_assignment_operator_replacement_spec.rb +12 -0
  93. data/spec/unit/mutants/mutations/method/unary_arithmetic_operator_deletion_spec.rb +21 -0
  94. data/spec/unit/mutants/mutations/method/unary_arithmetic_operator_insertion_spec.rb +21 -0
  95. data/spec/unit/pattern_spec.rb +28 -0
  96. data/spec/unit/subjects/environment/type_spec.rb +54 -0
  97. data/spec/unit/subjects/environment_spec.rb +71 -0
  98. data/spec/unit/subjects/subject_spec.rb +25 -0
  99. data/spec/unit/subjects/test_set_spec.rb +75 -0
  100. 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,16 @@
1
+ require_relative "../../tests"
2
+
3
+ module Mutiny
4
+ class Integration
5
+ class RSpec
6
+ class Test < Tests::Test
7
+ attr_reader :example
8
+
9
+ def initialize(example:, **rest)
10
+ super(rest)
11
+ @example = example
12
+ end
13
+ end
14
+ end
15
+ end
16
+ 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
@@ -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