Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TruffleRuby in CI and skip the few specs which are not yet supported on TruffleRuby #755

Closed
wants to merge 10 commits into from
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ group: beta
language: ruby

script:
- bundle exec rake compile || bundle exec rake compile
- bundle exec rake compile
- bundle exec rake test
- ITER=10 bundle exec rake bench:all
os:
Expand All @@ -15,6 +15,7 @@ rvm:
- 2.5.3
- 2.6.0
- ruby-head
- truffleruby-head

env:
- CC=gcc
Expand Down
31 changes: 11 additions & 20 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,15 @@ require 'rbconfig'
require 'rspec/core/rake_task'
require 'rubygems/package_task'

def java?
/java/ === RUBY_PLATFORM
end

BUILD_DIR = "build"
BUILD_EXT_DIR = File.join(BUILD_DIR, "#{RbConfig::CONFIG['arch']}", 'ffi_c', RUBY_VERSION)

def gem_spec
@gem_spec ||= Gem::Specification.load('ffi.gemspec')
end

TEST_DEPS = []
if RUBY_PLATFORM == "java"
RSpec::Core::RakeTask.new(:spec) do |config|
config.rspec_opts = YAML.load_file 'spec/spec.opts'
end
else
RSpec::Core::RakeTask.new(:spec => :compile) do |config|
config.rspec_opts = YAML.load_file 'spec/spec.opts'
end

TEST_DEPS.unshift :compile
RSpec::Core::RakeTask.new(:spec => :compile) do |config|
config.rspec_opts = YAML.load_file 'spec/spec.opts'
end

