summaryrefslogtreecommitdiff
path: root/test/socket/test_basicsocket.rb
blob: c8e9b23f8393267d79605ca9afa768fc41cc3fe7 (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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# frozen_string_literal: true

begin
  require "socket"
  require "test/unit"
  require "io/nonblock"
rescue LoadError
end

class TestSocket_BasicSocket < Test::Unit::TestCase
  def inet_stream
    sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
    yield sock
  ensure
    assert(sock.closed?)
  end

  def test_getsockopt
    inet_stream do |s|
      begin
        n = s.getsockopt(Socket::SOL_SOCKET, Socket::SO_TYPE)
        assert_equal([Socket::SOCK_STREAM].pack("i"), n.data)

        n = s.getsockopt("SOL_SOCKET", "SO_TYPE")
        assert_equal([Socket::SOCK_STREAM].pack("i"), n.data)

        n = s.getsockopt(:SOL_SOCKET, :SO_TYPE)
        assert_equal([Socket::SOCK_STREAM].pack("i"), n.data)

        n = s.getsockopt(:SOCKET, :TYPE)
        assert_equal([Socket::SOCK_STREAM].pack("i"), n.data)

        n = s.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR)
        assert_equal([0].pack("i"), n.data)
      rescue Minitest::Assertion
        s.close
        if /aix/ =~ RUBY_PLATFORM
          skip "Known bug in getsockopt(2) on AIX"
        end
        raise $!
      end

      val = Object.new
      class << val; self end.send(:define_method, :to_int) {
        s.close
        Socket::SO_TYPE
      }
      assert_raise(IOError) {
        n = s.getsockopt(Socket::SOL_SOCKET, val)
      }
    end
  end

  def test_setsockopt
    s = nil
    linger = [0, 0].pack("ii")

    val = Object.new
    class << val; self end.send(:define_method, :to_str) {
      s.close
      linger
    }
    inet_stream do |sock|
      s = sock
      assert_equal(0, s.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, linger))

      assert_raise(IOError, "[ruby-dev:25039]") {
        s.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, val)
      }
    end

    val = Object.new
    class << val; self end.send(:define_method, :to_int) {
      s.close
      Socket::SO_LINGER
    }
    inet_stream do |sock|
      s = sock
      assert_raise(IOError) {
        s.setsockopt(Socket::SOL_SOCKET, val, linger)
      }
    end
  end

  def test_listen
    s = nil
    log = Object.new
    class << log; self end.send(:define_method, :to_int) {
      s.close
      2
    }
    inet_stream do |sock|
      s = sock
      assert_raise(IOError) {
        s.listen(log)
      }
    end
  end

  def socks
    sserv = TCPServer.new('localhost', 0)
    ssock = nil
    t = Thread.new { ssock = sserv.accept }
    csock = TCPSocket.new('localhost', sserv.addr[1])
    t.join
    yield sserv, ssock, csock
  ensure
    ssock.close rescue nil
    csock.close rescue nil
    sserv.close rescue nil
  end

  def test_close_read
    socks do |sserv, ssock, csock|

      # close_read makes subsequent reads raise IOError
      csock.close_read
      assert_raise(IOError) { csock.read(5) }

      # close_read ignores any error from shutting down half of still-open socket
      assert_nothing_raised { csock.close_read }

      # close_read raises if socket is not open
      assert_nothing_raised { csock.close }
      assert_raise(IOError) { csock.close_read }
    end
  end

  def test_close_write
    socks do |sserv, ssock, csock|

      # close_write makes subsequent writes raise IOError
      csock.close_write
      assert_raise(IOError) { csock.write(5) }

      # close_write ignores any error from shutting down half of still-open socket
      assert_nothing_raised { csock.close_write }

      # close_write raises if socket is not open
      assert_nothing_raised { csock.close }
      assert_raise(IOError) { csock.close_write }
    end
  end

  def test_for_fd
    assert_raise(Errno::EBADF, '[ruby-core:72418] [Bug #11854]') do
      BasicSocket.for_fd(-1)
    end
    inet_stream do |sock|
      s = BasicSocket.for_fd(sock.fileno)
      assert_instance_of BasicSocket, s
      s.autoclose = false
      sock.close
    end
  end

  def test_read_write_nonblock
    socks do |sserv, ssock, csock|
      set_nb = true
      buf = String.new
      if ssock.respond_to?(:nonblock?)
        assert_not_predicate(ssock, :nonblock?)
        assert_not_predicate(csock, :nonblock?)
        csock.nonblock = ssock.nonblock = false

        # Linux may use MSG_DONTWAIT to avoid setting O_NONBLOCK
        if RUBY_PLATFORM.match?(/linux/) && Socket.const_defined?(:MSG_DONTWAIT)
          set_nb = false
        end
      end
      assert_equal :wait_readable, ssock.read_nonblock(1, buf, exception: false)
      assert_equal 5, csock.write_nonblock('hello')
      IO.select([ssock])
      assert_same buf, ssock.read_nonblock(5, buf, exception: false)
      assert_equal 'hello', buf
      buf = '*' * 16384
      n = 0

      case w = csock.write_nonblock(buf, exception: false)
      when Integer
        n += w
      when :wait_writable
        break
      end while true

      assert_equal :wait_writable, w
      assert_raise(IO::WaitWritable) { loop { csock.write_nonblock(buf) } }
      assert_operator n, :>, 0
      assert_not_predicate(csock, :nonblock?, '[Feature #13362]') unless set_nb
      csock.close

      case r = ssock.read_nonblock(16384, buf, exception: false)
      when String
        next
      when nil
        break
      when :wait_readable
        IO.select([ssock], nil, nil, 10) or
          flunk 'socket did not become readable'
      else
        flunk "unexpected read_nonblock return: #{r.inspect}"
      end while true

      assert_raise(EOFError) { ssock.read_nonblock(1) }

      assert_not_predicate(ssock, :nonblock?) unless set_nb
    end
  end

  def test_read_nonblock_mix_buffered
    socks do |sserv, ssock, csock|
      ssock.write("hello\nworld\n")
      assert_equal "hello\n", csock.gets
      IO.select([csock], nil, nil, 10) or
        flunk 'socket did not become readable'
      assert_equal "world\n", csock.read_nonblock(8)
    end
  end

  def test_write_nonblock_buffered
    socks do |sserv, ssock, csock|
      ssock.sync = false
      ssock.write("h")
      assert_equal :wait_readable, csock.read_nonblock(1, exception: false)
      assert_equal 4, ssock.write_nonblock("ello")
      ssock.close
      assert_equal "hello", csock.read(5)
    end
  end
end if defined?(BasicSocket)