Reassigning global $stdout to console - ruby - ruby

I am trying to set $stdout to write to a file temporarily and then back to a file.
test.rb :
old_stdout = $stdout
$stdout.reopen("mytestfile.out",'w+')
puts "this goes in mytestfile"
$stdout= old_stdout
puts "this should be on the console"
$stdout.reopen("mytestfile1.out",'w+')
puts "this goes in mytestfile1:"
$stdout = old_stdout
puts "this should be back on the console"
Here is the output.
ruby test.rb => no output on the console
cat mytestfile.out
this goes in mytestfile
this should be on the console
cat mytestfile1.out
this goes in mytestfile1:
this should be back on the console
I am not sure why $stdout is not getting reset to console ?

This problem can be resolved by calling dup on $stdout before changing it:
old_stdout = $stdout.dup
$stdout.reopen("mytestfile.out",'w+')
puts "this goes in mytestfile"
$stdout = old_stdout.dup
puts "this should be on the console"
$stdout.reopen("mytestfile1.out",'w+')
puts "this goes in mytestfile1:"
$stdout = old_stdout
puts "this should be back on the console"
Output:
ruby test.rb
# => this should be on the console
# => this should be back on the console
cat mytestfile.out
# => this goes in mytestfile
cat mytestfile1.out
# => this goes in mytestfile1
Here's how I usually package this functionality into a function:
# Runs a block of code while blocking stdout.
# Note that /dev/null should be changed to NUL on Windows.
def silence_stdout(log = '/dev/null')
old = $stdout.dup
$stdout.reopen(File.new(log, 'w'))
yield
$stdout = old
end
Usage:
silence_stdout 'mytestfile.out' do
puts "this goes in mytestfile"
end
puts "this should be on the console"
silence_stdout 'mytestfile1.out' do
puts "this goes in mytestfile1"
end
puts "this should be back on the console"
Edit: as another poster mentioned, using reopen is only necessary when working with pure Ruby code. The function above works both with pure Ruby code and when using, for example, C extensions that write to STDOUT.

You don't need to use reopen if you're just using Ruby code. puts and other Ruby methods will use the current value of $stdout so you can just reassign it.
old_stdout = $stdout
$stdout = File.new("mytestfile.out",'w+')
puts "this goes in mytestfile"
$stdout = old_stdout
puts "this should be on the console"
$stdout = File.new("mytestfile1.out",'w+')
puts "this goes in mytestfile1:"
$stdout = old_stdout
puts "this should be back on the console"
You only need to use reopen if you're doing something like creating child processes (e.g. with fork) and want the child's output to go elsewhere, or if you have an extension that writes directly to standard out without using Ruby's $stdout global.
In your code, when you call reopen you are redirecting both $stdout and old_stdout, as they are both just references to the same IO object, which is why you aren't getting output back to the console when you assign old_stdout back to stdout.

Related

How can I use IO#reopen with a StringIO?

