diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ce00b4..ececcb5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ jobs: uses: ruby/actions/.github/workflows/ruby_versions.yml@master with: engine: cruby - min_version: 2.5 + min_version: 3.1 test: needs: ruby-versions name: >- @@ -21,9 +21,6 @@ jobs: os: "ubuntu-latest" TEST_SYMLINK: yes rubyopt: "--enable-frozen-string-literal" - exclude: - - ruby-version: "2.5" - os: "macos-latest" runs-on: ${{ matrix.os }} env: TEST_SYMLINK: ${{ matrix.TEST_SYMLINK }} diff --git a/Gemfile b/Gemfile index 22399a2..992034e 100644 --- a/Gemfile +++ b/Gemfile @@ -12,7 +12,6 @@ group :development do gem 'test-unit' gem 'rake' gem 'simplecov' - gem 'bundler' gem 'irb', '>= 1.3.1' gem 'benchmark-ips' end diff --git a/README.md b/README.md index 0ff93db..c461c08 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,11 @@ Use following test frameworks or extensions instead. * [power_p](https://github.com/k-tsj/power_p) ## Requirement -* CRuby 2.5+ +* CRuby 3.1+ ## Configuration To colorize output messages, add require "power_assert/colorize" to your code. -(It requires CRuby 3.0.1+ or irb 1.3.1+) +(It requires irb 1.3.1+) ## Known Limitations * Expressions must be put in one line. Expressions with folded long lines produce nothing report, e.g.: @@ -61,23 +61,19 @@ assert do end ``` -* Returned values from accessor methods, method missing, or "super" produce nothing report, e.g: +* Returned values from method missing, or "super" produce nothing report, e.g: ```ruby class Foo - attr_accessor :val + def method_missing(*) + :foo + end end foo = Foo.new -foo.val = false - -assert do - # reported (only the value of "foo" and the literal "true") - foo.val == true -end assert do # won't be reported - foo.val + foo.foo end ``` diff --git a/Rakefile b/Rakefile index e82d34c..1e86b31 100644 --- a/Rakefile +++ b/Rakefile @@ -8,7 +8,6 @@ Rake::TestTask.new(:test) do |t| t.ruby_opts = ["-w", "-r#{helper_path}"] t.test_files = FileList["test/**/*_test.rb"].exclude do |i| begin - next false unless defined?(RubyVM) RubyVM::InstructionSequence.compile(File.read(i)) false rescue SyntaxError diff --git a/lib/power_assert.rb b/lib/power_assert.rb index 785b19c..8de209e 100644 --- a/lib/power_assert.rb +++ b/lib/power_assert.rb @@ -28,9 +28,7 @@ module PowerAssert class << self def start(assertion_proc_or_source, assertion_method: nil, source_binding: TOPLEVEL_BINDING) - if respond_to?(:clear_global_method_cache, true) - clear_global_method_cache - end + clear_global_method_cache yield Context.new(assertion_proc_or_source, assertion_method, source_binding) end @@ -51,13 +49,11 @@ def internal_file?(file) end end - if defined?(RubyVM) - CLEAR_CACHE_ISEQ = RubyVM::InstructionSequence.compile('using PowerAssert.const_get(:Empty)') - private_constant :CLEAR_CACHE_ISEQ + CLEAR_CACHE_ISEQ = RubyVM::InstructionSequence.compile('using PowerAssert.const_get(:Empty)') + private_constant :CLEAR_CACHE_ISEQ - def clear_global_method_cache - CLEAR_CACHE_ISEQ.eval - end + def clear_global_method_cache + CLEAR_CACHE_ISEQ.eval end end diff --git a/lib/power_assert/enable_tracepoint_events.rb b/lib/power_assert/enable_tracepoint_events.rb index b0c4101..bdccd66 100644 --- a/lib/power_assert/enable_tracepoint_events.rb +++ b/lib/power_assert/enable_tracepoint_events.rb @@ -1,67 +1,53 @@ require 'power_assert/configuration' -if defined?(RubyVM) - if PowerAssert.configuration._redefinition - module PowerAssert - # set redefined flag - basic_classes = [ - Integer, Float, String, Array, Hash, Symbol, Time, Regexp, NilClass, TrueClass, FalseClass - ] - - verbose = $VERBOSE - begin - $VERBOSE = nil - [:Fixnum, :Bignum].each do |c| - if Object.const_defined?(c) and (c = Object.const_get(c)) != Integer - basic_classes << c +if PowerAssert.configuration._redefinition + module PowerAssert + # set redefined flag + basic_classes = [ + Integer, Float, String, Array, Hash, Symbol, Time, Regexp, NilClass, TrueClass, FalseClass + ] + + basic_operators = [ + :+, :-, :*, :/, :%, :==, :===, :<, :<=, :<<, :[], :[]=, :length, :size, + :empty?, :nil?, :succ, :>, :>=, :!, :!=, :=~, :freeze, :-@, :max, :min, + # :call (it is just used for block call optimization) + :&, :|, + # :default (no specialized instruction for this) + :pack, :include?, + ] + + basic_classes.each do |klass| + basic_operators.each do |bop| + if klass.public_method_defined?(bop) + refine(klass) do + define_method(bop) {} end end - ensure - $VERBOSE = verbose end + end - basic_operators = [ - :+, :-, :*, :/, :%, :==, :===, :<, :<=, :<<, :[], :[]=, :length, :size, - :empty?, :nil?, :succ, :>, :>=, :!, :!=, :=~, :freeze, :-@, :max, :min, - # :call (it is just used for block call optimization) - :&, :|, - # :default (no specialized instruction for this) - :pack, :include?, - ] - - basic_classes.each do |klass| - basic_operators.each do |bop| - if klass.public_method_defined?(bop) - refine(klass) do - define_method(bop) {} - end - end - end + # bypass check_cfunc + refine BasicObject do + def ! end - # bypass check_cfunc - refine BasicObject do - def ! - end - - def == - end + def == end + end - refine Module do - def == - end + refine Module do + def == end + end - refine Class do - def new - end + refine Class do + def new end end end - - # disable optimization - RubyVM::InstructionSequence.compile_option = { - specialized_instruction: false - } end + +# disable optimization +RubyVM::InstructionSequence.compile_option = { + specialized_instruction: false +} diff --git a/lib/power_assert/parser.rb b/lib/power_assert/parser.rb index ff67200..c8ad988 100644 --- a/lib/power_assert/parser.rb +++ b/lib/power_assert/parser.rb @@ -33,16 +33,13 @@ def method_id_set private def valid_syntax?(str) - return true unless defined?(RubyVM) - begin - verbose, $VERBOSE = $VERBOSE, nil - RubyVM::InstructionSequence.compile(str) - true - rescue SyntaxError - false - ensure - $VERBOSE = verbose - end + verbose, $VERBOSE = $VERBOSE, nil + RubyVM::InstructionSequence.compile(str) + true + rescue SyntaxError + false + ensure + $VERBOSE = verbose end def slice_expression(str) @@ -68,113 +65,81 @@ class Branch < Array # +--------+ # def extract_idents(sexp) - tag, * = sexp - case tag - when :arg_paren, :assoc_splat, :fcall, :hash, :method_add_block, :string_literal, :return - extract_idents(sexp[1]) - when :assign, :massign - extract_idents(sexp[2]) - when :opassign - _, _, (_, op_name, (_, op_column)), s0 = sexp - extract_idents(s0) + [Ident[:method, op_name.sub(/=\z/, ''), op_column]] - when :dyna_symbol - if sexp[1][0].kind_of?(Symbol) - # sexp[1] can be [:string_content, [..]] while parsing { "a": 1 } - extract_idents(sexp[1]) - else - sexp[1].flat_map {|s| extract_idents(s) } - end - when :assoclist_from_args, :bare_assoc_hash, :paren, :string_embexpr, - :regexp_literal, :xstring_literal - sexp[1].flat_map {|s| extract_idents(s) } - when :command - [sexp[2], sexp[1]].flat_map {|s| extract_idents(s) } - when :assoc_new, :dot2, :dot3, :string_content - sexp[1..-1].flat_map {|s| extract_idents(s) } - when :unary - handle_columnless_ident([], sexp[1], extract_idents(sexp[2])) - when :binary - op = sexp[2] - if AND_OR_OPS.include?(op) - extract_idents(sexp[1]) + [Branch[extract_idents(sexp[3]), []]] - else - handle_columnless_ident(extract_idents(sexp[1]), op, extract_idents(sexp[3])) - end - when :call - _, recv, (op_sym, op_name, _), method = sexp + case sexp + in [:arg_paren | :assoc_splat | :fcall | :hash | :method_add_block | :string_literal | :return, s, *] + extract_idents(s) + in [:assign | :massign, _, s] + extract_idents(s) + in [:opassign, _, [_, op_name, [_, op_column]], s] + extract_idents(s) + [Ident[:method, op_name.sub(/=\z/, ''), op_column]] + in [:dyna_symbol, [Symbol, *] => s] + # s can be [:string_content, [..]] while parsing an expression like { "a": 1 } + extract_idents(s) + in [:dyna_symbol, ss] + ss.flat_map {|s| extract_idents(s) } + in [:assoclist_from_args | :bare_assoc_hash | :paren | :string_embexpr | :regexp_literal | :xstring_literal, ss, *] + ss.flat_map {|s| extract_idents(s) } + in [:command, s0, s1] + [s1, s0].flat_map {|s| extract_idents(s) } + in [:assoc_new | :dot2 | :dot3 | :string_content, *ss] + ss.flat_map {|s| extract_idents(s) } + in [:unary, mid, s] + handle_columnless_ident([], mid, extract_idents(s)) + in [:binary, s0, op, s1] if AND_OR_OPS.include?(op) + extract_idents(s0) + [Branch[extract_idents(s1), []]] + in [:binary, s0, op, s1] + handle_columnless_ident(extract_idents(s0), op, extract_idents(s1)) + in [:call, recv, [op_sym, op_name, _], method] with_safe_op = ((op_sym == :@op and op_name == '&.') or op_sym == :"&.") if method == :call handle_columnless_ident(extract_idents(recv), :call, [], with_safe_op) else extract_idents(recv) + (with_safe_op ? [Branch[extract_idents(method), []]] : extract_idents(method)) end - when :array - sexp[1] ? sexp[1].flat_map {|s| extract_idents(s) } : [] - when :command_call - [sexp[1], sexp[4], sexp[3]].flat_map {|s| extract_idents(s) } - when :aref - handle_columnless_ident(extract_idents(sexp[1]), :[], extract_idents(sexp[2])) - when :method_add_arg - idents = extract_idents(sexp[1]) - if idents.empty? - # idents may be empty(e.g. ->{}.()) - extract_idents(sexp[2]) - else - if idents[-1].kind_of?(Branch) and idents[-1][1].empty? - # Safe navigation operator is used. See :call clause also. - idents[0..-2] + [Branch[extract_idents(sexp[2]) + idents[-1][0], []]] - else - idents[0..-2] + extract_idents(sexp[2]) + [idents[-1]] - end - end - when :args_add_block - _, (tag, ss0, *ss1), _ = sexp - if tag == :args_add_star - (ss0 + ss1).flat_map {|s| extract_idents(s) } - else - sexp[1].flat_map {|s| extract_idents(s) } - end - when :vcall - _, (tag, name, (_, column)) = sexp - if tag == :@ident - [Ident[@proc_local_variables.include?(name) ? :ref : :method, name, column]] - else - [] - end - when :program - _, ((tag0, (tag1, (tag2, (tag3, mname, _)), _), (tag4, _, ss))) = sexp - if tag0 == :method_add_block and tag1 == :method_add_arg and tag2 == :fcall and - (tag3 == :@ident or tag3 == :@const) and mname == @assertion_method_name and (tag4 == :brace_block or tag4 == :do_block) - ss.flat_map {|s| extract_idents(s) } - else - _, (s0, *) = sexp - extract_idents(s0) + in [:array, ss] + ss ? ss.flat_map {|s| extract_idents(s) } : [] + in [:command_call, s0, _, s1, s2] + [s0, s2, s1].flat_map {|s| extract_idents(s) } + in [:aref, s0, s1] + handle_columnless_ident(extract_idents(s0), :[], extract_idents(s1)) + in [:method_add_arg, s0, s1] + case extract_idents(s0) + in [] + # idents(s0) may be empty(e.g. ->{}.()) + extract_idents(s1) + in [*is0, Branch[is1, []]] + # Safe navigation operator is used. See :call clause also. + is0 + [Branch[extract_idents(s1) + is1, []]] + in [*is, i] + is + extract_idents(s1) + [i] end - when :ifop - _, s0, s1, s2 = sexp + in [:args_add_block, [:args_add_star, ss0, *ss1], _] + (ss0 + ss1).flat_map {|s| extract_idents(s) } + in [:args_add_block, ss, _] + ss.flat_map {|s| extract_idents(s) } + in [:vcall, [:@ident, name, [_, column]]] + [Ident[@proc_local_variables.include?(name) ? :ref : :method, name, column]] + in [:vcall, _] + [] + in [:program, [[:method_add_block, [:method_add_arg, [:fcall, [:@ident | :@const, ^@assertion_method_name, _]], _], [:brace_block | :do_block, _, ss]]]] + ss.flat_map {|s| extract_idents(s) } + in [:program, [s, *]] + extract_idents(s) + in [:ifop, s0, s1, s2] [*extract_idents(s0), Branch[extract_idents(s1), extract_idents(s2)]] - when :if, :unless - _, s0, ss0, (_, ss1) = sexp - [*extract_idents(s0), Branch[ss0.flat_map {|s| extract_idents(s) }, ss1 ? ss1.flat_map {|s| extract_idents(s) } : []]] - when :if_mod, :unless_mod - _, s0, s1 = sexp + in [:if | :unless, s0, ss0, [_, ss1]] + [*extract_idents(s0), Branch[ss0.flat_map {|s| extract_idents(s) }, ss1.flat_map {|s| extract_idents(s) }]] + in [:if | :unless, s0, ss0, _] + [*extract_idents(s0), Branch[ss0.flat_map {|s| extract_idents(s) }, []]] + in [:if_mod | :unless_mod, s0, s1] [*extract_idents(s0), Branch[extract_idents(s1), []]] - when :var_ref, :var_field - _, (tag, ref_name, (_, column)) = sexp - case tag - when :@kw - if ref_name == 'self' - [Ident[:ref, 'self', column]] - else - [] - end - when :@ident, :@const, :@cvar, :@ivar, :@gvar - [Ident[:ref, ref_name, column]] - else - [] - end - when :@ident, :@const, :@op - _, method_name, (_, column) = sexp + in [:var_ref | :var_field, [:@kw, 'self', [_, column]]] + [Ident[:ref, 'self', column]] + in [:var_ref | :var_field, [:@ident | :@const | :@cvar | :@ivar | :@gvar, ref_name, [_, column]]] + [Ident[:ref, ref_name, column]] + in [:var_ref | :var_field, _] + [] + in [:@ident | :@const | :@op, method_name, [_, column]] [Ident[:method, method_name, column]] else [] diff --git a/test/block_test.rb b/test/block_test.rb index 1d8e16a..8a83dc3 100644 --- a/test/block_test.rb +++ b/test/block_test.rb @@ -1,4 +1,4 @@ -if defined?(RubyVM) and ! RubyVM::InstructionSequence.compile_option[:specialized_instruction] +if ! RubyVM::InstructionSequence.compile_option[:specialized_instruction] warn "#{__FILE__}: specialized_instruction is set to false" end @@ -285,13 +285,7 @@ def inspect; '#'; end end t do - older = < -END - newer = <'; end | 0 # END - assert_includes [older, newer], assertion_message { @obj.to_i.to_i.to_s } end diff --git a/test/dyna_symbol_key_test.rb b/test/dyna_symbol_key_test.rb index 6a21399..a855c3a 100644 --- a/test/dyna_symbol_key_test.rb +++ b/test/dyna_symbol_key_test.rb @@ -1,4 +1,4 @@ -if defined?(RubyVM) and ! RubyVM::InstructionSequence.compile_option[:specialized_instruction] +if ! RubyVM::InstructionSequence.compile_option[:specialized_instruction] warn "#{__FILE__}: specialized_instruction is set to false" end diff --git a/test/safe_op_test.rb b/test/safe_op_test.rb index 0fc573a..dee3e95 100644 --- a/test/safe_op_test.rb +++ b/test/safe_op_test.rb @@ -1,4 +1,4 @@ -if defined?(RubyVM) and ! RubyVM::InstructionSequence.compile_option[:specialized_instruction] +if ! RubyVM::InstructionSequence.compile_option[:specialized_instruction] warn "#{__FILE__}: specialized_instruction is set to false" end