summaryrefslogtreecommitdiff
path: root/spec/ruby/library/socket/tcpserver/accept_spec.rb
blob: d8892cd5f061d3104142017b36e300bf8500868b (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
require_relative '../spec_helper'
require_relative '../fixtures/classes'

describe "TCPServer#accept" do
  before :each do
    @server = TCPServer.new("127.0.0.1", 0)
    @port = @server.addr[1]
  end

  after :each do
    @server.close unless @server.closed?
  end

  it "accepts a connection and returns a TCPSocket" do
    data = nil
    t = Thread.new do
      client = @server.accept
      client.should be_kind_of(TCPSocket)
      data = client.read(5)
      client << "goodbye"
      client.close
    end
    Thread.pass while t.status and t.status != "sleep"

    socket = TCPSocket.new('127.0.0.1', @port)
    socket.write('hello')
    socket.shutdown(1) # we are done with sending
    socket.read.should == 'goodbye'
    t.join
    data.should == 'hello'
    socket.close
  end

  it "can be interrupted by Thread#kill" do
    t = Thread.new { @server.accept }

    Thread.pass while t.status and t.status != "sleep"

    # kill thread, ensure it dies in a reasonable amount of time
    t.kill
    a = 0
    while t.alive? and a < 5000
      sleep 0.001
      a += 1
    end
    a.should < 5000
  end

  it "can be interrupted by Thread#raise" do
    t = Thread.new {
      -> {
        @server.accept
      }.should raise_error(Exception, "interrupted")
    }

    Thread.pass while t.status and t.status != "sleep"
    t.raise Exception, "interrupted"
    t.join
  end

  it "is automatically retried when interrupted by SIGVTALRM" do
    t = Thread.new do
      client = @server.accept
      value = client.read(2)
      client.close
      value
    end

    Thread.pass while t.status and t.status != "sleep"
    # Thread#backtrace uses SIGVTALRM on TruffleRuby and potentially other implementations.
    # Sending a signal to a thread is not possible with Ruby APIs.
    t.backtrace.join("\n").should =~ /in [`'](?:TCPServer#)?accept'/

    socket = TCPSocket.new('127.0.0.1', @port)
    socket.write("OK")
    socket.close

    t.value.should == "OK"
  end

  it "raises an IOError if the socket is closed" do
    @server.close
    -> { @server.accept }.should raise_error(IOError)
  end
end

describe 'TCPServer#accept' do
  SocketSpecs.each_ip_protocol do |family, ip_address|
    before do
      @server = TCPServer.new(ip_address, 0)
    end

    after do
      @server.close
    end

    describe 'without a connected client' do
      it 'blocks the caller' do
        -> { @server.accept }.should block_caller
      end
    end

    describe 'with a connected client' do
      before do
        @client = TCPSocket.new(ip_address, @server.connect_address.ip_port)
      end

      after do
        @socket.close if @socket
        @client.close
      end

      it 'returns a TCPSocket' do
        @socket = @server.accept
        @socket.should be_an_instance_of(TCPSocket)
      end

      platform_is_not :windows do
        it "returns a TCPSocket which is set to nonblocking" do
          require 'io/nonblock'
          @socket = @server.accept
          @socket.should.nonblock?
        end
      end

      it "returns a TCPSocket which is set to close on exec" do
        @socket = @server.accept
        @socket.should.close_on_exec?
      end
    end
  end
end