desc "Build all packages"
Expand Down Expand Up @@ -61,19 +48,19 @@ namespace :bench do
bench_libs = "-Ilib" unless RUBY_PLATFORM == "java"
bench_files = Dir["bench/bench_*.rb"].reject { |f| f == "bench/bench_helper.rb" }
bench_files.each do |bench|
task File.basename(bench, ".rb")[6..-1] => TEST_DEPS do
task File.basename(bench, ".rb")[6..-1] => :compile do
sh %{#{Gem.ruby} #{bench_libs} #{bench} #{ITER}}
end
end
task :all => TEST_DEPS do
task :all => :compile do
bench_files.each do |bench|
sh %{#{Gem.ruby} #{bench_libs} #{bench}}
end
end
end

task 'spec:run' => TEST_DEPS
task 'spec:specdoc' => TEST_DEPS
task 'spec:run' => :compile
task 'spec:specdoc' => :compile

task :default => :spec

Expand Down Expand Up @@ -101,7 +88,7 @@ end

task 'gem:java' => 'java:gem'

unless java?
if RUBY_ENGINE == 'ruby' || RUBY_ENGINE == 'rbx'
require 'rake/extensiontask'
Rake::ExtensionTask.new('ffi_c', gem_spec) do |ext|
ext.name = 'ffi_c' # indicate the name of the extension.
Expand All @@ -124,6 +111,10 @@ unless java?
sh "x86_64-w64-mingw32-strip -S build/x64-mingw32/stage/lib/#{ruby_version[/^\d+\.\d+/]}/ffi_c.so"
end
end
else
task :compile do
STDERR.puts "Nothing to compile on #{RUBY_ENGINE}"
end
end

desc "build a windows gem without all the ceremony"
Expand Down
6 changes: 4 additions & 2 deletions ext/ffi_c/LongDouble.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ rbffi_longdouble_new(long double ld)

if (RTEST(rb_cBigDecimal) && rb_cBigDecimal != rb_cObject) {
char buf[128];
return rb_funcall(rb_cBigDecimal, rb_intern("new"), 1, rb_str_new(buf, sprintf(buf, "%.35Le", ld)));
return rb_funcall(rb_mKernel, rb_intern("BigDecimal"), 1, rb_str_new(buf, sprintf(buf, "%.35Le", ld)));
}

/* Fall through to handling as a float */
Expand All @@ -41,7 +41,9 @@ rbffi_num2longdouble(VALUE value)

if (RTEST(rb_cBigDecimal) && rb_cBigDecimal != rb_cObject && RTEST(rb_obj_is_kind_of(value, rb_cBigDecimal))) {
VALUE s = rb_funcall(value, rb_intern("to_s"), 1, rb_str_new2("E"));
return strtold(RSTRING_PTR(s), NULL);
long double ret = strtold(RSTRING_PTR(s), NULL);
RB_GC_GUARD(s);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this required in Truffleruby? In MRI there is no need for a guard here IMHO.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it's not, TruffleRuby doesn't use the C extension for FFI.
I did it to try to debug this long double failure on Windows.
Why is it not needed on MRI, couldn't s be GC'd during the strtold call?

return ret;
}

/* Fall through to handling as a float */
Expand Down
2 changes: 2 additions & 0 deletions spec/ffi/async_callback_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module LibTest
end

it ":int (0x7fffffff) argument" do
skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby"
v = 0xdeadbeef
called = false
cb = Proc.new {|i| v = i; called = true }
Expand All @@ -25,6 +26,7 @@ module LibTest
end

it "called a second time" do
skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby"
v = 0xdeadbeef
called = false
cb = Proc.new {|i| v = i; called = true }
Expand Down
12 changes: 10 additions & 2 deletions spec/ffi/callback_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ class S8F32S32 < FFI::Struct
attach_function :testCallbackVrP, :testClosureVrP, [ :cbVrP ], :pointer
attach_function :testCallbackReturningFunction, :testClosureVrP, [ :cbVrP ], :cbVrP
attach_function :testCallbackVrY, :testClosureVrP, [ :cbVrY ], S8F32S32.ptr
attach_function :testCallbackVrT, :testClosureVrT, [ :cbVrT ], S8F32S32.by_value
attach_function :testCallbackTrV, :testClosureTrV, [ :cbTrV, S8F32S32.ptr ], :void
if RUBY_ENGINE != "truffleruby" # struct by value not yet supported on TruffleRuby
attach_function :testCallbackVrT, :testClosureVrT, [ :cbVrT ], S8F32S32.by_value
attach_function :testCallbackTrV, :testClosureTrV, [ :cbTrV, S8F32S32.ptr ], :void
end
attach_variable :cbVrS8, :gvar_pointer, :cbVrS8
attach_variable :pVrS8, :gvar_pointer, :pointer
attach_function :testGVarCallbackVrS8, :testClosureVrB, [ :pointer ], :char
Expand Down Expand Up @@ -275,6 +277,7 @@ class S8F32S32 < FFI::Struct
end

it "returning struct by value" do
skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby"
skip "Segfault on 32 bit MINGW" if RUBY_PLATFORM == 'i386-mingw32'
s = LibTest::S8F32S32.new
s[:s8] = 0x12
Expand All @@ -288,6 +291,7 @@ class S8F32S32 < FFI::Struct
end

it "struct by value parameter" do
skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby"
s = LibTest::S8F32S32.new
s[:s8] = 0x12
s[:s32] = 0x1eefbeef
Expand Down Expand Up @@ -840,20 +844,23 @@ def assert_callback_in_same_thread_called_once
end

it "from ffi to fiddle" do
skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby"
assert_callback_in_same_thread_called_once do |block|
func = LibTestFiddle.bind_function(:cbVrV, Fiddle::TYPE_VOID, [], &block)
LibTestFFI.testCallbackVrV(FFI::Pointer.new(func.to_i))
end
end

it "from ffi to fiddle with blocking:true" do
skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby"
assert_callback_in_same_thread_called_once do |block|
func = LibTestFiddle.bind_function(:cbVrV, Fiddle::TYPE_VOID, [], &block)
LibTestFFI.testCallbackVrV_blocking(FFI::Pointer.new(func.to_i))
end
end

it "from fiddle to fiddle" do
skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby"
assert_callback_in_same_thread_called_once do |block|
func = LibTestFiddle.bind_function(:cbVrV, Fiddle::TYPE_VOID, [], &block)
LibTestFiddle.testClosureVrV(Fiddle::Pointer[func.to_i])
Expand All @@ -863,6 +870,7 @@ def assert_callback_in_same_thread_called_once
# https://github.com/ffi/ffi/issues/527
if RUBY_ENGINE == 'ruby' && RUBY_VERSION.split('.').map(&:to_i).pack("C*") >= [2,3,0].pack("C*")
it "C outside ffi call stack does not deadlock [#527]" do
skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby"
path = File.join(File.dirname(__FILE__), "embed-test/embed-test.rb")
pid = spawn(RbConfig.ruby, "-Ilib", path, { [:out, :err] => "embed-test.log" })
begin
Expand Down
5 changes: 3 additions & 2 deletions spec/ffi/long_double.rb → spec/ffi/long_double_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
require 'bigdecimal'

describe ":long_double arguments and return values" do
# long double not yet supported on TruffleRuby
describe ":long_double arguments and return values", :if => RUBY_ENGINE != "truffleruby" && RUBY_PLATFORM !~ /mingw|mswin/ do
module LibTest
extend FFI::Library
ffi_lib TestLibrary::PATH
attach_function :add_f128, [ :long_double, :long_double ], :long_double
attach_function :ret_f128, [ :long_double ], :long_double
end
end if RUBY_ENGINE != "truffleruby"
headius marked this conversation as resolved.
Show resolved Hide resolved

it "returns first parameter" do
expect(LibTest.ret_f128(0.1)).to be_within(0.01).of(0.1)
Expand Down
3 changes: 2 additions & 1 deletion spec/ffi/number_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ module LibTest
end
end
end
describe "Integer parameter range checking" do
# range checks are not yet supported on TruffleRuby
describe "Integer parameter range checking", :if => RUBY_ENGINE != "truffleruby" do
[ 128, -129 ].each do |i|
it ":char call(:char (#{i}))" do
expect { expect(LibTest.ret_s8(i)).to eq(i) }.to raise_error(Exception) { |error| expect([RSpec::Expectations::ExpectationNotMetError, RangeError]).to be_include(error.class) }
Expand Down
1 change: 1 addition & 0 deletions spec/ffi/pointer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def to_ptr
end

it "access beyond bounds should raise IndexError" do
skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby"
expect { @mptr.slice(4, 4).get_int(4) }.to raise_error(IndexError)
end
end
Expand Down
2 changes: 2 additions & 0 deletions spec/ffi/rbx/memory_pointer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,13 @@ module CTest
end

it "raises an error if you try putting a long into a pointer of size 1" do
skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby"
m = FFI::MemoryPointer.new(1)
expect { m.write_long(10) }.to raise_error(IndexError)
end

it "raises an error if you try putting an int into a pointer of size 1" do
skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby"
m = FFI::MemoryPointer.new(1)
expect { m.write_int(10) }.to raise_error(IndexError)
end
Expand Down
47 changes: 45 additions & 2 deletions spec/ffi/struct_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,19 @@ class StringMember < FFI::Struct
expect(s[:string]).to be_nil
end

it "Struct#clear sets the memory to zero" do
klass = Class.new(FFI::Struct) do
layout :a, :int, :b, :long
end

s = klass.new
s[:a] = 1
s[:b] = 2
s.clear
expect(s[:a]).to eq(0)
expect(s[:b]).to eq(0)
end

it "Struct#layout works with :name, :type pairs" do
s = Class.new(FFI::Struct) do
layout :a, :int, :b, :long_long
Expand Down Expand Up @@ -328,6 +341,17 @@ def test_num_field(type, v)
expect(s.pointer.send("get_#{type.to_s}", 0)).to eq(v)
s.pointer.send("put_#{type.to_s}", 0, 0)
expect(s[:v]).to eq(0)

# Test coercion
obj = double("coerce")
expect(obj).to receive(:to_int).and_return(v)
s[:v] = obj
expect(s.pointer.send("get_#{type.to_s}", 0)).to eq(v)

zero = double("zero")
expect(zero).to receive(:to_int).and_return(0)
s.pointer.send("put_#{type.to_s}", 0, zero)
expect(s[:v]).to eq(0)
end
def self.int_field_test(type, values)
values.each do |v|
Expand Down Expand Up @@ -360,6 +384,15 @@ def self.int_field_test(type, values)
value = 1.23456
s[:v] = value
expect((s.pointer.get_float(0) - value).abs).to be < 0.0001

# Test coercion
obj = double("coerce")
expect(obj).to receive(:to_f).and_return(42.0)
s[:v] = obj
expect((s.pointer.get_float(0) - 42.0).abs).to be < 0.0001

s.pointer.put_float(0, 1.0)
expect(s.pointer.get_float(0)).to eq(1.0)
end

it ":double field r/w" do
Expand All @@ -370,6 +403,15 @@ def self.int_field_test(type, values)
value = 1.23456
s[:v] = value
expect((s.pointer.get_double(0) - value).abs).to be < 0.0001

# Test coercion
obj = double("coerce")
expect(obj).to receive(:to_f).and_return(42.0)
s[:v] = obj
expect((s.pointer.get_double(0) - 42.0).abs).to be < 0.0001

s.pointer.put_double(0, 1.0)
expect(s.pointer.get_double(0)).to eq(1.0)
end
module EnumFields
extend FFI::Library
Expand Down Expand Up @@ -649,7 +691,8 @@ class ContainerStruct < FFI::Struct
end
end

describe FFI::Struct, ' by value' do
# struct by value not yet supported on TruffleRuby
describe FFI::Struct, ' by value', :if => RUBY_ENGINE != "truffleruby" do
module LibTest
extend FFI::Library
ffi_lib TestLibrary::PATH
Expand All @@ -671,7 +714,7 @@ class StructString < FFI::Struct
attach_function :struct_s8s32_ret_s8s32, [ S8S32.by_value ], S8S32.by_value
attach_function :struct_s32_ptr_s32_s8s32_ret_s32, [ :int, :pointer, :int, S8S32.by_value ], :int
attach_function :struct_varargs_ret_struct_string, [ :int, :varargs ], StructString.by_value
end
end if RUBY_ENGINE != "truffleruby"
headius marked this conversation as resolved.
Show resolved Hide resolved

it 'return using pre-set values' do
s = LibTest.struct_return_s8s32
Expand Down