summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/prism/fixtures/bom_leading_space.txt1
-rw-r--r--test/prism/fixtures/bom_spaces.txt1
-rw-r--r--test/prism/ruby/ripper_test.rb62
-rw-r--r--test/psych/test_data.rb5
-rw-r--r--test/ruby/test_zjit.rb354
-rw-r--r--test/rubygems/test_gem_ext_builder.rb53
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