diff options
Diffstat (limited to 'test/ripper')
-rw-r--r-- | test/ripper/assert_parse_files.rb | 2 | ||||
-rw-r--r-- | test/ripper/dummyparser.rb | 53 | ||||
-rw-r--r-- | test/ripper/test_lexer.rb | 266 | ||||
-rw-r--r-- | test/ripper/test_parser_events.rb | 149 | ||||
-rw-r--r-- | test/ripper/test_ripper.rb | 39 | ||||
-rw-r--r-- | test/ripper/test_scanner_events.rb | 21 | ||||
-rw-r--r-- | test/ripper/test_sexp.rb | 28 |
7 files changed, 502 insertions, 56 deletions
diff --git a/test/ripper/assert_parse_files.rb b/test/ripper/assert_parse_files.rb index f59e028182..0d583a99e3 100644 --- a/test/ripper/assert_parse_files.rb +++ b/test/ripper/assert_parse_files.rb @@ -7,7 +7,7 @@ class TestRipper::Generic < Test::Unit::TestCase def assert_parse_files(dir, pattern = "**/*.rb", exclude: nil, gc_stress: GC.stress, test_ratio: nil) test_ratio ||= ENV["TEST_RIPPER_RATIO"]&.tap {|s|break s.to_f} || 0.05 # testing all files needs too long time... - assert_separately(%W[--disable-gem -rripper - #{SRCDIR}/#{dir} #{pattern}], + assert_separately(%W[-rripper - #{SRCDIR}/#{dir} #{pattern}], __FILE__, __LINE__, "#{<<-"begin;"}\n#{<<-'end;'}", timeout: Float::INFINITY) GC.stress = false pattern = "#{pattern}" diff --git a/test/ripper/dummyparser.rb b/test/ripper/dummyparser.rb index fa834bd0f7..ef5ea49b1f 100644 --- a/test/ripper/dummyparser.rb +++ b/test/ripper/dummyparser.rb @@ -4,8 +4,9 @@ # require 'ripper' +module TestRipper; end -class Node +class TestRipper::Node def initialize(name, *nodes) @name = name @children = nodes @@ -14,7 +15,7 @@ class Node attr_reader :name, :children def to_s - "#{@name}(#{Node.trim_nil(@children).map {|n| n.to_s }.join(',')})" + "#{@name}(#{TestRipper::Node.trim_nil(@children).map {|n| n.to_s }.join(',')})" end def self.trim_nil(list) @@ -36,7 +37,7 @@ class Node end end -class NodeList +class TestRipper::NodeList def initialize(list = []) @list = list end @@ -62,7 +63,7 @@ class NodeList end end -class DummyParser < Ripper +class TestRipper::DummyParser < Ripper def hook(*names) class << self; self; end.class_eval do names.each do |name| @@ -81,7 +82,7 @@ class DummyParser < Ripper end def on_stmts_new - NodeList.new + TestRipper::NodeList.new end def on_stmts_add(stmts, st) @@ -90,23 +91,23 @@ class DummyParser < Ripper end def on_void_stmt - Node.new('void') + TestRipper::Node.new('void') end def on_var_ref(name) - Node.new('ref', name) + TestRipper::Node.new('ref', name) end def on_var_alias(a, b) - Node.new('valias', a, b) + TestRipper::Node.new('valias', a, b) end def on_assign_error(mesg = nil, a) - Node.new('assign_error', a) + TestRipper::Node.new('assign_error', a) end def on_alias_error(mesg = nil, a) - Node.new('aliaserr', a) + TestRipper::Node.new('aliaserr', a) end def on_arg_paren(args) @@ -114,7 +115,7 @@ class DummyParser < Ripper end def on_args_new - NodeList.new + TestRipper::NodeList.new end def on_args_add(list, arg) @@ -156,7 +157,7 @@ class DummyParser < Ripper end def on_brace_block(params, code) - Node.new('block', params, code) + TestRipper::Node.new('block', params, code) end def on_block_var(params, shadow) @@ -176,7 +177,7 @@ class DummyParser < Ripper end def on_params(required, optional, rest, more, keyword, keyword_rest, block) - args = NodeList.new + args = TestRipper::NodeList.new required.each do |req| args.push(req) @@ -197,15 +198,15 @@ class DummyParser < Ripper end def on_assoc_new(a, b) - Node.new('assoc', a, b) + TestRipper::Node.new('assoc', a, b) end def on_bare_assoc_hash(assoc_list) - Node.new('assocs', *assoc_list) + TestRipper::Node.new('assocs', *assoc_list) end def on_assoclist_from_args(a) - Node.new('assocs', *a) + TestRipper::Node.new('assocs', *a) end def on_word_new @@ -217,7 +218,7 @@ class DummyParser < Ripper end def on_words_new - NodeList.new + TestRipper::NodeList.new end def on_words_add(words, word) @@ -225,7 +226,7 @@ class DummyParser < Ripper end def on_qwords_new - NodeList.new + TestRipper::NodeList.new end def on_qwords_add(words, word) @@ -233,27 +234,27 @@ class DummyParser < Ripper end def on_symbols_new - NodeList.new + TestRipper::NodeList.new end def on_symbols_add(symbols, symbol) - symbols.push Node::Sym.new(symbol) + symbols.push TestRipper::Node::Sym.new(symbol) end def on_qsymbols_new - NodeList.new + TestRipper::NodeList.new end def on_qsymbols_add(symbols, symbol) - symbols.push Node::Sym.new(symbol) + symbols.push TestRipper::Node::Sym.new(symbol) end def on_mlhs_new - NodeList.new + TestRipper::NodeList.new end def on_mlhs_paren(list) - Node.new(:mlhs, list) + TestRipper::Node.new(:mlhs, list) end def on_mlhs_add(list, node) @@ -277,12 +278,12 @@ class DummyParser < Ripper end def on_rescue(exc, *rest) - Node.new('rescue', (exc && NodeList.new(exc)), *rest) + TestRipper::Node.new('rescue', (exc && TestRipper::NodeList.new(exc)), *rest) end (Ripper::PARSER_EVENTS.map(&:to_s) - instance_methods(false).map {|n|n.to_s.sub(/^on_/, '')}).each do |event| define_method(:"on_#{event}") do |*args| - Node.new(event, *args) + TestRipper::Node.new(event, *args) end end end diff --git a/test/ripper/test_lexer.rb b/test/ripper/test_lexer.rb index 8e8a616627..4e8c0003db 100644 --- a/test/ripper/test_lexer.rb +++ b/test/ripper/test_lexer.rb @@ -171,7 +171,7 @@ class TestRipper::Lexer < Test::Unit::TestCase end BAD_CODE = [ - [:parse_error, 'def req(true) end', %r[unexpected `true'], 'true'], + [:parse_error, 'def req(true) end', %r[unexpected 'true'], 'true'], [:parse_error, 'def req(a, a) end', %r[duplicated argument name], 'a'], [:assign_error, 'begin; nil = 1; end', %r[assign to nil], 'nil'], [:alias_error, 'begin; alias $x $1; end', %r[number variables], '$1'], @@ -253,15 +253,265 @@ world" assert_equal(code, Ripper.tokenize(code).join(""), bug) end + InvalidHeredocInsideBlockParam = <<~CODE + a do |b + <<-C + C + | + end + CODE + def test_heredoc_inside_block_param bug = '[Bug #19399]' - code = <<~CODE - a do |b - <<-C - C - | - end - CODE + code = InvalidHeredocInsideBlockParam assert_equal(code, Ripper.tokenize(code).join(""), bug) end + + def test_heredoc_no_memory_leak + assert_no_memory_leak([], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true) + require "ripper" + source = "" #{InvalidHeredocInsideBlockParam.dump} + begin; + 400_000.times do + Ripper.new(source).parse + end + end; + end + + def test_heredoc_unterminated_interpolation + code = <<~'HEREDOC' + <<A+1 + #{ + HEREDOC + + assert_include(Ripper.tokenize(code).join(""), "+1") + end + + def test_nested_heredoc + code = <<~'HEREDOC' + <<~H1 + 1 + #{<<~H2} + 2 + H2 + 3 + H1 + HEREDOC + + expected = [ + [[1, 0], :on_heredoc_beg, "<<~H1", state(:EXPR_BEG)], + [[1, 5], :on_nl, "\n", state(:EXPR_BEG)], + [[2, 0], :on_ignored_sp, " ", state(:EXPR_BEG)], + [[2, 2], :on_tstring_content, "1\n", state(:EXPR_BEG)], + [[3, 0], :on_ignored_sp, " ", state(:EXPR_BEG)], + [[3, 2], :on_embexpr_beg, "\#{", state(:EXPR_BEG)], + [[3, 4], :on_heredoc_beg, "<<~H2", state(:EXPR_BEG)], + [[3, 9], :on_embexpr_end, "}", state(:EXPR_END)], + [[3, 10], :on_tstring_content, "\n", state(:EXPR_BEG)], + [[4, 0], :on_ignored_sp, " ", state(:EXPR_BEG)], + [[4, 4], :on_tstring_content, "2\n", state(:EXPR_BEG)], + [[5, 0], :on_heredoc_end, " H2\n", state(:EXPR_BEG)], + [[6, 0], :on_ignored_sp, " ", state(:EXPR_BEG)], + [[6, 2], :on_tstring_content, "3\n", state(:EXPR_BEG)], + [[7, 0], :on_heredoc_end, "H1\n", state(:EXPR_BEG)], + ] + + assert_lexer(expected, code) + + code = <<~'HEREDOC' + <<-H1 + 1 + #{<<~H2} + 2 + H2 + 3 + H1 + HEREDOC + + expected = [ + [[1, 0], :on_heredoc_beg, "<<-H1", state(:EXPR_BEG)], + [[1, 5], :on_nl, "\n", state(:EXPR_BEG)], + [[2, 0], :on_tstring_content, " 1\n ", state(:EXPR_BEG)], + [[3, 2], :on_embexpr_beg, "\#{", state(:EXPR_BEG)], + [[3, 4], :on_heredoc_beg, "<<~H2", state(:EXPR_BEG)], + [[3, 9], :on_embexpr_end, "}", state(:EXPR_END)], + [[3, 10], :on_tstring_content, "\n", state(:EXPR_BEG)], + [[4, 0], :on_ignored_sp, " ", state(:EXPR_BEG)], + [[4, 4], :on_tstring_content, "2\n", state(:EXPR_BEG)], + [[5, 0], :on_heredoc_end, " H2\n", state(:EXPR_BEG)], + [[6, 0], :on_tstring_content, " 3\n", state(:EXPR_BEG)], + [[7, 0], :on_heredoc_end, "H1\n", state(:EXPR_BEG)], + ] + + assert_lexer(expected, code) + end + + def test_invalid_escape_ctrl_mbchar + code = %["\\C-\u{3042}"] + expected = [ + [[1, 0], :on_tstring_beg, '"', state(:EXPR_BEG)], + [[1, 1], :on_tstring_content, "\\C-\u{3042}", state(:EXPR_BEG)], + [[1, 7], :on_tstring_end, '"', state(:EXPR_END)], + ] + + assert_lexer(expected, code) + end + + def test_invalid_escape_meta_mbchar + code = %["\\M-\u{3042}"] + expected = [ + [[1, 0], :on_tstring_beg, '"', state(:EXPR_BEG)], + [[1, 1], :on_tstring_content, "\\M-\u{3042}", state(:EXPR_BEG)], + [[1, 7], :on_tstring_end, '"', state(:EXPR_END)], + ] + + assert_lexer(expected, code) + end + + def test_invalid_escape_meta_ctrl_mbchar + code = %["\\M-\\C-\u{3042}"] + expected = [ + [[1, 0], :on_tstring_beg, '"', state(:EXPR_BEG)], + [[1, 1], :on_tstring_content, "\\M-\\C-\u{3042}", state(:EXPR_BEG)], + [[1, 10], :on_tstring_end, '"', state(:EXPR_END)], + ] + + assert_lexer(expected, code) + end + + def test_invalid_escape_ctrl_meta_mbchar + code = %["\\C-\\M-\u{3042}"] + expected = [ + [[1, 0], :on_tstring_beg, '"', state(:EXPR_BEG)], + [[1, 1], :on_tstring_content, "\\C-\\M-\u{3042}", state(:EXPR_BEG)], + [[1, 10], :on_tstring_end, '"', state(:EXPR_END)], + ] + + assert_lexer(expected, code) + end + + def test_invalid_escape_string + code = "\"hello\\x world" + expected = [ + [[1, 0], :on_tstring_beg, "\"", state(:EXPR_BEG)], + [[1, 1], :on_tstring_content, "hello", state(:EXPR_BEG)], + [[1, 5], :on_tstring_content, "\\x", state(:EXPR_BEG)], + [[1, 7], :on_tstring_content, " world", state(:EXPR_BEG)], + ] + + code = "\"\nhello\\x world" + expected = [ + [[1, 0], :on_tstring_beg, "\"", state(:EXPR_BEG)], + [[1, 1], :on_tstring_content, "\n" "hello", state(:EXPR_BEG)], + [[2, 5], :on_tstring_content, "\\x", state(:EXPR_BEG)], + [[2, 7], :on_tstring_content, " world", state(:EXPR_BEG)], + ] + assert_lexer(expected, code) + + code = "\"\n\\Cxx\"" + expected = [ + [[1, 0], :on_tstring_beg, "\"", state(:EXPR_BEG)], + [[1, 1], :on_tstring_content, "\n", state(:EXPR_BEG)], + [[2, 0], :on_tstring_content, "\\Cx", state(:EXPR_BEG)], + [[2, 3], :on_tstring_content, "x", state(:EXPR_BEG)], + [[2, 4], :on_tstring_end, "\"", state(:EXPR_END)], + ] + assert_lexer(expected, code) + + code = "\"\n\\Mxx\"" + expected = [ + [[1, 0], :on_tstring_beg, "\"", state(:EXPR_BEG)], + [[1, 1], :on_tstring_content, "\n", state(:EXPR_BEG)], + [[2, 0], :on_tstring_content, "\\Mx", state(:EXPR_BEG)], + [[2, 3], :on_tstring_content, "x", state(:EXPR_BEG)], + [[2, 4], :on_tstring_end, "\"", state(:EXPR_END)], + ] + assert_lexer(expected, code) + + code = "\"\n\\c\\cx\"" + expected = [ + [[1, 0], :on_tstring_beg, "\"", state(:EXPR_BEG)], + [[1, 1], :on_tstring_content, "\n", state(:EXPR_BEG)], + [[2, 0], :on_tstring_content, "\\c\\c", state(:EXPR_BEG)], + [[2, 4], :on_tstring_content, "x", state(:EXPR_BEG)], + [[2, 5], :on_tstring_end, "\"", state(:EXPR_END)], + ] + assert_lexer(expected, code) + + code = "\"\n\\ux\"" + expected = [ + [[1, 0], :on_tstring_beg, "\"", state(:EXPR_BEG)], + [[1, 1], :on_tstring_content, "\n", state(:EXPR_BEG)], + [[2, 0], :on_tstring_content, "\\u", state(:EXPR_BEG)], + [[2, 2], :on_tstring_content, "x", state(:EXPR_BEG)], + [[2, 3], :on_tstring_end, "\"", state(:EXPR_END)], + ] + assert_lexer(expected, code) + + code = "\"\n\\xx\"" + expected = [ + [[1, 0], :on_tstring_beg, "\"", state(:EXPR_BEG)], + [[1, 1], :on_tstring_content, "\n", state(:EXPR_BEG)], + [[2, 0], :on_tstring_content, "\\x", state(:EXPR_BEG)], + [[2, 2], :on_tstring_content, "x", state(:EXPR_BEG)], + [[2, 3], :on_tstring_end, "\"", state(:EXPR_END)], + ] + assert_lexer(expected, code) + + code = "<<A\n\n\\xyz" + expected = [ + [[1, 0], :on_heredoc_beg, "<<A", state(:EXPR_BEG)], + [[1, 3], :on_nl, "\n", state(:EXPR_BEG)], + [[2, 0], :on_tstring_content, "\n", state(:EXPR_BEG)], + [[3, 0], :on_tstring_content, "\\x", state(:EXPR_BEG)], + [[3, 2], :on_tstring_content, "yz", state(:EXPR_BEG)], + ] + assert_lexer(expected, code) + + code = "%(\n\\xyz)" + expected = [ + [[1, 0], :on_tstring_beg, "%(", state(:EXPR_BEG)], + [[1, 2], :on_tstring_content, "\n", state(:EXPR_BEG)], + [[2, 0], :on_tstring_content, "\\x", state(:EXPR_BEG)], + [[2, 2], :on_tstring_content, "yz", state(:EXPR_BEG)], + [[2, 4], :on_tstring_end, ")", state(:EXPR_END)], + ] + assert_lexer(expected, code) + + code = "%Q(\n\\xyz)" + expected = [ + [[1, 0], :on_tstring_beg, "%Q(", state(:EXPR_BEG)], + [[1, 3], :on_tstring_content, "\n", state(:EXPR_BEG)], + [[2, 0], :on_tstring_content, "\\x", state(:EXPR_BEG)], + [[2, 2], :on_tstring_content, "yz", state(:EXPR_BEG)], + [[2, 4], :on_tstring_end, ")", state(:EXPR_END)], + ] + assert_lexer(expected, code) + + code = ":\"\n\\xyz\"" + expected = [ + [[1, 0], :on_symbeg, ":\"", state(:EXPR_FNAME)], + [[1, 2], :on_tstring_content, "\n", state(:EXPR_FNAME)], + [[2, 0], :on_tstring_content, "\\x", state(:EXPR_FNAME)], + [[2, 2], :on_tstring_content, "yz", state(:EXPR_FNAME)], + [[2, 4], :on_tstring_end, "\"", state(:EXPR_END)], + ] + assert_lexer(expected, code) + end + + def test_spaces_at_eof + code = "1\n\t \t" + expected = [ + [[1, 0], :on_int, "1", state(:EXPR_END)], + [[1, 1], :on_nl, "\n", state(:EXPR_BEG)], + [[2, 0], :on_sp, "\t \t", state(:EXPR_END)], + ] + assert_lexer(expected, code) + end + + def assert_lexer(expected, code) + assert_equal(code, Ripper.tokenize(code).join("")) + assert_equal(expected, result = Ripper.lex(code), + proc {expected.zip(result) {|e, r| break diff(e, r) unless e == r}}) + end end diff --git a/test/ripper/test_parser_events.rb b/test/ripper/test_parser_events.rb index 1ea8d23378..aa7434c083 100644 --- a/test/ripper/test_parser_events.rb +++ b/test/ripper/test_parser_events.rb @@ -16,7 +16,7 @@ class TestRipper::ParserEvents < Test::Unit::TestCase end def parse(str, nm = nil, &bl) - dp = DummyParser.new(str) + dp = TestRipper::DummyParser.new(str) dp.hook(*nm, &bl) if nm dp.parse.to_s end @@ -152,6 +152,7 @@ class TestRipper::ParserEvents < Test::Unit::TestCase thru_args_forward = false parse(code, :on_args_forward) {thru_args_forward = true} assert_equal true, thru_args_forward, "no args_forward for: #{code}" + parse(code, :on_params) {|*, block| assert_nil(block)} end end @@ -266,17 +267,29 @@ class TestRipper::ParserEvents < Test::Unit::TestCase end def test_assign_error_backref - thru_assign_error = false + errors = [] result = - parse('$` = 1', :on_assign_error) {thru_assign_error = true} - assert_equal true, thru_assign_error - assert_equal '[assign(assign_error(var_field($`)),1)]', result + parse('$& = 1', %i[on_assign_error compile_error]) {|e, *| errors << e} + assert_equal %i[on_assign_error], errors + assert_equal '[assign(assign_error(var_field($&)),1)]', result - thru_assign_error = false + errors = [] result = - parse('$`, _ = 1', :on_assign_error) {thru_assign_error = true} - assert_equal true, thru_assign_error - assert_equal '[massign([assign_error(var_field($`)),var_field(_)],1)]', result + parse('$&, _ = 1', %i[on_assign_error compile_error]) {|e, *| errors << e} + assert_equal %i[on_assign_error], errors + assert_equal '[massign([assign_error(var_field($&)),var_field(_)],1)]', result + + errors = [] + result = + parse('$& += 1', %i[on_assign_error compile_error]) {|e, *| errors << e} + assert_equal %i[on_assign_error], errors + assert_equal '[assign_error(opassign(var_field($&),+=,1))]', result + + errors = [] + result = + parse('$& += cmd 1, 2', %i[on_assign_error compile_error]) {|e, *| errors << e} + assert_equal %i[on_assign_error], errors + assert_equal '[assign_error(opassign(var_field($&),+=,command(cmd,[1,2])))]', result end def test_assign_error_const_qualified @@ -499,6 +512,23 @@ class TestRipper::ParserEvents < Test::Unit::TestCase assert_equal "[call(ref(self),&.,foo,[])]", tree end + def test_call_colon2 + hook = Module.new do + def on_op(op) + super("(op: #{op.inspect})") + end + def on_call(recv, name, *args) + super(recv, "(method: #{name})", *args) + end + def on_ident(name) + super("(ident: #{name.inspect})") + end + end + + parser = TestRipper::DummyParser.new("a::b").extend(hook) + assert_equal '[call(vcall((ident: "a")),(method: (op: "::")),(ident: "b"))]', parser.parse.to_s + end + def test_excessed_comma thru_excessed_comma = false parse("proc{|x,|}", :on_excessed_comma) {thru_excessed_comma = true} @@ -1626,20 +1656,20 @@ class TestRipper::ParserEvents < Test::Unit::TestCase end def test_invalid_instance_variable_name - assert_equal("`@1' is not allowed as an instance variable name", compile_error('proc{@1}')) - assert_equal("`@' without identifiers is not allowed as an instance variable name", compile_error('@%')) - assert_equal("`@' without identifiers is not allowed as an instance variable name", compile_error('@')) + assert_equal("'@1' is not allowed as an instance variable name", compile_error('proc{@1}')) + assert_equal("'@' without identifiers is not allowed as an instance variable name", compile_error('@%')) + assert_equal("'@' without identifiers is not allowed as an instance variable name", compile_error('@')) end def test_invalid_class_variable_name - assert_equal("`@@1' is not allowed as a class variable name", compile_error('@@1')) - assert_equal("`@@' without identifiers is not allowed as a class variable name", compile_error('@@%')) - assert_equal("`@@' without identifiers is not allowed as a class variable name", compile_error('@@')) + assert_equal("'@@1' is not allowed as a class variable name", compile_error('@@1')) + assert_equal("'@@' without identifiers is not allowed as a class variable name", compile_error('@@%')) + assert_equal("'@@' without identifiers is not allowed as a class variable name", compile_error('@@')) end def test_invalid_global_variable_name - assert_equal("`$%' is not allowed as a global variable name", compile_error('$%')) - assert_equal("`$' without identifiers is not allowed as a global variable name", compile_error('$')) + assert_equal("'$%' is not allowed as a global variable name", compile_error('$%')) + assert_equal("'$' without identifiers is not allowed as a global variable name", compile_error('$')) end def test_warning_ignored_magic_comment @@ -1654,6 +1684,26 @@ class TestRipper::ParserEvents < Test::Unit::TestCase assert_equal(%w"frozen_string_literal nottrue", args) end + def test_warning_duplicated_when_clause + fmt, *args = warning(<<~STR) + a = 1 + case a + when 1 + when 1 + when 2 + else + end + STR + assert_match(/duplicates 'when' clause/, fmt) + assert_equal([4, 3], args) + end + + def test_warn_duplicated_hash_keys + fmt, *args = warn("{ a: 1, a: 2 }") + assert_match(/is duplicated and overwritten on line/, fmt) + assert_equal([:a, 1], args) + end + def test_warn_cr_in_middle fmt = nil assert_warn("") {fmt, = warn("\r;")} @@ -1693,4 +1743,69 @@ class TestRipper::ParserEvents < Test::Unit::TestCase parse('case 0; in {a:}; end', :on_hshptn) {thru_hshptn = true} assert_equal true, thru_hshptn end + + def test_return_out_of_compile_error_no_memory_leak + assert_no_memory_leak(%w(-rripper), "#{<<~'begin;'}", "#{<<~'end;'}", rss: true) + class MyRipper < Ripper + def initialize(src, &blk) + super(src) + @blk = blk + end + + def compile_error(msg) = @blk.call(msg) + end + + def call_parse = MyRipper.new("/") { |msg| return msg }.parse + + # Check that call_parse does return a syntax error + raise "call_parse should return a syntax error" unless call_parse + begin; + 100_000.times do + call_parse + end + end; + end + + def test_return_out_of_warn_no_memory_leak + assert_no_memory_leak(%w(-rripper), "#{<<~'begin;'}", "#{<<~'end;'}", rss: true) + class MyRipper < Ripper + def initialize(src, &blk) + super(src) + @blk = blk + end + + def warn(msg, *args) = @blk.call(msg) + end + + def call_parse = MyRipper.new("{ a: 1, a: 2 }") { |msg| return msg }.parse + + # Check that call_parse does warn + raise "call_parse should warn" unless call_parse + begin; + 500_000.times do + call_parse + end + end; + + assert_no_memory_leak(%w(-rripper), "#{<<~'begin;'}", "#{<<~'end;'}", rss: true) + class MyRipper < Ripper + def initialize(src, &blk) + super(src) + @blk = blk + end + + def warn(msg, *args) = @blk.call(msg) + end + + $VERBOSE = true + def call_parse = MyRipper.new("if true\n end\n") { |msg| return msg }.parse + + # Check that call_parse does warn + raise "call_parse should warn" unless call_parse + begin; + 1_000_000.times do + call_parse + end + end; + end end if ripper_test diff --git a/test/ripper/test_ripper.rb b/test/ripper/test_ripper.rb index 76276c54ef..f129a41be9 100644 --- a/test/ripper/test_ripper.rb +++ b/test/ripper/test_ripper.rb @@ -14,6 +14,13 @@ class TestRipper::Ripper < Test::Unit::TestCase @ripper = Ripper.new '1 + 1' end + def test_new + assert_separately(%w[-rripper], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_nil EnvUtil.under_gc_stress {Ripper.new("")}.state + end; + end + def test_column assert_nil @ripper.column end @@ -81,7 +88,7 @@ class TestRipper::Ripper < Test::Unit::TestCase ripper.yydebug = true ripper.debug_output = out ripper.parse - assert_include out.string[/.*"literal content".*/], 'woot' + assert_include out.string[/.*"literal content".*/], '1.1-1.5' end def test_regexp_with_option @@ -141,6 +148,36 @@ end assert_nothing_raised { Ripper.lex src } end + def test_no_memory_leak + assert_no_memory_leak(%w(-rripper), "", "#{<<~'end;'}", rss: true) + 2_000_000.times do + Ripper.parse("") + end + end; + + # [Bug #19835] + assert_no_memory_leak(%w(-rripper), "", "#{<<~'end;'}", rss: true) + 1_000_000.times do + Ripper.parse("class Foo") + end + end; + + # [Bug #19836] + assert_no_memory_leak(%w(-rripper), "", "#{<<~'end;'}", rss: true) + 1_000_000.times do + Ripper.parse("-> {") + end + end; + end + + def test_sexp_no_memory_leak + assert_no_memory_leak(%w(-rripper), "", "#{<<~'end;'}", rss: true) + 1_000_000.times do + Ripper.sexp("") + end + end; + end + class TestInput < self Input = Struct.new(:lines) do def gets diff --git a/test/ripper/test_scanner_events.rb b/test/ripper/test_scanner_events.rb index 5d6ac615ca..261e259889 100644 --- a/test/ripper/test_scanner_events.rb +++ b/test/ripper/test_scanner_events.rb @@ -53,6 +53,8 @@ class TestRipper::ScannerEvents < Test::Unit::TestCase Ripper.tokenize("1 .foo\n") assert_equal ["1", "\n", " ", ".", "foo", "\n"], Ripper.tokenize("1\n .foo\n") + assert_equal ["def", " ", "f", ";", " ", "(", "x", ")", "::", "A", " ", "="], + Ripper.tokenize("def f; (x)::A =") end def test_lex @@ -179,6 +181,11 @@ class TestRipper::ScannerEvents < Test::Unit::TestCase scan('backtick', %q[p `make all`]) end + def test_colon2_call + assert_equal ["::"], + scan('op', %q[ a::b ]) + end + def test_comma assert_equal [','] * 6, scan('comma', %q[ m(0,1,2,3,4,5,6) ]) @@ -986,13 +993,21 @@ class TestRipper::ScannerEvents < Test::Unit::TestCase assert_equal("\e", err[2]) end - def test_invalid_hex_escape + def test_invalid_escape + err = nil + assert_equal ["\\C-\u{3042}"], scan('tstring_content', %["\\C-\u{3042}"]) {|*e| err = e} + assert_equal [:on_parse_error, "Invalid escape character syntax", "\\C-\u{3042}"], err + end + + def test_invalid_hex_escape_string err = nil - assert_equal ['U'], scan('tstring_content', '"\\xU"') {|*e| err = e} + assert_equal ['\\x', 'U'], scan('tstring_content', '"\\xU"') {|*e| err = e} assert_equal [:on_parse_error, "invalid hex escape", "\\x"], err + end + def test_invalid_hex_escape_regexp err = nil - assert_equal ['U'], scan('tstring_content', '/\\xU/') {|*e| err = e} + assert_equal ['\\x', 'U'], scan('tstring_content', '/\\xU/') {|*e| err = e} assert_equal [:on_parse_error, "invalid hex escape", "\\x"], err end diff --git a/test/ripper/test_sexp.rb b/test/ripper/test_sexp.rb index a1cf5e4f0a..3ebcf3062e 100644 --- a/test/ripper/test_sexp.rb +++ b/test/ripper/test_sexp.rb @@ -18,6 +18,11 @@ class TestRipper::Sexp < Test::Unit::TestCase assert_nil Ripper.sexp("/*") assert_nil Ripper.sexp("/*/") assert_nil Ripper.sexp("/+/") + assert_nil Ripper.sexp("m(&nil) {}"), '[Bug #10436]' + assert_nil Ripper.sexp("/(?<a>)/ =~ ''; x = a **a, **a if false"), '[Bug #18988]' + assert_nil Ripper.sexp("return + return"), '[Bug #20055]' + assert_nil Ripper.sexp("1 in [a, a]"), '[Bug #20055]' + assert_nil Ripper.sexp("1 + (1 => [a, a])"), '[Bug #20055]' end def test_regexp_content @@ -34,6 +39,14 @@ class TestRipper::Sexp < Test::Unit::TestCase assert_equal '(?<n>a(b|\g<n>))', search_sexp(:@tstring_content, search_sexp(:regexp_literal, sexp))[1] end + def test_regexp_named_capture + sexp = Ripper.sexp("/(?<a>)/ =~ ''; x = a **a, a if false") + assert_not_nil sexp, '[Bug #18988]' + + sexp = Ripper.sexp("/(?<a>)/ =~ ''; a %(exit)") + assert_equal 'exit', search_sexp(:@ident, search_sexp(:paren, sexp))[1], '[Bug #18988]' + end + def test_heredoc_content sexp = Ripper.sexp("<<E\nfoo\nE") assert_equal "foo\n", search_sexp(:@tstring_content, sexp)[1] @@ -110,6 +123,21 @@ eot assert_equal(exp, named) end + def test_command + sexp = Ripper.sexp("a::C {}") + assert_equal( + [:program, + [ + [:method_add_block, + [:command_call, + [:vcall, [:@ident, "a", [1, 0]]], + [:@op, "::", [1, 1]], + [:@const, "C", [1, 3]], + nil], + [:brace_block, nil, [[:void_stmt]]]]]], + sexp) + end + def search_sexp(sym, sexp) return sexp if !sexp or sexp[0] == sym sexp.find do |e| |