# Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: BUSL-1.1 # -*- coding: utf-8 -*- require File.expand_path("../../../base", __FILE__) require 'vagrant/util/io' describe Vagrant::Util::IO do describe ".read_until_block" do let(:io) { double("io") } before do # Ensure that we don't get stuck in a loop allow(io).to receive(:read_nonblock).and_raise(EOFError) allow(io).to receive(:readpartial).and_raise(EOFError) end context "on non-Windows system" do before { allow(Vagrant::Util::Platform).to receive(:windows?). and_return(false) } it "should use a non-blocking read" do expect(io).to receive(:read_nonblock).and_return("") described_class.read_until_block(io) end it "should receive data until breakable event" do expect(io).to receive(:read_nonblock).and_return("one") expect(io).to receive(:read_nonblock).and_return("two") expect(io).to receive(:read_nonblock).and_return("three") data = described_class.read_until_block(io) expect(data).to eq("onetwothree") end context "with breakable errors" do [EOFError, Errno::EAGAIN, IO::EAGAINWaitReadable, IO::EINPROGRESSWaitReadable, IO::EWOULDBLOCKWaitReadable].each do |err_class| it "should break without error on #{err_class}" do expect(io).to receive(:read_nonblock).and_raise(err_class) expect(described_class.read_until_block(io)).to be_empty end end end context "with non-breakable errors" do it "should raise the error" do expect(io).to receive(:read_nonblock).and_raise(StandardError) expect { described_class.read_until_block(io) }.to raise_error(StandardError) end end end context "on Windows system" do before do allow(Vagrant::Util::Platform).to receive(:windows?). and_return(true) allow(IO).to receive(:select).with([io], any_args). and_return([io]) allow(io).to receive(:empty?).and_return(false) end it "should use select" do expect(IO).to receive(:select).with([io], any_args) described_class.read_until_block(io) end it "should receive data until breakable event" do expect(io).to receive(:readpartial).and_return("one") expect(io).to receive(:readpartial).and_return("two") expect(io).to receive(:readpartial).and_return("three") data = described_class.read_until_block(io) expect(data).to eq("onetwothree") end context "with breakable errors" do [EOFError, Errno::EAGAIN, IO::EAGAINWaitReadable, IO::EINPROGRESSWaitReadable, IO::EWOULDBLOCKWaitReadable].each do |err_class| it "should break without error on #{err_class}" do expect(io).to receive(:readpartial).and_raise(err_class) expect(described_class.read_until_block(io)).to be_empty end end end context "with non-breakable errors" do it "should raise the error" do expect(io).to receive(:readpartial).and_raise(StandardError) expect { described_class.read_until_block(io) }.to raise_error(StandardError) end end context "encoding" do let(:output) { "output".force_encoding("ASCII-8BIT") } before { expect(io).to receive(:readpartial).and_return(output) } it "should encode output to UTF-8" do expect(described_class.read_until_block(io).encoding.name).to eq("UTF-8") end context "when output includes characters with undefined conversion" do let(:output) { "output\xFF".force_encoding("ASCII-8BIT") } before { expect(Encoding).to receive(:default_external). and_return(Encoding.find("ASCII-8BIT")) } it "should return data with invalid characters replaced" do expect(described_class.read_until_block(io)).to include("�") end end end end end end