I would like to capture stderr into a variable in memory, without using a file on the filesystem. (This is because if the process is kill -9'ed, the file, even if it is a Tempfile, will not be deleted.)
There is a solution for this at How do I temporarily redirect stderr in Ruby?, but its strategy is to assign a StringIO to $stderr. This will not work if the value of $stderr was copied prior to the reassignment, nor will it work if STDERR is used. As evidence:
#!/usr/bin/env ruby
stderr_sav = $stderr
$stderr = File.open(File::NULL, 'w')
$stderr.puts 'Using $stderr'
stderr_sav.puts 'Using stderr_sav'
STDERR.puts 'Using STDERR'
# Outputs:
# Using stderr_sav
# Using STDERR
In contrast, using reopen works:
#!/usr/bin/env ruby
stderr_sav = $stderr
$stderr.reopen(File.new(File::NULL, 'w'))
$stderr.puts 'Using $stderr'
stderr_sav.puts 'Using stderr_sav'
STDERR.puts 'Using STDERR'
# Outputs:
# [nothing]
Unfortunately, passing a StringIO to reopen does not work:
`reopen': no implicit conversion of StringIO into String (TypeError)
Is there any way to accomplish the capturing of $stderr without using a file?
How about a proxy which temporary delegate IO#puts to StringIO#puts
stderr_sav = $stderr
# delegate
$stdclone = $stderr.clone
$temp = StringIO.new
[:puts].each do |m|
if $temp.respond_to?(m)
$stderr.define_singleton_method "#{m}" do |*args, &block|
$temp.send(m, *args, &block)
end
end
end
# test
$stderr.puts 'secret1' # nothing
STDERR.puts 'secret2' # nothing
stderr_sav.puts 'secret3' # nothing
new_strerr = IO.new(1)
new_strerr.puts "not secret" # not secret
$stdclone.puts $temp.string # secret 1 -> 3
# reset
$stderr = $stdclone
$stderr.puts "secret4" # secret4

Trace $stdout/$stderr in Ruby

I'm attempting to test the output of my gem that is using GLI however I'm running into issues. In my test setup I'm redirecting $stdout and $stderr to StringIO instances however when I run my code I'm still getting a message printed to my screen rather than being redirected to my custom objects.
begin
old_stdout, old_stderr, $stdout, $stderr = $stdout, $stderr, StringIO.new, StringIO.new
#exit_code = described_class.run([command] + command_args)
#stdout = $stdout.string
#stderr = $stderr.string
ensure
$stdout = old_stdout
$stderror = old_stderr
end
I would expect that described_class.run([command] + command_args) which ends up calling GLI's exit_now! method would be redirected to $stderr however this does not seem to be the case.
Is there a way to "trace" where/how a string was printed to the screen?
It looks like you're using RSpec based on described_class. If you're just trying to test stdout/stderr, then there's a matcher for that.
it "does something" do
expect {
described_class.run([command] + command_args)
}.to output(/some string match/).to_stdout
end
The owner of the GLI project made some suggestions here about how to test GLI setup, perhaps that will help as well.

Suppress console output during RSpec tests

I am testing the class which put on the console some messages (with puts, p warnings and etc.). I am just wondering if there is any ability to suppress this output during RSpec tests ?
I suppress puts output in my classes by redirecting $stout to a text file. That way, if I need to see the output for any reason, it is there but it doesn't muddy up my test results.
#spec_helper.rb
RSpec.configure do |config|
config.before(:all, &:silence_output)
config.after(:all, &:enable_output)
end
public
# Redirects stderr and stout to /dev/null.txt
def silence_output
# Store the original stderr and stdout in order to restore them later
#original_stderr = $stderr
#original_stdout = $stdout
# Redirect stderr and stdout
$stderr = File.new(File.join(File.dirname(__FILE__), 'dev', 'null.txt'), 'w')
$stdout = File.new(File.join(File.dirname(__FILE__), 'dev', 'null.txt'), 'w')
end
# Replace stderr and stdout so anything else is output correctly
def enable_output
$stderr = #original_stderr
$stdout = #original_stdout
#original_stderr = nil
#original_stdout = nil
end
EDIT:
In response to the comment by #MyronMarston, it probably would be smarter to just insert the methods directly into before and after as blocks.
#spec_helper.rb
RSpec.configure do |config|
original_stderr = $stderr
original_stdout = $stdout
config.before(:all) do
# Redirect stderr and stdout
$stderr = File.new(File.join(File.dirname(__FILE__), 'dev', 'null.txt'), 'w')
$stdout = File.new(File.join(File.dirname(__FILE__), 'dev', 'null.txt'), 'w')
end
config.after(:all) do
$stderr = original_stderr
$stdout = original_stdout
end
end
It looks a little cleaner and keeps methods off of main.
Also, note that if you are using Ruby 2.0, you can use __dir__ instead of File.dirname(__FILE__).
EDIT2
Also it should be mentioned, that you can forward to true os /dev/null by using File::NULL as it was introduced in Ruby v 1.9.3. (jruby 1.7)
Then the code snippet will look as following:
#spec_helper.rb
RSpec.configure do |config|
original_stderr = $stderr
original_stdout = $stdout
config.before(:all) do
# Redirect stderr and stdout
$stderr = File.open(File::NULL, "w")
$stdout = File.open(File::NULL, "w")
end
config.after(:all) do
$stderr = original_stderr
$stdout = original_stdout
end
end
Try stubbing methods that make the output in a before block, e.g.
before do
IO.any_instance.stub(:puts) # globally
YourClass.any_instance.stub(:puts) # or for just one class
end
This is explicit, so you won't miss anything you don't want to miss. If you don't care about any output and the method above doesn't work you can always stub the IO object itself:
before do
$stdout.stub(:write) # and/or $stderr if needed
end
An Rspec3.0 Version would be => in spec_helper.rb
RSpec.configure do |c|
c.before { allow($stdout).to receive(:puts) }
end
it will act as before(:each)
but :each is default, so no need to write it explicitly
Tested with rspec-core (~> 3.4.0)
In describe block you could do
# spec_helper.rb
def suppress_log_output
allow(STDOUT).to receive(:puts) # this disables puts
logger = double('Logger').as_null_object
allow(Logger).to receive(:new).and_return(logger)
end
# some_class_spec.rb
RSpec.describe SomeClass do
before do
suppress_log_output
end
end
This way you have the advantage of toggling log output for specific tests. Note, this will not suppress rspec warnings, or messages from rspec.
Another way to disable warnings coming from gems:
add config.warnings = false to spec_helper
If you wanted to suppress only certain logger methods, like error, info, or warn you could do
allow_any_instance_of(Logger).to receive(:warn).and_return(nil)
To disable warnings coming from the rspec gem
allow(RSpec::Support).to receive(:warning_notifier).and_return(nil)
but this is generally discouraged because it is meant as a way to let you know you are doing something smelly in your tests.
If you want to suppress output for a single test, there is a more concise way:
it "should do something with printing" do
silence_stream(STDOUT) do
foo.print.should be_true
end
end
You may want to change STDOUT to STDERR if your test prints an error.
Updated answer for Rails 5, in a one-off situation:
before do
RSpec::Mocks.with_temporary_scope do
allow(STDOUT).to receive(:puts)
end
end
You can make this into a method in spec_helper if you'll be doing this a lot.
After trying all of these examples, I ended up using this varation which does not silence or mute binding.pry
# frozen_string_literal: true
RSpec.configure do |config|
config.before(:each) do
allow($stdout).to receive(:puts)
allow($stdout).to receive(:write)
end
end
It can be useful to inject an IO object defaulting to STDOUT. This also makes it easier to assert on the output if you want to.
E.g.
def my_method(arg, io: STDOUT)
io.puts "hello"
arg.reverse
end
And then in your test:
# Suppress it.
my_method("hi", io: StringIO.new)
# Assert on it.
io = StringIO.new
my_method("hi", io: io)
output = io.tap(&:rewind).read
expect(output).to include("hello")
You could have the object itself supress based on the environment:
class Foo
def call
puts("blah blah")
# ...
end
private
def puts(msg)
return if ENV['APP_ENV'] == 'test'
super
end
end

Test output to command line with RSpec

I want to do is run ruby sayhello.rb on the command line, then receive Hello from Rspec.
I've got that with this:
class Hello
def speak
puts 'Hello from RSpec'
end
end
hi = Hello.new #brings my object into existence
hi.speak
Now I want to write a test in rspec to check that the command line output is in fact "Hello from RSpec"
and not "I like Unix"
NOT WORKING. I currently have this in my sayhello_spec.rb file
require_relative 'sayhello.rb' #points to file so I can 'see' it
describe "sayhello.rb" do
it "should say 'Hello from Rspec' when ran" do
STDOUT.should_receive(:puts).with('Hello from RSpec')
end
end
Can someone point me in the right direction please?
Here's a pretty good way to do this. Copied from the hirb test_helper source:
def capture_stdout(&block)
original_stdout = $stdout
$stdout = fake = StringIO.new
begin
yield
ensure
$stdout = original_stdout
end
fake.string
end
Use like this:
output = capture_stdout { Hello.new.speak }
output.should == "Hello from RSpec\n"
The quietly command is probably what you want (cooked into ActiveSupport, see docs at api.rubyonrails.org). This snippet of RSpec code below shows how to ensure there is no output on stderr while simultaneously silencing stdout.
quietly do # silence everything
commands.each do |c|
content = capture(:stderr) { # capture anything sent to :stderr
MyGem::Cli.start(c)
}
expect(content).to be_empty, "#{c.inspect} had output on stderr: #{content}"
end
end
So you don't have to change your main ruby code I just found out you can do something like this:
def my_meth
print 'Entering my method'
p 5 * 50
puts 'Second inside message'
end
describe '#my_meth' do
it 'puts a 2nd message to the console' do
expect{my_meth}.to output(/Second inside message/).to_stdout
end
end
When checking for a desired output text I used it inside / / like a Regexp because after many many maaany tests and looking around, the STDOUT is everything that is outputted so I found it to be better to use Regex so you could check the whole STDOUT for the exact text that you want.
Like I put it, it works in the terminal just perfect.
//Just had to share this, it took me days to figure it out.
it "should say 'Hello from Rspec' when run" do
output = `ruby sayhello.rb`
output.should == 'Hello from RSpec'
end

How can I copy STDOUT to a file without stopping it showing onscreen using Ruby

I'm attempting to copy stdout to a file for logging purposes. I also want it to display in the Ruby console of the IDE I'm using.
I inserted this code into my script and it redirects $stdout to the my.log file:
$stdout.reopen("my.log", "w")
Does anyone know of a gem or technique to copy the contents of $stdout to a file and not redirect it to a file? Also, I am not using Rails just Ruby.
Something like this might help you:
class TeeIO < IO
def initialize orig, file
#orig = orig
#file = file
end
def write string
#file.write string
#orig.write string
end
end
Most of the methods in IO that do output ultimately use write, so you only have to override this one method. You can use it like this:
#setup
tee = TeeIO.new $stdout, File.new('out.txt', 'w')
$stdout = tee
# Now lots of example uses:
puts "Hello"
$stdout.puts "Extending IO allows us to expicitly use $stdout"
print "heres", :an, :example, "using", 'print', "\n"
48.upto(57) do |i|
putc i
end
putc 10 #newline
printf "%s works as well - %d\n", "printf", 42
$stdout.write "Goodbye\n"
After this example, the following is written identically to both the console and to the file:
Hello
Extending IO allows us to expicitly use $stdout
heresanexampleusingprint
0123456789
printf works as well - 42
Goodbye
I won't claim this technique is fail proof, but it should work for simple uses of stdout. Test it for your use.
Note that you don't have to use reopen on $stdout unless you want to redirect output from a child process or an uncooperative extension. Simply assigning a different IO object to it will work for most uses.
RSpec
The RSpec command line takes a reference to $stdout before you can get any code to run to reassign it, so this doesn't work. reopen still works in this case as you're changing the actual object pointed to by both $stdout and the reference that RSpec has, but this doesn't give you output to both.
One solution is to monkey-patch $stdout like this:
$out_file = File.new('out.txt', 'w')
def $stdout.write string
$out_file.write string
super
end
This works, but as with all monkey patching, be careful. It would be safer to use your OS's tee command.
If you are using Linux or Mac OS, the tee command available in the OS makes it easy to do this. From its man page:
NAME
tee -- pipe fitting
SYNOPSIS
tee [-ai] [file ...]
DESCRIPTION
The tee utility copies standard input to standard output, making a copy in zero or more files. The output is unbuffered.
So something like:
echo '>foo bar' | tee tmp.out
>foo bar
echos the output to STDOUT and to the file. Catting the file gives me:
cat tmp.out
>foo bar
Otherwise, if you want to do it inside your code, it's a simple task:
def tee_output(logfile)
log_output = File.open(logfile, 'w+')
->(o) {
log_output.puts o
puts o
}
end
tee = tee_output('tmp.out')
tee.call('foo bar')
Running it:
>ruby test.rb
foo bar
And checking the output file:
>cat tmp.out
foo bar
I'd use "w+" for my file access to append to the output file, rather than over-write it.
CAVEAT: This opens the file and leaves it open during the life of the code after you've called the tee_output method. That bothers some people, but, personally, it doesn't bother me because Ruby will close the file when the script exits. In general we want to close files as soon as we're done with them, but in your code, it makes more sense to open it and leave it open, than to repeatedly open and close the output file, but your mileage might vary.
EDIT:
For Ruby 1.8.7, use lambda instead of the new -> syntax:
def tee_output(logfile)
log_output = File.open(logfile, 'w+')
lambda { |o|
log_output.puts o
puts o
}
end
tee = tee_output('tmp.out')
tee.call('foo bar')
I know it's a old question, but I found myself in the same situation. I wrote a Multi-IO Class which extends File and overrode write puts and close methods, I also made sure its thread safe:
require 'singleton'
class MultiIO < File
include Singleton
##targets = []
##mutex = Mutex.new
def self.instance
self.open('/dev/null','w+')
end
def puts(str)
write "#{str}\n"
end
def write(str)
##mutex.synchronize do
##targets.each { |t| t.write str; flush }
end
end
def setTargets(targets)
raise 'setTargets is a one-off operation' unless ##targets.length < 1
targets.each do |t|
##targets.push STDOUT.clone if t == STDOUT
##targets.push STDERR.clone if t == STDERR
break if t == STDOUT or t == STDERR
##targets.push(File.open(t,'w+'))
end
self
end
def close
##targets.each {|t| f.close}
end
end
STDOUT.reopen MultiIO.instance.setTargets(['/tmp/1.log',STDOUT,STDERR])
STDERR.reopen STDOUT
threads = []
5.times.each do |i|
threads.push(
Thread.new do
10000.times.each do |j|
STDOUT.puts "out#{i}:#{j}"
end
end
)
end
5.times.each do |i|
threads.push(
Thread.new do
10000.times.each do |j|
STDERR.puts "err#{i}:#{j}"
end
end
)
end
threads.each {|t| t.join}
Tiny improvement from matts answer:
class TeeIO < IO
def initialize(ios)
#ios = ios
end
def write(string)
#ios.each { |io| io.write string }
end
end

Resources