require 'spec_helper'
require 'r10k/util/subprocess/runner/pump'

describe R10K::Util::Subprocess::Runner::Pump do

  let(:pair) { IO.pipe }
  let(:r) { pair.first }
  let(:w) { pair.last }

  after do
    pair.each { |io| io.close unless io.closed? }
  end

  subject { described_class.new(r) }

  it "returns an empty string if nothing has been read" do
    expect(subject.string).to eq('')
  end

  describe "reading all data in the stream" do
    it "reads data until the stream reaches EOF" do
      subject.start
      w << "hello"
      w << " "
      w << "world!"
      w.close
      subject.wait
      expect(subject.string).to eq("hello world!")
    end
  end

  describe "halting" do
    it "does not read any more information read off the pipe" do
      subject.min_delay = 0.01
      subject.start
      w << "hello"

      # This should ensure that we yield to the pumping thread. If this test
      # sporadically fails then we may need to increase the timeout.
      sleep 0.1
      subject.halt!
      w << " world!"

      expect(subject.string).to eq("hello")
    end
  end

  describe "backing off" do
    it "does not back off more than the max delay time" do
      max_delay = subject.min_delay * 2
      subject.max_delay = max_delay
      subject.start
      sleep max_delay * 2

      Timeout.timeout(max_delay * 1.5) do
        subject.halt!
      end

    end
  end

  # Linux 2.6.11+ has a maximum pipe capacity of 64 KiB, and writing to the
  # pipe when the pipe is at capacity will block. To make sure the pump is
  # actively removing contents from the pipe we need to attempt to fill up
  # the entire pipe.
  #
  # See man pipe(7)
  it "does not block if more than 64 kilobytes are fed into the pipe" do
    # The maximum pipe buffer size is 2 ** 16 bytes, so that's the minimum
    # amount of data needed to cause further writes to block. We then double
    # this value to make sure that we are continuously emptying the pipe.
    pipe_buffer_size = 2 ** 17
    blob = "buffalo!" * pipe_buffer_size
    subject.start
    Timeout.timeout(60) { w << blob }
    w.close
    subject.wait
  end
end
