diff --git a/Changelog.md b/Changelog.md index 755612dca..aea294f67 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,7 @@ +# v0.9.14 2020-10-16 + +* Add 2.7 syntax support. [#1062](https://github.com/mbj/mutant/pull/1062). + # v0.9.13 2020-10-07 * Improve isolation error reporting [#1055](https://github.com/mbj/mutant/pull/1055). diff --git a/Gemfile.lock b/Gemfile.lock index c9376708f..c2c21f41e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - mutant (0.9.13) + mutant (0.9.14) abstract_type (~> 0.0.7) adamantium (~> 0.2.0) anima (~> 0.3.1) @@ -14,7 +14,7 @@ PATH mprelude (~> 0.1.0) parser (~> 2.7.1) procto (~> 0.0.2) - unparser (~> 0.4.8) + unparser (~> 0.5.2) variable (~> 0.0.1) GEM @@ -78,7 +78,7 @@ GEM ruby-progressbar (1.10.1) thread_safe (0.3.6) unicode-display_width (1.6.1) - unparser (0.4.9) + unparser (0.5.2) abstract_type (~> 0.0.7) adamantium (~> 0.2.0) anima (~> 0.3.1) diff --git a/README.md b/README.md index 9031ee52a..2e9b9efdf 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Supported indicates if a specific Ruby version / Implementation is actively supp | -------------- | -------------- | ------- | ------------------ | ------------------ | ------------------ | | cRUBY/MRI | 2.5 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | cRUBY/MRI | 2.6 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| cRUBY/MRI | 2.7 | :heavy_check_mark: | :soon: | :soon: | :heavy_check_mark: | +| cRUBY/MRI | 2.7 | :heavy_check_mark: | :heavy_check_mark: | :soon: | :heavy_check_mark: | | jruby | TBD | :email: | :email: | :email: | :email: | | mruby | TBD | :email: | :email: | :email: | :email: | | cRUBY/MRI | < 2.5 | :no_entry: | :no_entry: | :no_entry: | :no_entry: | diff --git a/lib/mutant.rb b/lib/mutant.rb index 7b2133f71..41bb70ac4 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -90,8 +90,7 @@ module Mutant require 'mutant/mutator/node/begin' require 'mutant/mutator/node/binary' require 'mutant/mutator/node/const' -require 'mutant/mutator/node/dstr' -require 'mutant/mutator/node/dsym' +require 'mutant/mutator/node/dynamic_literal' require 'mutant/mutator/node/kwbegin' require 'mutant/mutator/node/named_value/access' require 'mutant/mutator/node/named_value/constant_assignment' diff --git a/lib/mutant/meta/example.rb b/lib/mutant/meta/example.rb index 96c568f98..40f957c50 100644 --- a/lib/mutant/meta/example.rb +++ b/lib/mutant/meta/example.rb @@ -3,7 +3,19 @@ module Mutant module Meta class Example - include Adamantium, Anima.new(:file, :original_source, :node, :types, :expected) + include Adamantium + include Anima.new( + :expected, + :file, + :lvars, + :node, + :original_source, + :types + ) + + class Expected + include Anima.new(:original_source, :node) + end # Verification instance for example # diff --git a/lib/mutant/meta/example/dsl.rb b/lib/mutant/meta/example/dsl.rb index 1d996fc96..09ccf2250 100644 --- a/lib/mutant/meta/example/dsl.rb +++ b/lib/mutant/meta/example/dsl.rb @@ -25,10 +25,11 @@ def self.call(file, types, block) # # @return [undefined] def initialize(file, types) + @expected = [] @file = file - @types = types + @lvars = [] @source = nil - @expected = [] + @types = types end # Example captured by DSL @@ -42,37 +43,37 @@ def example Example.new( expected: @expected, file: @file, + lvars: @lvars, node: @node, original_source: @source, types: @types ) end + # Declare a local variable + # + # @param [Symbol] + def declare_lvar(name) + @lvars << name + end + private - # rubocop:disable Metrics/MethodLength def source(input) fail 'source already defined' if @source - case input - when String - @source = input - @node = Unparser::Preprocessor.run(Unparser.parse(input)) - when ::Parser::AST::Node - @source = Unparser.unparse(input) - @node = input - else - fail "Unsupported input: #{input.inspect}" - end + @source = input + @node = node(input) end - # rubocop:enable Metrics/MethodLength def mutation(input) - node = node(input) - if @expected.include?(node) + expected = Expected.new(original_source: input, node: node(input)) + + if @expected.include?(expected) fail "Mutation for input: #{input.inspect} is already expected" end - @expected << node + + @expected << expected end def singleton_mutations @@ -83,14 +84,17 @@ def singleton_mutations def node(input) case input when String - Unparser::Preprocessor.run(Unparser.parse(input)) - when ::Parser::AST::Node - input + parser.parse(Unparser.buffer(input)) else - fail "Cannot coerce to node: #{input.inspect}" + fail "Unsupported input: #{input.inspect}" end end + def parser + Unparser.parser.tap do |parser| + @lvars.each(&parser.static_env.public_method(:declare)) + end + end end # DSL end # Example end # Meta diff --git a/lib/mutant/meta/example/verification.rb b/lib/mutant/meta/example/verification.rb index 80050e077..c400a3bc3 100644 --- a/lib/mutant/meta/example/verification.rb +++ b/lib/mutant/meta/example/verification.rb @@ -11,52 +11,94 @@ class Verification # # @return [Boolean] def success? - [missing, unexpected, no_diffs, invalid_syntax].all?(&:empty?) + [ + original_verification, + invalid, + missing, + no_diffs, + unexpected + ].all?(&:empty?) end memoize :success? - # Error report - # - # @return [String] def error_report - fail 'no error report on successful validation' if success? - - YAML.dump( - 'file' => example.file, - 'original_ast' => example.node.inspect, - 'original_source' => example.original_source, - 'missing' => format_mutations(missing), - 'unexpected' => format_mutations(unexpected), - 'invalid_syntax' => format_mutations(invalid_syntax), - 'no_diff' => no_diff_report - ) + reports.join("\n") end - memoize :error_report private + def reports + reports = [example.file] + reports.concat(original) + reports.concat(original_verification) + reports.concat(make_report('Missing mutations:', missing)) + reports.concat(make_report('Unexpected mutations:', unexpected)) + reports.concat(make_report('No-Diff mutations:', no_diffs)) + reports.concat(invalid) + end + + def make_report(label, mutations) + if mutations.any? + [label, mutations.map(&method(:report_mutation))] + else + [] + end + end + + def report_mutation(mutation) + [ + mutation.node.inspect, + mutation.source + ] + end + + def original + [ + 'Original:', + example.node, + example.original_source + ] + end + + def original_verification + validation = Unparser::Validation.from_string(example.original_source) + if validation.success? + [] + else + [ + prefix('[original]', validation.report) + ] + end + end + + def prefix(prefix, string) + string.each_line.map do |line| + "#{prefix} #{line}" + end.join + end + + def invalid + mutations.each_with_object([]) do |mutation, aggregate| + validation = Unparser::Validation.from_node(mutation.node) + aggregate << prefix('[invalid-mutation]', validation.report) unless validation.success? + end + end + memoize :invalid + def unexpected mutations.reject do |mutation| - example.expected.include?(mutation.node) + example.expected.any? { |expected| expected.node.eql?(mutation.node) } end end memoize :unexpected def missing - (example.expected - mutations.map(&:node)).map do |node| - Mutation::Evil.new(self, node) + (example.expected.map(&:node) - mutations.map(&:node)).map do |node| + Mutation::Evil.new(nil, node) end end memoize :missing - def invalid_syntax - mutations.reject do |mutation| - ::Parser::CurrentRuby.parse(mutation.source) - rescue ::Parser::SyntaxError # rubocop:disable Lint/SuppressedException - end - end - memoize :invalid_syntax - def no_diffs mutations.select { |mutation| mutation.source.eql?(example.original_source_generated) } end diff --git a/lib/mutant/mutator/node.rb b/lib/mutant/mutator/node.rb index 57131f7f2..e410736c9 100644 --- a/lib/mutant/mutator/node.rb +++ b/lib/mutant/mutator/node.rb @@ -75,7 +75,7 @@ def emit_self end def emit_nil - emit(N_NIL) unless left_assignment? + emit(N_NIL) unless left_op_assignment? end def parent_node @@ -86,7 +86,7 @@ def parent_type parent_node&.type end - def left_assignment? + def left_op_assignment? AST::Types::OP_ASSIGN.include?(parent_type) && parent.node.children.first.equal?(node) end diff --git a/lib/mutant/mutator/node/dsym.rb b/lib/mutant/mutator/node/dsym.rb deleted file mode 100644 index 015a572af..000000000 --- a/lib/mutant/mutator/node/dsym.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -module Mutant - class Mutator - class Node - - # Dsym mutator - class Dsym < Generic - - handle(:dsym) - - private - - def dispatch - super() - emit_singletons - end - - end # Dsym - end # Node - end # Mutator -end # Mutant diff --git a/lib/mutant/mutator/node/dstr.rb b/lib/mutant/mutator/node/dynamic_literal.rb similarity index 50% rename from lib/mutant/mutator/node/dstr.rb rename to lib/mutant/mutator/node/dynamic_literal.rb index 48e2a39f9..1effc7b81 100644 --- a/lib/mutant/mutator/node/dstr.rb +++ b/lib/mutant/mutator/node/dynamic_literal.rb @@ -3,17 +3,19 @@ module Mutant class Mutator class Node + # Mutator for dynamic literals + class DynamicLiteral < self - # Dstr mutator - class Dstr < Generic - - handle(:dstr) + handle(:dstr, :dsym) private def dispatch - super() emit_singletons + + children.each_index do |index| + mutate_child(index, &method(:n_begin?)) + end end end # Dstr diff --git a/lib/mutant/mutator/node/index.rb b/lib/mutant/mutator/node/index.rb index 2063fb242..9aefb58d4 100644 --- a/lib/mutant/mutator/node/index.rb +++ b/lib/mutant/mutator/node/index.rb @@ -24,7 +24,7 @@ def dispatch end def emit_send_forms - return if left_assignment? + return if left_op_assignment? SEND_REPLACEMENTS.each do |selector| emit(s(:send, receiver, selector, *indices)) @@ -43,7 +43,7 @@ def emit_drop_mutation def mutate_indices children_indices(index_range).each do |index| - emit_propagation(children.fetch(index)) unless left_assignment? + emit_propagation(children.fetch(index)) unless left_op_assignment? delete_child(index) mutate_child(index) end @@ -77,7 +77,7 @@ class Assign < self def dispatch super() - return if left_assignment? + return if left_op_assignment? emit_index_read emit(children.last) @@ -89,7 +89,7 @@ def emit_index_read end def index_range - if left_assignment? + if left_op_assignment? NO_VALUE_RANGE else REGULAR_RANGE diff --git a/lib/mutant/mutator/node/op_asgn.rb b/lib/mutant/mutator/node/op_asgn.rb index d6affbdd9..64c1ba733 100644 --- a/lib/mutant/mutator/node/op_asgn.rb +++ b/lib/mutant/mutator/node/op_asgn.rb @@ -15,10 +15,24 @@ class OpAsgn < self def dispatch emit_singletons + + left_mutations + + emit_right_mutations + end + + def left_mutations emit_left_mutations do |node| !n_self?(node) end - emit_right_mutations + + emit_left_promotion if n_send?(left) + end + + def emit_left_promotion + receiver = left.children.first + + emit_left(s(:ivasgn, *receiver)) if n_ivar?(receiver) end end # OpAsgn diff --git a/lib/mutant/mutator/node/send.rb b/lib/mutant/mutator/node/send.rb index 92889bddb..2d76ec0fe 100644 --- a/lib/mutant/mutator/node/send.rb +++ b/lib/mutant/mutator/node/send.rb @@ -159,7 +159,7 @@ def emit_selector_replacement end def emit_naked_receiver - emit(receiver) if receiver + emit(receiver) if receiver && !left_op_assignment? end def mutate_arguments diff --git a/lib/mutant/mutator/node/send/attribute_assignment.rb b/lib/mutant/mutator/node/send/attribute_assignment.rb index b60143846..adff72a98 100644 --- a/lib/mutant/mutator/node/send/attribute_assignment.rb +++ b/lib/mutant/mutator/node/send/attribute_assignment.rb @@ -8,6 +8,7 @@ class Send class AttributeAssignment < self ATTRIBUTE_RANGE = (0..-2).freeze + private_constant(*constants(false)) private diff --git a/lib/mutant/subject/method/instance.rb b/lib/mutant/subject/method/instance.rb index f19ebc7fe..af8e94f7e 100644 --- a/lib/mutant/subject/method/instance.rb +++ b/lib/mutant/subject/method/instance.rb @@ -42,7 +42,7 @@ def prepare private def wrap_node(mutant) - s(:begin, mutant, s(:send, nil, :memoize, s(:args, s(:sym, name), *options))) + s(:begin, mutant, s(:send, nil, :memoize, s(:sym, name), *options)) end # The optional AST node for adamantium memoization options diff --git a/lib/mutant/version.rb b/lib/mutant/version.rb index 294ca9011..c09de6987 100644 --- a/lib/mutant/version.rb +++ b/lib/mutant/version.rb @@ -2,5 +2,5 @@ module Mutant # Current mutant version - VERSION = '0.9.13' + VERSION = '0.9.14' end # Mutant diff --git a/meta/begin.rb b/meta/begin.rb index bd0f9c0bf..bc6e86cb0 100644 --- a/meta/begin.rb +++ b/meta/begin.rb @@ -13,8 +13,7 @@ end Mutant::Meta::Example.add :begin do + source '(true)' - source s(:begin, s(:true)) - # Mutation of each statement in block - mutation s(:begin, s(:false)) + mutation '(false)' end diff --git a/meta/dstr.rb b/meta/dstr.rb index 1c6054a32..d4dd076fa 100644 --- a/meta/dstr.rb +++ b/meta/dstr.rb @@ -4,10 +4,6 @@ source '"foo#{bar}baz"' singleton_mutations - mutation '"#{nil}#{bar}baz"' - mutation '"#{self}#{bar}baz"' - mutation '"foo#{bar}#{nil}"' - mutation '"foo#{bar}#{self}"' mutation '"foo#{nil}baz"' mutation '"foo#{self}baz"' end diff --git a/meta/dsym.rb b/meta/dsym.rb index 3582a9c1f..e779d6166 100644 --- a/meta/dsym.rb +++ b/meta/dsym.rb @@ -5,10 +5,6 @@ singleton_mutations - mutation ':"#{nil}#{bar}#{"baz"}"' - mutation ':"#{self}#{bar}#{"baz"}"' - mutation ':"#{"foo"}#{nil}#{"baz"}"' - mutation ':"#{"foo"}#{self}#{"baz"}"' - mutation ':"#{"foo"}#{bar}#{nil}"' - mutation ':"#{"foo"}#{bar}#{self}"' + mutation ':"foo#{nil}baz"' + mutation ':"foo#{self}baz"' end diff --git a/meta/float.rb b/meta/float.rb index 428e0d9cb..4550b6f2c 100644 --- a/meta/float.rb +++ b/meta/float.rb @@ -8,9 +8,9 @@ # edge cases mutation '0.0' mutation '1.0' - mutation '(0.0 / 0.0)' - mutation '(1.0 / 0.0)' - mutation '(-1.0 / 0.0)' + mutation '0.0 / 0.0' + mutation '1.0 / 0.0' + mutation '-1.0 / 0.0' # negative mutation '-10.0' @@ -21,9 +21,9 @@ singleton_mutations mutation '1.0' - mutation '(0.0 / 0.0)' - mutation '(1.0 / 0.0)' - mutation '(-1.0 / 0.0)' + mutation '0.0 / 0.0' + mutation '1.0 / 0.0' + mutation '-1.0 / 0.0' end Mutant::Meta::Example.add :float do @@ -31,7 +31,7 @@ singleton_mutations mutation '1.0' - mutation '(0.0 / 0.0)' - mutation '(1.0 / 0.0)' - mutation '(-1.0 / 0.0)' + mutation '0.0 / 0.0' + mutation '1.0 / 0.0' + mutation '-1.0 / 0.0' end diff --git a/meta/lvar.rb b/meta/lvar.rb index 158720382..2498f1de5 100644 --- a/meta/lvar.rb +++ b/meta/lvar.rb @@ -1,16 +1,10 @@ # frozen_string_literal: true Mutant::Meta::Example.add :lvar do - source 'a = nil; a' + declare_lvar :a - mutation 'a = nil; nil' - mutation 'a = nil; self' - mutation 'a = nil' - # TODO: fix invalid AST - # These ASTs are not valid and should NOT be emitted - # Mutations of lvarasgn need to be special cased to avoid this. - mutation s(:begin, s(:lvasgn, :a__mutant__, s(:nil)), s(:lvar, :a)) - mutation s(:begin, s(:nil), s(:lvar, :a)) - mutation s(:begin, s(:self), s(:lvar, :a)) - mutation s(:lvar, :a) + source 'a' + + mutation 'nil' + mutation 'self' end diff --git a/meta/lvasgn.rb b/meta/lvasgn.rb index 87ce862bd..d8bea4d23 100644 --- a/meta/lvasgn.rb +++ b/meta/lvasgn.rb @@ -9,7 +9,7 @@ end Mutant::Meta::Example.add :array, :lvasgn do - source 'a = *b' + source 'a = [*b]' singleton_mutations mutation 'a__mutant__ = *b' diff --git a/meta/op_assgn.rb b/meta/op_assgn.rb index a4d442b5d..a6250dfec 100644 --- a/meta/op_assgn.rb +++ b/meta/op_assgn.rb @@ -1,17 +1,42 @@ # frozen_string_literal: true -Mutant::Meta::Example.add :op_asgn do +Mutant::Meta::Example.add :op_asgn, :send do source '@a.b += 1' singleton_mutations - mutation 'a.b += 1' + + mutation '@a += 1' mutation '@a.b += -1' - mutation '@a.b += 2' mutation '@a.b += 0' + mutation '@a.b += 2' mutation '@a.b += nil' mutation '@a.b += self' + mutation 'a.b += 1' + mutation 'self.b += 1' +end + +Mutant::Meta::Example.add :op_asgn, :send do + source 'a.b += 1' + + singleton_mutations + + mutation 'a.b += -1' + mutation 'a.b += 0' + mutation 'a.b += 2' + mutation 'a.b += nil' + mutation 'a.b += self' mutation 'self.b += 1' - # TODO: fix invalid AST - # This should not get emitted as invalid AST with valid unparsed source - mutation s(:op_asgn, s(:ivar, :@a), :+, s(:int, 1)) +end + +Mutant::Meta::Example.add :op_asgn, :send do + source 'b += 1' + + singleton_mutations + + mutation 'b__mutant__ += 1' + mutation 'b += -1' + mutation 'b += 0' + mutation 'b += 2' + mutation 'b += nil' + mutation 'b += self' end diff --git a/meta/send.rb b/meta/send.rb index 30668fbb4..ca53411d5 100644 --- a/meta/send.rb +++ b/meta/send.rb @@ -514,16 +514,16 @@ singleton_mutations mutation 'foo' mutation '(left - right)' - mutation 'left / foo' - mutation 'right / foo' + mutation '(left) / foo' + mutation '(right) / foo' mutation '(left - right) / nil' mutation '(left - right) / self' mutation '(left - nil) / foo' mutation '(left - self) / foo' mutation '(nil - right) / foo' mutation '(self - right) / foo' - mutation 'nil / foo' - mutation 'self / foo' + mutation '(nil) / foo' + mutation '(self) / foo' end Mutant::Meta::Example.add :send do @@ -596,7 +596,7 @@ mutation '!self' mutation '!foo' mutation '!self&.!' - mutation '!(!foo)' + mutation '!!foo' end Mutant::Meta::Example.add :send do diff --git a/mutant.gemspec b/mutant.gemspec index d806c69ac..3f44bce79 100644 --- a/mutant.gemspec +++ b/mutant.gemspec @@ -32,7 +32,7 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency('mprelude', '~> 0.1.0') gem.add_runtime_dependency('parser', '~> 2.7.1') gem.add_runtime_dependency('procto', '~> 0.0.2') - gem.add_runtime_dependency('unparser', '~> 0.4.8') + gem.add_runtime_dependency('unparser', '~> 0.5.2') gem.add_runtime_dependency('variable', '~> 0.0.1') gem.add_development_dependency('parallel', '~> 1.3') diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 998308e6a..e8d828857 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -38,7 +38,7 @@ def generate(node) end def parse(string) - Unparser::Preprocessor.run(Unparser.parse(string)) + Unparser.parse(string) end def parse_expression(string) @@ -62,7 +62,7 @@ def undefined end # XSpecHelper RSpec.configuration.around(file_path: %r{spec/unit}) do |example| - Timeout.timeout(1, &example) + Timeout.timeout(2, &example) end RSpec.shared_examples_for 'a command method' do diff --git a/spec/support/ruby_vm.rb b/spec/support/ruby_vm.rb index c4a7a0faa..6f12fa24d 100644 --- a/spec/support/ruby_vm.rb +++ b/spec/support/ruby_vm.rb @@ -28,7 +28,7 @@ def initialize(attributes) def handle(vm, observation) # rubocop:disable Naming/MethodParameterName unless match?(observation) - fail "Unexpected event observation: #{observation.inspect}, expected #{inspect}" + fail "Unexpected event observation:\n#{observation.inspect}\nexpected:\n#{inspect}" end trigger_requires.each(&vm.method(:require)) diff --git a/spec/unit/mutant/meta/example/dsl_spec.rb b/spec/unit/mutant/meta/example/dsl_spec.rb index 9dda8a297..4ebbfeb96 100644 --- a/spec/unit/mutant/meta/example/dsl_spec.rb +++ b/spec/unit/mutant/meta/example/dsl_spec.rb @@ -4,15 +4,17 @@ describe '.call' do subject { described_class.call(file, types, block) } + let(:expected) { [] } let(:file) { 'foo.rb' } + let(:lvars) { [] } let(:node) { s(:false) } let(:types) { Set.new([node.type]) } - let(:expected) { [] } let(:expected_example) do Mutant::Meta::Example.new( expected: expected, file: file, + lvars: lvars, node: node, original_source: source, types: types @@ -39,14 +41,6 @@ def self.expect_error(message, &block) end end - context 'source as node' do - let(:source) { 'false' } - - expect_example do - source s(:false) - end - end - context 'source as string' do let(:source) { 'false' } @@ -55,19 +49,29 @@ def self.expect_error(message, &block) end end - context 'on node that needs unparser preprocessing to be normalized' do - let(:node) { s(:send, s(:float, -1.0), :/, s(:float, 0.0)) } - - let(:source) { '(-1.0) / 0.0' } + context 'using #declare lvar' do + let(:lvars) { %i[a] } + let(:node) { s(:lvar, :a) } + let(:source) { 'a' } expect_example do - source '(-1.0) / 0.0' + declare_lvar :a + + source 'a' end end context 'using #mutation' do - let(:source) { 'false' } - let(:expected) { [s(:nil)] } + let(:source) { 'false' } + + let(:expected) do + [ + Mutant::Meta::Example::Expected.new( + node: s(:nil), + original_source: 'nil' + ) + ] + end expect_example do source 'false' @@ -77,8 +81,20 @@ def self.expect_error(message, &block) end context 'using #singleton_mutations' do - let(:source) { 'false' } - let(:expected) { [s(:nil), s(:self)] } + let(:source) { 'false' } + + let(:expected) do + [ + Mutant::Meta::Example::Expected.new( + node: s(:nil), + original_source: 'nil' + ), + Mutant::Meta::Example::Expected.new( + node: s(:self), + original_source: 'self' + ) + ] + end expect_example do source 'false' diff --git a/spec/unit/mutant/meta/example/verification_spec.rb b/spec/unit/mutant/meta/example/verification_spec.rb index 81f480905..6289ddcd5 100644 --- a/spec/unit/mutant/meta/example/verification_spec.rb +++ b/spec/unit/mutant/meta/example/verification_spec.rb @@ -1,14 +1,16 @@ # frozen_string_literal: true RSpec.describe Mutant::Meta::Example::Verification do - let(:object) { described_class.new(example, mutations) } + let(:object) { described_class.new(example, mutations) } + let(:original_source) { 'true' } let(:example) do Mutant::Meta::Example.new( - expected: expected_nodes, + expected: expected, file: 'foo.rb', node: s(:true), - original_source: 'true', + lvars: [], + original_source: original_source, types: [:true] ) end @@ -20,136 +22,161 @@ end let(:generated_nodes) { [] } - let(:expected_nodes) { [] } + let(:expected) { [] } - describe '#success?' do - subject { object.success? } + def make_expected(input) + Mutant::Meta::Example::Expected.new( + node: Unparser.parse(input), + original_source: input + ) + end + + context 'when generated nodes equal expected nodes' do + it 'returns success' do + expect(object.success?).to be(true) + end + + it 'returns empty error report' do + expect(object.error_report).to eql(<<~'REPORT'.chomp) + foo.rb + Original: + (true) + true + REPORT + end + end + + shared_examples_for 'failure' do + it 'returns failure' do + expect(object.success?).to be(false) + end + + it 'returns expected error report' do + expect(object.error_report).to eql(expected_report) + end + end + + context 'when original source fails the unparser validation' do + let(:expected_report) do + <<~REPORT.chomp + foo.rb + Original: + (true) + true + [original] report + [original] lines + REPORT + end + + let(:validation) do + instance_double(Unparser::Validation, success?: false, report: "report\nlines") + end - context 'when generated nodes equal expected nodes' do - it { should be(true) } + before do + allow(Unparser::Validation).to receive_messages(from_string: validation) end - context 'when expected node is missing' do - let(:expected_nodes) { [s(:false)] } + include_examples 'failure' + end - it { should be(false) } + context 'when expected node is missing' do + let(:expected) { [make_expected('false'), make_expected('nil')] } + let(:generated_nodes) { [s(:false)] } + + let(:expected_report) do + <<~'REPORT'.chomp + foo.rb + Original: + (true) + true + Missing mutations: + s(:nil) + nil + REPORT end - context 'when there is extra generated node' do - let(:generated_nodes) { [s(:false)] } + include_examples 'failure' + end - it { should be(false) } + context 'when there is unexpected generated node' do + let(:expected) { [make_expected('false')] } + let(:generated_nodes) { [s(:false), s(:nil)] } + + let(:expected_report) do + <<~'REPORT'.chomp + foo.rb + Original: + (true) + true + Unexpected mutations: + s(:nil) + nil + REPORT end - context 'when there is no diff to original source' do - let(:expected_nodes) { [s(:true)] } - let(:generated_nodes) { [s(:true)] } + include_examples 'failure' + end - it { should be(false) } + context 'when mutation generates is no diff to original source' do + let(:expected) { [make_expected('true')] } + let(:generated_nodes) { [s(:true)] } + + let(:expected_report) do + <<~'REPORT'.chomp + foo.rb + Original: + (true) + true + No-Diff mutations: + s(:true) + true + REPORT end + + include_examples 'failure' end - describe '#error_report' do - subject { object.error_report } + context 'when the mutation is invalid' do + let(:invalid_node) do + s(:op_asgn, s(:send, s(:self), :at, s(:int, 1)), :+, s(:int, 1)) + end + + let(:generated_nodes) { [invalid_node] } - context 'on success' do - specify do - expect { subject }.to raise_error( - RuntimeError, - 'no error report on successful validation' + let(:expected) do + [ + Mutant::Meta::Example::Expected.new( + node: invalid_node, + original_source: 'self.at(1) += 1' ) - end - end - - context 'when expected node is missing' do - let(:expected_nodes) { [s(:false), s(:nil)] } - - specify do - should eql(<<~'REPORT') - --- - file: foo.rb - original_ast: s(:true) - original_source: 'true' - missing: - - node: s(:false) - source: 'false' - - node: s(:nil) - source: nil - unexpected: [] - invalid_syntax: [] - no_diff: [] - REPORT - end - end - - context 'when there is extra generated node' do - let(:generated_nodes) { [s(:false), s(:nil)] } - - specify do - should eql(<<~'REPORT') - --- - file: foo.rb - original_ast: s(:true) - original_source: 'true' - missing: [] - unexpected: - - node: s(:false) - source: 'false' - - node: s(:nil) - source: nil - invalid_syntax: [] - no_diff: [] - REPORT - end - end - - context 'when there is no diff to original source' do - let(:expected_nodes) { [s(:true)] } - let(:generated_nodes) { [s(:true)] } - - specify do - should eql(<<~'REPORT') - --- - file: foo.rb - original_ast: s(:true) - original_source: 'true' - missing: [] - unexpected: [] - invalid_syntax: [] - no_diff: - - node: s(:true) - source: 'true' - REPORT - end - end - - context 'when the generated node is invalid syntax after unparsed' do - let(:invalid_node) do - s(:op_asgn, s(:send, s(:self), :at, s(:int, 1)), :+, s(:int, 1)) - end - - let(:expected_nodes) { [invalid_node] } - let(:generated_nodes) { [invalid_node] } - - specify do - should eql(<<~'REPORT') - --- - file: foo.rb - original_ast: s(:true) - original_source: 'true' - missing: [] - unexpected: [] - invalid_syntax: - - node: |- - s(:op_asgn, - s(:send, - s(:self), :at, - s(:int, 1)), :+, - s(:int, 1)) - source: self.at(1) += 1 - no_diff: [] - REPORT - end + ] + end + + let(:expected_report) do + <<~REPORT.chomp + foo.rb + Original: + (true) + true + [invalid-mutation] report + [invalid-mutation] lines + REPORT + end + + let(:validation) do + instance_double(Unparser::Validation, success?: false, report: "report\nlines") + end + + before do + allow(Unparser::Validation).to receive_messages(from_node: validation) + end + + include_examples 'failure' + + it 'genrates validation with expected node' do + object.success? + + expect(Unparser::Validation).to have_received(:from_node).with(invalid_node) end end end diff --git a/spec/unit/mutant/meta/example_spec.rb b/spec/unit/mutant/meta/example_spec.rb index ebcba7150..85069f77b 100644 --- a/spec/unit/mutant/meta/example_spec.rb +++ b/spec/unit/mutant/meta/example_spec.rb @@ -3,11 +3,12 @@ RSpec.describe Mutant::Meta::Example do let(:object) do described_class.new( + expected: mutation_nodes, file: file, + lvars: [], node: node, - types: [node.type], original_source: 'true', - expected: mutation_nodes + types: [node.type] ) end diff --git a/spec/unit/mutant/subject/method/instance_spec.rb b/spec/unit/mutant/subject/method/instance_spec.rb index 836dcc385..ea13619b9 100644 --- a/spec/unit/mutant/subject/method/instance_spec.rb +++ b/spec/unit/mutant/subject/method/instance_spec.rb @@ -178,7 +178,7 @@ def foo; end end let(:memoize_node) do - s(:send, nil, :memoize, s(:args, s(:sym, :foo), *options_node)) + s(:send, nil, :memoize, s(:sym, :foo), *options_node) end let(:options_node) { nil } @@ -232,7 +232,7 @@ def foo; end context 'when Memoizable is included in scope' do include_context 'memoizable scope setup' - let(:source) { "def foo\nend\nmemoize(:foo)" } + let(:source) { "def foo\nend\nmemoize(:foo)\n" } it { should eql(source) } end @@ -241,7 +241,7 @@ def foo; end include_context 'adamantium scope setup' let(:source) do - "def foo\nend\nmemoize(:foo, { freezer: #{freezer_option.inspect} })" + "def foo\nend\nmemoize(:foo, freezer: #{freezer_option.inspect})\n" end { diff --git a/spec/unit/mutant/subject/method/metaclass_spec.rb b/spec/unit/mutant/subject/method/metaclass_spec.rb index 8cf5d4d13..7cfe80e84 100644 --- a/spec/unit/mutant/subject/method/metaclass_spec.rb +++ b/spec/unit/mutant/subject/method/metaclass_spec.rb @@ -58,6 +58,6 @@ def name describe '#source' do subject { object.source } - it { should eql("class << self\n def foo\n end\nend") } + it { should eql("class << self\n def foo\n end\nend\n") } end end diff --git a/spec/unit/mutant/zombifier_spec.rb b/spec/unit/mutant/zombifier_spec.rb index e14e251d5..ec6600623 100644 --- a/spec/unit/mutant/zombifier_spec.rb +++ b/spec/unit/mutant/zombifier_spec.rb @@ -39,7 +39,7 @@ MutantSpec::RubyVM::EventExpectation::Eval.new( expected_payload: { binding: TOPLEVEL_BINDING, - source: "module Zombie\n module Project\n end\nend", + source: "module Zombie\n module Project\n end\nend\n", source_location: 'a/project.rb' }, trigger_requires: %w[foo bar], @@ -61,7 +61,7 @@ MutantSpec::RubyVM::EventExpectation::Eval.new( expected_payload: { binding: TOPLEVEL_BINDING, - source: "module Zombie\n module Bar\n end\nend", + source: "module Zombie\n module Bar\n end\nend\n", source_location: 'b/bar.rb' }, trigger_requires: %w[],