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
