core: allow IO redirection of UI for testing

Use of $stdin, $stdout, and $stderr globals makes testing difficult. By
exposing the IO objects as writable attributes, input/output can be more
easily simulated using StringIO or doubles.
This commit is contained in:
Dan Duvall 2015-03-04 11:09:34 -08:00
parent a8dcf92f14
commit 3b8bc2a433
2 changed files with 36 additions and 9 deletions

View File

@ -21,9 +21,22 @@ module Vagrant
# specific. See the implementation for more docs. # specific. See the implementation for more docs.
attr_accessor :opts attr_accessor :opts
# @return [IO] UI input. Defaults to `$stdin`.
attr_accessor :stdin
# @return [IO] UI output. Defaults to `$stdout`.
attr_accessor :stdout
# @return [IO] UI error output. Defaults to `$stderr`.
attr_accessor :stderr
def initialize def initialize
@logger = Log4r::Logger.new("vagrant::ui::interface") @logger = Log4r::Logger.new("vagrant::ui::interface")
@opts = {} @opts = {}
@stdin = $stdin
@stdout = $stdout
@stderr = $stderr
end end
def initialize_copy(original) def initialize_copy(original)
@ -132,7 +145,7 @@ module Vagrant
super(message) super(message)
# We can't ask questions when the output isn't a TTY. # We can't ask questions when the output isn't a TTY.
raise Errors::UIExpectsTTY if !$stdin.tty? && !Vagrant::Util::Platform.cygwin? raise Errors::UIExpectsTTY if !@stdin.tty? && !Vagrant::Util::Platform.cygwin?
# Setup the options so that the new line is suppressed # Setup the options so that the new line is suppressed
opts ||= {} opts ||= {}
@ -144,11 +157,11 @@ module Vagrant
say(:info, message, opts) say(:info, message, opts)
input = nil input = nil
if opts[:echo] if opts[:echo] || !@stdin.respond_to?(:noecho)
input = $stdin.gets input = @stdin.gets
else else
begin begin
input = $stdin.noecho(&:gets) input = @stdin.noecho(&:gets)
# Output a newline because without echo, the newline isn't # Output a newline because without echo, the newline isn't
# echoed either. # echoed either.
@ -206,7 +219,7 @@ module Vagrant
# Determine the proper IO channel to send this message # Determine the proper IO channel to send this message
# to based on the type of the message # to based on the type of the message
channel = type == :error || opts[:channel] == :error ? $stderr : $stdout channel = type == :error || opts[:channel] == :error ? @stderr : @stdout
# Output! We wrap this in a lock so that it safely outputs only # Output! We wrap this in a lock so that it safely outputs only
# one line at a time. We wrap this in a thread because as of Ruby 2.0 # one line at a time. We wrap this in a thread because as of Ruby 2.0

View File

@ -40,23 +40,37 @@ describe Vagrant::UI::Basic do
subject.output("foo", new_line: false) subject.output("foo", new_line: false)
end end
it "outputs to stdout" do it "outputs to the assigned stdout" do
stdout = StringIO.new
subject.stdout = stdout
expect(subject).to receive(:safe_puts).with { |message, **opts| expect(subject).to receive(:safe_puts).with { |message, **opts|
expect(opts[:io]).to be($stdout) expect(opts[:io]).to be(stdout)
true true
} }
subject.output("foo") subject.output("foo")
end end
it "outputs to stderr for errors" do it "outputs to stdout by default" do
expect(subject.stdout).to be($stdout)
end
it "outputs to the assigned stderr for errors" do
stderr = StringIO.new
subject.stderr = stderr
expect(subject).to receive(:safe_puts).with { |message, **opts| expect(subject).to receive(:safe_puts).with { |message, **opts|
expect(opts[:io]).to be($stderr) expect(opts[:io]).to be(stderr)
true true
} }
subject.error("foo") subject.error("foo")
end end
it "outputs to stderr for errors by default" do
expect(subject.stderr).to be($stderr)
end
end end
context "#detail" do context "#detail" do