summaryrefslogtreecommitdiff
path: root/spec/ruby/core/io/select_spec.rb
blob: 3893e7620f9e103346cfbd09b4c1e639805fcdac (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
require_relative '../../spec_helper'

describe "IO.select" do
  before :each do
    @rd, @wr = IO.pipe
  end

  after :each do
    @rd.close unless @rd.closed?
    @wr.close unless @wr.closed?
  end

  it "blocks for duration of timeout and returns nil if there are no objects ready for I/O" do
    IO.select([@rd], nil, nil, 0.001).should == nil
  end

  it "returns immediately all objects that are ready for I/O when timeout is 0" do
    @wr.syswrite("be ready")
    IO.pipe do |_, wr|
      result = IO.select [@rd], [wr], nil, 0
      result.should == [[@rd], [wr], []]
    end
  end

  it "returns nil after timeout if there are no objects ready for I/O" do
    result = IO.select [@rd], nil, nil, 0
    result.should == nil
  end

  it "returns supplied objects when they are ready for I/O" do
    main = Thread.current
    t = Thread.new {
      Thread.pass until main.status == "sleep"
      @wr.write "be ready"
    }
    result = IO.select [@rd], nil, nil, nil
    result.should == [[@rd], [], []]
    t.join
  end

  it "leaves out IO objects for which there is no I/O ready" do
    @wr.write "be ready"
    platform_is :aix do
      # In AIX, when a pipe is readable, select(2) returns the write side
      # of the pipe as "readable", even though you cannot actually read
      # anything from the write side.
      result = IO.select [@wr, @rd], nil, nil, nil
      result.should == [[@wr, @rd], [], []]
    end
    platform_is_not :aix do
      # Order matters here. We want to see that @wr doesn't expand the size
      # of the returned array, so it must be 1st.
      result = IO.select [@wr, @rd], nil, nil, nil
      result.should == [[@rd], [], []]
    end
  end

  it "returns supplied objects correctly when monitoring the same object in different arrays" do
    filename = tmp("IO_select_pipe_file")
    io = File.open(filename, 'w+')
    result = IO.select [io], [io], nil, 0
    result.should == [[io], [io], []]
    io.close
    rm_r filename
  end

  it "returns the pipe read end in read set if the pipe write end is closed concurrently" do
    main = Thread.current
    t = Thread.new {
      Thread.pass until main.stop?
      @wr.close
    }
    IO.select([@rd]).should == [[@rd], [], []]
  ensure
    t.join
  end

  it "invokes to_io on supplied objects that are not IO and returns the supplied objects" do
    # make some data available
    @wr.write("foobar")

    obj = mock("read_io")
    obj.should_receive(:to_io).at_least(1).and_return(@rd)
    IO.select([obj]).should == [[obj], [], []]

    IO.pipe do |_, wr|
      obj = mock("write_io")
      obj.should_receive(:to_io).at_least(1).and_return(wr)
      IO.select(nil, [obj]).should == [[], [obj], []]
    end
  end

  it "raises TypeError if supplied objects are not IO" do
    -> { IO.select([Object.new]) }.should raise_error(TypeError)
    -> { IO.select(nil, [Object.new]) }.should raise_error(TypeError)

    obj = mock("io")
    obj.should_receive(:to_io).any_number_of_times.and_return(nil)

    -> { IO.select([obj]) }.should raise_error(TypeError)
    -> { IO.select(nil, [obj]) }.should raise_error(TypeError)
  end

  it "raises a TypeError if the specified timeout value is not Numeric" do
    -> { IO.select([@rd], nil, nil, Object.new) }.should raise_error(TypeError)
  end

  it "raises TypeError if the first three arguments are not Arrays" do
    -> { IO.select(Object.new)}.should raise_error(TypeError)
    -> { IO.select(nil, Object.new)}.should raise_error(TypeError)
    -> { IO.select(nil, nil, Object.new)}.should raise_error(TypeError)
  end

  it "raises an ArgumentError when passed a negative timeout" do
    -> { IO.select(nil, nil, nil, -5)}.should raise_error(ArgumentError)
  end

  describe "returns the available descriptors when the file descriptor" do
    it "is in both read and error arrays" do
      @wr.write("foobar")
      result = IO.select([@rd], nil, [@rd])
      result.should == [[@rd], [], []]
    end

    it "is in both write and error arrays" do
      result = IO.select(nil, [@wr], [@wr])
      result.should == [[], [@wr], []]
    end

    it "is in both read and write arrays" do
      filename = tmp("IO_select_read_write_file")
      w = File.open(filename, 'w+')
      begin
        IO.select([w], [w], []).should == [[w], [w], []]
      ensure
        w.close
        rm_r filename
      end

      IO.select([@wr], [@wr], []).should == [[], [@wr], []]

      @wr.write("foobar")
      # CRuby on macOS returns [[@rd], [@rd], []], weird but we accept it here, probably only for pipe read-end
      [
        [[@rd], [], []],
        [[@rd], [@rd], []]
      ].should.include? IO.select([@rd], [@rd], [])
    end
  end
end

describe "IO.select when passed nil for timeout" do
  it "sleeps forever and sets the thread status to 'sleep'" do
    t = Thread.new do
      IO.select(nil, nil, nil, nil)
    end

    Thread.pass while t.status && t.status != "sleep"
    t.join unless t.status
    t.status.should == "sleep"
    t.kill
    t.join
  end
end