summaryrefslogtreecommitdiff
path: root/test/fiddle/test_function.rb
blob: 0bb66f9b8decc374d7848a9269856ce22b6417aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# frozen_string_literal: true
begin
  require_relative 'helper'
rescue LoadError
end

module Fiddle
  class TestFunction < Fiddle::TestCase
    def setup
      super
      Fiddle.last_error = nil
    end

    def test_default_abi
      func = Function.new(@libm['sin'], [TYPE_DOUBLE], TYPE_DOUBLE)
      assert_equal Function::DEFAULT, func.abi
    end

    def test_name
      func = Function.new(@libm['sin'], [TYPE_DOUBLE], TYPE_DOUBLE, name: 'sin')
      assert_equal 'sin', func.name
    end

    def test_argument_errors
      assert_raise(TypeError) do
        Function.new(@libm['sin'], TYPE_DOUBLE, TYPE_DOUBLE)
      end

      assert_raise(TypeError) do
        Function.new(@libm['sin'], ['foo'], TYPE_DOUBLE)
      end

      assert_raise(TypeError) do
        Function.new(@libm['sin'], [TYPE_DOUBLE], 'foo')
      end
    end

    def test_argument_type_conversion
      type = Struct.new(:int, :call_count) do
        def initialize(int)
          super(int, 0)
        end
        def to_int
          raise "exhausted" if (self.call_count += 1) > 1
          self.int
        end
      end
      type_arg = type.new(TYPE_DOUBLE)
      type_result = type.new(TYPE_DOUBLE)
      assert_nothing_raised(RuntimeError) do
        Function.new(@libm['sin'], [type_arg], type_result)
      end
      assert_equal(1, type_arg.call_count)
      assert_equal(1, type_result.call_count)
    end

    def test_call
      func = Function.new(@libm['sin'], [TYPE_DOUBLE], TYPE_DOUBLE)
      assert_in_delta 1.0, func.call(90 * Math::PI / 180), 0.0001
    end

    def test_argument_count
      closure = Class.new(Closure) {
        def call one
          10 + one
        end
      }.new(TYPE_INT, [TYPE_INT])
      func = Function.new(closure, [TYPE_INT], TYPE_INT)

      assert_raise(ArgumentError) do
        func.call(1,2,3)
      end
      assert_raise(ArgumentError) do
        func.call
      end
    end

    def test_last_error
      func = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)

      assert_nil Fiddle.last_error
      func.call(+"000", "123")
      refute_nil Fiddle.last_error
    end

    def test_strcpy
      f = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
      buff = +"000"
      str = f.call(buff, "123")
      assert_equal("123", buff)
      assert_equal("123", str.to_s)
    end

    def test_nogvl_poll
      # XXX hack to quiet down CI errors on EINTR from r64353
      # [ruby-core:88360] [Misc #14937]
      # Making pipes (and sockets) non-blocking by default would allow
      # us to get rid of POSIX timers / timer pthread
      # https://bugs.ruby-lang.org/issues/14968
      IO.pipe { |r,w| IO.select([r], [w]) }
      begin
        poll = @libc['poll']
      rescue Fiddle::DLError
        skip 'poll(2) not available'
      end
      f = Function.new(poll, [TYPE_VOIDP, TYPE_INT, TYPE_INT], TYPE_INT)

      msec = 200
      t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
      th = Thread.new { f.call(nil, 0, msec) }
      n1 = f.call(nil, 0, msec)
      n2 = th.value
      t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
      delta = EnvUtil.apply_timeout_scale(180)
      assert_in_delta(msec, t1 - t0, delta, 'slept amount of time')
      assert_equal(0, n1, perror("poll(2) in main-thread"))
      assert_equal(0, n2, perror("poll(2) in sub-thread"))
    end

    def test_no_memory_leak
      prep = 'r = Fiddle::Function.new(Fiddle.dlopen(nil)["rb_obj_frozen_p"], [Fiddle::TYPE_UINTPTR_T], Fiddle::TYPE_UINTPTR_T); a = "a"'
      code = 'begin r.call(a); rescue TypeError; end'
      assert_no_memory_leak(%w[-W0 -rfiddle], "#{prep}\n1000.times{#{code}}", "10_000.times {#{code}}", limit: 1.2)
    end

    private

    def perror(m)
      proc do
        if e = Fiddle.last_error
          m = "#{m}: #{SystemCallError.new(e).message}"
        end
        m
      end
    end
  end
end if defined?(Fiddle)