diff options
Diffstat (limited to 'test/ripper')
-rw-r--r-- | test/ripper/assert_parse_files.rb | 25 | ||||
-rw-r--r-- | test/ripper/dummyparser.rb | 53 | ||||
-rw-r--r-- | test/ripper/test_lexer.rb | 264 | ||||
-rw-r--r-- | test/ripper/test_parser_events.rb | 84 | ||||
-rw-r--r-- | test/ripper/test_ripper.rb | 39 | ||||
-rw-r--r-- | test/ripper/test_scanner_events.rb | 37 | ||||
-rw-r--r-- | test/ripper/test_sexp.rb | 54 |
7 files changed, 495 insertions, 61 deletions
diff --git a/test/ripper/assert_parse_files.rb b/test/ripper/assert_parse_files.rb index 85d20cf69e..0d583a99e3 100644 --- a/test/ripper/assert_parse_files.rb +++ b/test/ripper/assert_parse_files.rb @@ -5,26 +5,39 @@ module TestRipper; end class TestRipper::Generic < Test::Unit::TestCase SRCDIR = File.expand_path("../../..", __FILE__) - def assert_parse_files(dir, pattern = "**/*.rb") - assert_separately(%W[--disable-gem -rripper - #{SRCDIR}/#{dir} #{pattern}], + 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[-rripper - #{SRCDIR}/#{dir} #{pattern}], __FILE__, __LINE__, "#{<<-"begin;"}\n#{<<-'end;'}", timeout: Float::INFINITY) + GC.stress = false pattern = "#{pattern}" + exclude = ( + #{exclude if exclude} + ) + test_ratio = ( + #{test_ratio} + ) + gc_stress = ( + #{gc_stress} + ) begin; - TEST_RATIO = ENV["TEST_RIPPER_RATIO"]&.tap {|s|break s.to_f} || 0.05 # testing all files needs too long time... class Parser < Ripper PARSER_EVENTS.each {|n| eval "def on_#{n}(*args) r = [:#{n}, *args]; r.inspect; Object.new end" } SCANNER_EVENTS.each {|n| eval "def on_#{n}(*args) r = [:#{n}, *args]; r.inspect; Object.new end" } end dir = ARGV.shift - scripts = Dir.chdir(dir) {Dir[pattern]} - if (1...scripts.size).include?(num = scripts.size * TEST_RATIO) + scripts = Dir.glob(pattern, base: dir) + scripts.reject! {|script| File.fnmatch?(exclude, script, File::FNM_PATHNAME)} if exclude + if (1...scripts.size).include?(num = scripts.size * test_ratio) scripts = scripts.sample(num) end scripts.sort! for script in scripts assert_nothing_raised { parser = Parser.new(File.read("#{dir}/#{script}"), script) - parser.instance_eval "parse", "<#{script}>" + EnvUtil.under_gc_stress(gc_stress) do + parser.instance_eval "parse", "<#{script}>" + end } end end; 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 27e0007023..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'], @@ -252,4 +252,266 @@ 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 = 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..5434acb523 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;")} 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 13bd44e83d..792f19ef1a 100644 --- a/test/ripper/test_scanner_events.rb +++ b/test/ripper/test_scanner_events.rb @@ -113,11 +113,11 @@ class TestRipper::ScannerEvents < Test::Unit::TestCase [[5, 0], :on_imaginary, "5.6ri", Ripper::EXPR_END], ], Ripper.lex("1r\n2i\n3ri\n4.2r\n5.6ri") - assert_lex [[[1, 0], :on_heredoc_beg, "<<~EOS", Ripper::EXPR_BEG], - [[1, 6], :on_nl, "\n", Ripper::EXPR_BEG], - [[2, 0], :on_ignored_sp, " ", Ripper::EXPR_BEG], - [[2, 2], :on_tstring_content, "heredoc\n", Ripper::EXPR_BEG], - [[3, 0], :on_heredoc_end, "EOS", Ripper::EXPR_BEG] + assert_lex [[[1, 0], :on_heredoc_beg, "<<~EOS", Ripper::EXPR_BEG], + [[1, 6], :on_nl, "\n", Ripper::EXPR_BEG], + [[2, 0], :on_ignored_sp, " ", Ripper::EXPR_BEG], + [[2, 2], :on_tstring_content, "heredoc\n", Ripper::EXPR_BEG], + [[3, 0], :on_heredoc_end, "EOS", Ripper::EXPR_BEG] ], Ripper.lex("<<~EOS\n heredoc\nEOS") assert_lex [[[1, 0], :on_tstring_beg, "'", Ripper::EXPR_BEG], @@ -179,6 +179,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) ]) @@ -712,7 +717,7 @@ class TestRipper::ScannerEvents < Test::Unit::TestCase scan('words_sep', '%w( w w w )') assert_equal [' ', "\n", ' ', ' '], scan('words_sep', "%w( w\nw w )") - assert_equal ["\n\n", "\n ", ' ', ' '], + assert_equal ["\n", "\n", "\n", ' ', ' ', ' '], scan('words_sep', "%w(\n\nw\n w w )") end @@ -986,13 +991,27 @@ 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 + + def test_error_token + src = "{a:,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n""hello}" + err = scan('parse_error', src) {|*e| break e} + assert_equal "", err[2] + end end if ripper_test diff --git a/test/ripper/test_sexp.rb b/test/ripper/test_sexp.rb index 9faeaba782..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| @@ -175,7 +203,7 @@ eot [:aryptn, nil, [[:var_field, [:@ident, "a", [1, 11]]]], - [:var_field, nil], + nil, nil], [[:void_stmt]], nil]], @@ -407,6 +435,14 @@ eot [[:void_stmt]], nil]], + [__LINE__, %q{ case 0; in [a,]; end }] => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:aryptn, nil, [[:var_field, [:@ident, "a", [1, 12]]]], nil, nil], + [[:void_stmt]], + nil]], + [__LINE__, %q{ case 0; in []; end }] => [:case, [:@int, "0", [1, 5]], @@ -486,6 +522,22 @@ eot [:begin, [:binary, [:@int, "0", [1, 13]], :+, [:@int, "0", [1, 15]]]], [[:void_stmt]], nil]], + + [__LINE__, %q{ case 0; in [*a]; a; end } ] => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:aryptn, nil, nil, [:var_field, [:@ident, "a", [1, 13]]], nil], + [[:var_ref, [:@ident, "a", [1, 17]]]], + nil]], + + [__LINE__, %q{ case 0; in {a:}; a; end } ] => + [:case, + [:@int, "0", [1, 5]], + [:in, + [:hshptn, nil, [[[:@label, "a:", [1, 12]], nil]], nil], + [[:var_ref, [:@ident, "a", [1, 17]]]], + nil]], } pattern_matching_data.each do |(i, src), expected| define_method(:"test_pattern_matching_#{i}") do |