diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/prism/fixtures/bom_leading_space.txt | 1 | ||||
| -rw-r--r-- | test/prism/fixtures/bom_spaces.txt | 1 | ||||
| -rw-r--r-- | test/prism/ruby/ripper_test.rb | 62 | ||||
| -rw-r--r-- | test/psych/test_data.rb | 5 | ||||
| -rw-r--r-- | test/ruby/test_zjit.rb | 354 | ||||
| -rw-r--r-- | test/rubygems/test_gem_ext_builder.rb | 53 |
6 files changed, 467 insertions, 9 deletions
diff --git a/test/prism/fixtures/bom_leading_space.txt b/test/prism/fixtures/bom_leading_space.txt new file mode 100644 index 0000000000..48d3ee50ea --- /dev/null +++ b/test/prism/fixtures/bom_leading_space.txt @@ -0,0 +1 @@ + p (42) diff --git a/test/prism/fixtures/bom_spaces.txt b/test/prism/fixtures/bom_spaces.txt new file mode 100644 index 0000000000..c18ad4c21a --- /dev/null +++ b/test/prism/fixtures/bom_spaces.txt @@ -0,0 +1 @@ +p ( 42 ) diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb index 2a0504c19f..6e9dcee4c9 100644 --- a/test/prism/ruby/ripper_test.rb +++ b/test/prism/ruby/ripper_test.rb @@ -39,6 +39,8 @@ module Prism # Skip these tests that we haven't implemented yet. omitted_sexp_raw = [ + "bom_leading_space.txt", + "bom_spaces.txt", "dos_endings.txt", "heredocs_with_fake_newlines.txt", "heredocs_with_ignored_newlines.txt", @@ -84,6 +86,56 @@ module Prism define_method("#{fixture.test_name}_lex") { assert_ripper_lex(fixture.read) } end + module Events + attr_reader :events + + def initialize(...) + super + @events = [] + end + + Prism::Translation::Ripper::PARSER_EVENTS.each do |event| + define_method(:"on_#{event}") do |*args| + @events << [event, *args] + super(*args) + end + end + end + + class RipperEvents < Ripper + include Events + end + + class PrismEvents < Translation::Ripper + include Events + end + + class ObjectEvents < Translation::Ripper + OBJECT = BasicObject.new + Prism::Translation::Ripper::PARSER_EVENTS.each do |event| + define_method(:"on_#{event}") { |*args| OBJECT } + end + end + + Fixture.each_for_current_ruby(except: incorrect) do |fixture| + define_method("#{fixture.test_name}_events") do + source = fixture.read + # Similar to test/ripper/assert_parse_files.rb in CRuby + object_events = ObjectEvents.new(source) + assert_nothing_raised { object_events.parse } + end + end + + def test_events + source = "1 rescue 2" + ripper = RipperEvents.new(source) + prism = PrismEvents.new(source) + ripper.parse + prism.parse + # This makes sure that the content is the same. Ordering is not correct for now. + assert_equal(ripper.events.sort, prism.events.sort) + end + def test_lexer lexer = Translation::Ripper::Lexer.new("foo") expected = [[1, 0], :on_ident, "foo", Translation::Ripper::EXPR_CMDARG] @@ -92,7 +144,7 @@ module Prism assert_equal(expected, lexer.parse[0].to_a) assert_equal(lexer.parse[0].to_a, lexer.scan[0].to_a) - assert_equal(%i[on_int on_op], Translation::Ripper::Lexer.new("1 +").lex.map(&:event)) + assert_equal(%i[on_int on_sp on_op], Translation::Ripper::Lexer.new("1 +").lex.map(&:event)) assert_raise(SyntaxError) { Translation::Ripper::Lexer.new("1 +").lex(raise_errors: true) } end @@ -121,15 +173,17 @@ module Prism def assert_ripper_lex(source) prism = Translation::Ripper.lex(source) ripper = Ripper.lex(source) - ripper.reject! { |elem| elem[1] == :on_sp } # Prism doesn't emit on_sp - ripper.sort_by! { |elem| elem[0] } # Prism emits tokens by their order in the code, not in parse order + + # Prism emits tokens by their order in the code, not in parse order + ripper.sort_by! { |elem| elem[0] } [prism.size, ripper.size].max.times do |i| expected = ripper[i] actual = prism[i] + # Since tokens related to heredocs are not emitted in the same order, # the state also doesn't line up. - if expected[1] == :on_heredoc_end && actual[1] == :on_heredoc_end + if expected && actual && expected[1] == :on_heredoc_end && actual[1] == :on_heredoc_end expected[3] = actual[3] = nil end diff --git a/test/psych/test_data.rb b/test/psych/test_data.rb index 57c3478193..5e340c580a 100644 --- a/test/psych/test_data.rb +++ b/test/psych/test_data.rb @@ -83,12 +83,11 @@ module Psych # completely different members TestData.send :remove_const, :D - TestData.const_set :D, Data.define(:foo, :bar) + TestData.const_set :D, Data.define(:a, :c) e = assert_raise(ArgumentError) { Psych.unsafe_load d } - assert_equal 'unknown keywords: :a, :b', e.message + assert_include e.message, 'keyword:' ensure TestData.send :remove_const, :D end end end - diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index ad2df806d5..2066610cb2 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -470,6 +470,42 @@ class TestZJIT < Test::Unit::TestCase }, insns: [:getblockparamproxy] end + def test_getblockparam + assert_compiles '2', %q{ + def test(&blk) + blk + end + test { 2 }.call + test { 2 }.call + }, insns: [:getblockparam] + end + + def test_getblockparam_proxy_side_exit_restores_block_local + assert_compiles '2', %q{ + def test(&block) + b = block + # sideexits here + raise "test" unless block + b ? 2 : 3 + end + test {} + test {} + }, insns: [:getblockparam, :getblockparamproxy] + end + + def test_getblockparam_used_twice_in_args + assert_compiles '1', %q{ + def f(*args) = args + def test(&blk) + b = blk + f(*[1], blk) + blk + end + test {1}.call + test {1}.call + }, insns: [:getblockparam] + end + def test_optimized_method_call_proc_call assert_compiles '2', %q{ p = proc { |x| x * 2 } @@ -833,6 +869,61 @@ class TestZJIT < Test::Unit::TestCase }, call_threshold: 2 end + def test_pos_optional_with_maybe_too_many_args + assert_compiles '[[1, 2, 3, 4, 5, 6], [10, 20, 30, 4, 5, 6], [10, 20, 30, 40, 50, 60]]', %q{ + def target(a = 1, b = 2, c = 3, d = 4, e = 5, f:) = [a, b, c, d, e, f] + def test = [target(f: 6), target(10, 20, 30, f: 6), target(10, 20, 30, 40, 50, f: 60)] + test + test + }, call_threshold: 2 + end + + def test_send_kwarg_partial_optional + assert_compiles '[[1, 2, 3], [1, 20, 3], [10, 2, 30]]', %q{ + def test(a: 1, b: 2, c: 3) = [a, b, c] + def entry = [test, test(b: 20), test(c: 30, a: 10)] + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_optional_a_lot + assert_compiles '[[1, 2, 3, 4, 5, 6], [1, 2, 3, 7, 8, 9], [2, 4, 6, 8, 10, 12]]', %q{ + def test(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6) = [a, b, c, d, e, f] + def entry = [test, test(d: 7, f: 9, e: 8), test(f: 12, e: 10, d: 8, c: 6, b: 4, a: 2)] + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_non_constant_default + assert_compiles '[[1, 2], [10, 2]]', %q{ + def make_default = 2 + def test(a: 1, b: make_default) = [a, b] + def entry = [test, test(a: 10)] + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_optional_static_with_side_exit + # verify frame reconstruction with synthesized keyword defaults is correct + assert_compiles '[10, 2, 10]', %q{ + def callee(a: 1, b: 2) + # use binding to force side-exit + x = binding.local_variable_get(:a) + [a, b, x] + end + + def entry + callee(a: 10) # b should get default value + end + + entry + entry + }, call_threshold: 2 + end + def test_send_all_arg_types assert_compiles '[:req, :opt, :post, :kwr, :kwo, true]', %q{ def test(a, b = :opt, c, d:, e: :kwo) = [a, b, c, d, e, block_given?] @@ -1388,6 +1479,190 @@ class TestZJIT < Test::Unit::TestCase }, call_threshold: 2 end + def test_invokesuper_with_optional_keyword_args + assert_compiles '[1, 2, 3]', %q{ + class Parent + def foo(a, b: 2, c: 3) = [a, b, c] + end + + class Child < Parent + def foo(a) = super(a) + end + + def test = Child.new.foo(1) + + test + test + }, call_threshold: 2 + end + + def test_send_with_non_constant_keyword_default + assert_compiles '[[2, 4, 16], [10, 4, 16], [2, 20, 16], [2, 4, 30], [10, 20, 30]]', %q{ + def dbl(x = 1) = x * 2 + + def foo(a: dbl, b: dbl(2), c: dbl(2 ** 3)) + [a, b, c] + end + + def test + [ + foo, + foo(a: 10), + foo(b: 20), + foo(c: 30), + foo(a: 10, b: 20, c: 30) + ] + end + + test + test + }, call_threshold: 2 + end + + def test_send_with_non_constant_keyword_default_not_evaluated_when_provided + assert_compiles '[1, 2, 3]', %q{ + def foo(a: raise, b: raise, c: raise) + [a, b, c] + end + + def test + foo(a: 1, b: 2, c: 3) + end + + test + test + }, call_threshold: 2 + end + + def test_send_with_non_constant_keyword_default_evaluated_when_not_provided + assert_compiles '["a", "b", "c"]', %q{ + def raise_a = raise "a" + def raise_b = raise "b" + def raise_c = raise "c" + + def foo(a: raise_a, b: raise_b, c: raise_c) + [a, b, c] + end + + def test_a + foo(b: 2, c: 3) + rescue RuntimeError => e + e.message + end + + def test_b + foo(a: 1, c: 3) + rescue RuntimeError => e + e.message + end + + def test_c + foo(a: 1, b: 2) + rescue RuntimeError => e + e.message + end + + def test + [test_a, test_b, test_c] + end + + test + test + }, call_threshold: 2 + end + + def test_send_with_non_constant_keyword_default_jit_to_jit + # Test that kw_bits passing works correctly in JIT-to-JIT calls + assert_compiles '[2, 4, 6]', %q{ + def make_default(x) = x * 2 + + def callee(a: make_default(1), b: make_default(2), c: make_default(3)) + [a, b, c] + end + + def caller_method + callee + end + + # Warm up callee first so it gets JITted + callee + callee + + # Now warm up caller - this creates JIT-to-JIT call + caller_method + caller_method + }, call_threshold: 2 + end + + def test_send_with_non_constant_keyword_default_side_exit + # Verify frame reconstruction includes correct values for non-constant defaults + assert_compiles '[10, 2, 30]', %q{ + def make_b = 2 + + def callee(a: 1, b: make_b, c: 3) + x = binding.local_variable_get(:a) + y = binding.local_variable_get(:b) + z = binding.local_variable_get(:c) + [x, y, z] + end + + def test + callee(a: 10, c: 30) + end + + test + test + }, call_threshold: 2 + end + + def test_send_with_non_constant_keyword_default_evaluation_order + # Verify defaults are evaluated left-to-right and only when not provided + assert_compiles '[["a", "b", "c"], ["b", "c"], ["a", "c"], ["a", "b"]]', %q{ + def log(x) + $order << x + x + end + + def foo(a: log("a"), b: log("b"), c: log("c")) + [a, b, c] + end + + def test + results = [] + + $order = [] + foo + results << $order.dup + + $order = [] + foo(a: "A") + results << $order.dup + + $order = [] + foo(b: "B") + results << $order.dup + + $order = [] + foo(c: "C") + results << $order.dup + + results + end + + test + test + }, call_threshold: 2 + end + + def test_send_with_too_many_non_constant_keyword_defaults + assert_compiles '35', %q{ + def many_kwargs( k1: 1, k2: 2, k3: 3, k4: 4, k5: 5, k6: 6, k7: 7, k8: 8, k9: 9, k10: 10, k11: 11, k12: 12, k13: 13, k14: 14, k15: 15, k16: 16, k17: 17, k18: 18, k19: 19, k20: 20, k21: 21, k22: 22, k23: 23, k24: 24, k25: 25, k26: 26, k27: 27, k28: 28, k29: 29, k30: 30, k31: 31, k32: 32, k33: 33, k34: k33 + 1) = k1 + k34 + def t = many_kwargs + t + t + }, call_threshold: 2 + end + def test_invokebuiltin # Not using assert_compiles due to register spill assert_runs '["."]', %q{ @@ -4417,6 +4692,82 @@ class TestZJIT < Test::Unit::TestCase }, call_threshold: 14, num_profiles: 5 end + def test_is_a_string_special_case + assert_compiles '[true, false, false, false, false, false]', %q{ + def test(x) + x.is_a?(String) + end + test("foo") + [test("bar"), test(1), test(false), test(:foo), test([]), test({})] + } + end + + def test_is_a_array_special_case + assert_compiles '[true, true, false, false, false, false, false]', %q{ + def test(x) + x.is_a?(Array) + end + test([]) + [test([1,2,3]), test([]), test(1), test(false), test(:foo), test("foo"), test({})] + } + end + + def test_is_a_hash_special_case + assert_compiles '[true, true, false, false, false, false, false]', %q{ + def test(x) + x.is_a?(Hash) + end + test({}) + [test({:a => "b"}), test({}), test(1), test(false), test(:foo), test([]), test("foo")] + } + end + + def test_is_a_hash_subclass + assert_compiles 'true', %q{ + class MyHash < Hash + end + def test(x) + x.is_a?(Hash) + end + test({}) + test(MyHash.new) + } + end + + def test_is_a_normal_case + assert_compiles '[true, false]', %q{ + class MyClass + end + def test(x) + x.is_a?(MyClass) + end + test("a") + [test(MyClass.new), test("a")] + } + end + + def test_exit_tracing + # This is a very basic smoke test. The StackProf format + # this option generates is external to us. + Dir.mktmpdir("zjit_test_exit_tracing") do |tmp_dir| + assert_compiles('true', <<~RUBY, extra_args: ['-C', tmp_dir, '--zjit-trace-exits']) + def test(object) = object.itself + + # induce an exit just for good measure + array = [] + test(array) + test(array) + def array.itself = :not_itself + test(array) + + RubyVM::ZJIT.exit_locations.is_a?(Hash) + RUBY + dump_files = Dir.glob('zjit_exits_*.dump', base: tmp_dir) + assert_equal(1, dump_files.length) + refute(File.empty?(File.join(tmp_dir, dump_files.first))) + end + end + private # Assert that every method call in `test_script` can be compiled by ZJIT @@ -4477,10 +4828,11 @@ class TestZJIT < Test::Unit::TestCase stats: false, debug: true, allowed_iseqs: nil, + extra_args: nil, timeout: 1000, pipe_fd: nil ) - args = ["--disable-gems"] + args = ["--disable-gems", *extra_args] if zjit args << "--zjit-call-threshold=#{call_threshold}" args << "--zjit-num-profiles=#{num_profiles}" diff --git a/test/rubygems/test_gem_ext_builder.rb b/test/rubygems/test_gem_ext_builder.rb index 34f85e6b75..5fcbc3e2ac 100644 --- a/test/rubygems/test_gem_ext_builder.rb +++ b/test/rubygems/test_gem_ext_builder.rb @@ -18,7 +18,7 @@ class TestGemExtBuilder < Gem::TestCase @spec = util_spec "a" - @builder = Gem::Ext::Builder.new @spec, "" + @builder = Gem::Ext::Builder.new @spec end def teardown @@ -201,6 +201,57 @@ install: Gem.configuration.install_extension_in_lib = @orig_install_extension_in_lib end + def test_build_multiple_extensions + pend if RUBY_ENGINE == "truffleruby" + pend "terminates on ruby/ruby" if ruby_repo? + + extension_in_lib do + @spec.extensions << "ext/Rakefile" + @spec.extensions << "ext/extconf.rb" + + ext_dir = File.join @spec.gem_dir, "ext" + + FileUtils.mkdir_p ext_dir + + extconf_rb = File.join ext_dir, "extconf.rb" + rakefile = File.join ext_dir, "Rakefile" + + File.open extconf_rb, "w" do |f| + f.write <<-'RUBY' + require 'mkmf' + + create_makefile 'a' + RUBY + end + + File.open rakefile, "w" do |f| + f.write <<-RUBY + task :default do + FileUtils.touch File.join "#{ext_dir}", 'foo' + end + RUBY + end + + ext_lib_dir = File.join ext_dir, "lib" + FileUtils.mkdir ext_lib_dir + FileUtils.touch File.join ext_lib_dir, "a.rb" + FileUtils.mkdir File.join ext_lib_dir, "a" + FileUtils.touch File.join ext_lib_dir, "a", "b.rb" + + use_ui @ui do + @builder.build_extensions + end + + assert_path_exist @spec.extension_dir + assert_path_exist @spec.gem_build_complete_path + assert_path_exist File.join @spec.gem_dir, "ext", "foo" + assert_path_exist File.join @spec.extension_dir, "gem_make.out" + assert_path_exist File.join @spec.extension_dir, "a.rb" + assert_path_exist File.join @spec.gem_dir, "lib", "a.rb" + assert_path_exist File.join @spec.gem_dir, "lib", "a", "b.rb" + end + end + def test_build_extensions_none use_ui @ui do @builder.build_extensions |
