I am trying to write fails and errors that occur in the test to a log file, so that they don't appear on-screen, but it appears as though errors and assert failures write to STDOUT instead of STDERR. I have been unable to find information on how to redirect this output after hours of googling, and would greatly appreciate help.
Why should the errors not be on stdout?
Up to now I have no prepared solution to suppress the output for specific errors.
If you accept the unchanged output, I have a solution to store errors and failures in a file. It will be no problem to create a 2nd file for Notifications...
gem 'test-unit'
require 'test/unit'
module Test
module Unit
class TestSuite
alias :old_run :run
def run(result, &progress_block)
old_run(result, &progress_block)
File.open('test.log', 'w'){|f|
result.faults.each{|err|
case err
when Test::Unit::Error, Test::Unit::Failure
f << err.long_display
f << "\n===========\n"
#not in log file
when Test::Unit::Pending, Test::Unit::Notification, Test::Unit::Omission
end
}
}
end
end
end
end
class MyTest < Test::Unit::TestCase
def test_1()
assert_equal( 3, 1+1) #failure
end
def test_2()
1 / 0 #force an error
end
def test_3()
notify 'sss'
end
def test_4()
pend "MeineKlasse.new"
end
def test_5
omit 'aaa' if RUBY_VERSION == '1.9.2'
end
def test_5
assert_in_delta( 0.1, 0.00001, 1.0/10)
end
end
Have you tried redirecting the output to StringIO, to write it to a logfile later?
original_stdout = $stdout
original_stderr = $stderr
fake_stdout = StringIO.new
fake_stderr = StringIO.new
$stdout = fake_stdout
$stderr = fake_stderr
Then after you've ran the tests:
$stdout = original_stdout
$stderr = original_stderr
#stdout = fake_stdout.string
#stderr = fake_stderr.string
I'm not really sure this will work though...
I hope I understood yor question correct.
Do you mean something like this:
gem 'test-unit'
require 'test/unit'
class StdOutLogger < IO
def initialize(*)
super
#file = File.open('log.txt', 'w')
#stdout = true
end
#write to stdout and file
def write(s)
$stdout << s
#file << s
end
end
STDOUT = StdOutLogger.new(1)
class MyTest < Test::Unit::TestCase
def test_1()
assert_equal( 2, 1+1)
assert_equal( 2, 4/2)
assert_equal( 1, 3/2)
assert_equal( 1.5, 3/2.0)
end
end
But I would recommend to copy stdout on operation system level
ruby mytest.rb > log.txt
Here is a version to skip between stdout and file output. The output switch is only a interim solution - perhaps it can be made better.
class StdOutLogger < IO
def initialize(*)
super
#file = File.open('log.txt', 'w')
#stdout = true
end
def write(s)
case s
when /\AError:/
#stdout = false #change to file
when /\A(Pending):/
#stdout = true #change to file
end
if #stdout
$stdout << s
else
#file << s
end
end
end
STDOUT = StdOutLogger.new(1)
Related
I have the following piece of code which I want to test using minitest:
def write(text, fname)
File.open(fname, 'w') do |f|
f << text
end
end
I wanted to mock the file using StringIO, so I did:
class TestWrite << Minitest::Test
def test_write
# arrange
m_file = StringIO.new
mock_open = Minitest::Mock.new
mock_open.expect :call, m_file, ['fname', 'w']
# act
File.stub :open, mock_open do
write('hello!', 'fname')
end
# assert
mock_open.verify # successful assert!
assert_equal 'hello!', m_file.string # failure :(
end
The second assert seem to fail because the code inside the do block of File.open never runs. Any idea what I'm doing wrong?
I am trying to emulate UNIX command line pipes in a Ruby-only solution that uses multiple cores. Eventually, the records piped from command to command will be Ruby objects marshaled using msgpack. Unfortunately, the below code hangs after the first dump command. I suspect a pipe deadlock, but I fail to resolve it?
#!/usr/bin/env ruby
require 'parallel'
require 'msgpack'
require 'pp'
class Pipe
def initialize
#commands = []
end
def add(command, options = {})
#commands << Command.new(command, options)
self
end
def run
#commands.each_cons(2) do |c_in, c_out|
reader, writer = IO.pipe
c_out.input = MessagePack::Unpacker.new(reader)
c_in.output = MessagePack::Packer.new(writer)
end
Parallel.each(#commands, in_processes: #commands.size) { |command| command.run }
self
end
class Command
attr_accessor :input, :output
def initialize(command, options)
#command = command
#options = options
#input = nil
#output = nil
end
def run
send #command
end
def cat
#input.each_with_index { |record, i| #output.write(record).flush } if #input
File.open(#options[:input]) do |ios|
ios.each { |record| #output.write(record).flush } if #output
end
end
def dump
#input.each do |record|
puts record
#output.write(record).flush if #output
end
end
end
end
p = Pipe.new
p.add(:cat, input: "foo.tab").add(:dump).add(:cat, input: "table.txt").add(:dump)
p.run
In order to handle a -q (quiet) option in my script, I did this:
class NoWrite
def write(*args)
end
end
$stdout = NoWrite.new if $options[:quiet]
It works fine.
However I'm wondering if there isn't a metaprogramming way of making the write method moot.
remove_method and undef_method can't be used because the write method has to be defined, I just want to make it do nothing.
Also, $stderr can't be impacted.
Alternatively, you can redirect stdout to /dev/null
def stfu
begin
orig_stdout = $stdout.clone
$stdout.reopen File.new('/dev/null', 'w')
retval = yield
rescue Exception => e
$stdout.reopen orig_stdout
raise e
ensure
$stdout.reopen orig_stdout
end
retval
end
require 'some_noisy_gem'
stfu do
some_function_that_generates_a_lot_of_cruft_on_stdout
end
Original snippet is at: https://jacob.hoffman-andrews.com/README/index.php/2011/04/14/ruby-function-stfu-temporarily-redirect-noisy-stdout-writes-to-devnull/
You can use instance_eval for this:
$stdout.instance_eval{ def write(*args); end } if $options[:quiet]
Or you can make an anonymous class:
$stdout = Class.new{ def self.write(*args); end } if $options[:quiet]
I've got some code that I cobbled together from hints found that worked. But something is going wrong, and I am baffled. Nothing is sent to the screen, and file is empty.
Here's the program:
#!/usr/bin/env ruby -w
require "stringio"
class Tee
def initialize
date_str = `date '+%Y%m%d_%H%M%S'`.chomp
#log = File.new("tee_output_example_#{date_str}.log","w")
end
["$stdout", "$stderr"].each do |std|
io = eval(std)
old_write = io.method(:write)
class << io
self
end.module_eval do
define_method(:write) do |text|
unless text =~ /^[\r\n]+$/ # Because puts calls twice.
File.open(#log, "a") do |f|
# f.puts [std[1..-1].upcase, caller[2], text].join(" ")
f.puts text
end
end
old_write.call(text)
end
end
end
end
logger = Tee.new()
logger.puts "text on stdout"
logger.puts "Something else"
$stdout = STDOUT
$stderr = STDERR
$stdout.puts "plain puts to $stdout"
$stderr.puts "plain puts to $stderr"
I managed to solve it simply with this command:
STDOUT.reopen IO.popen "tee stdout.log", "a"
STDERR.reopen IO.popen "tee stderr.log", "a"
Your expectations aren't very clear to me, but this seems to be a reasonable start (with several directions you could take it depending on your ultimate goal).
#!/usr/bin/env ruby -w
class Tee
attr_accessor :log_file
def initialize()
self.log_file = File.open "tee_output_example_#{date_str}.log", "w"
at_exit { log_file.close }
end
def date_str
Time.now.strftime "%Y%m%d_%H%M%S"
end
def puts(*strings)
log_file.puts(*strings)
$stdout.puts(*strings)
end
end
# will be sent to both $stdout and the logfile
logger = Tee.new
logger.puts "text on stdout"
logger.puts "Something else"
# will only be sent to $stdout or $stderr
$stdout.puts "plain puts to $stdout"
$stderr.puts "plain puts to $stderr"
I could give some tips on debugging techniques, but instead, I'll suggest an alternate solution which might meet your needs:
class Tee
def initialize(a,b); #a,#b = a,b; end
def method_missing(m,*args,&b)
#a.send(m,*args,&b)
#b.send(m,*args,&b)
end
end
This class is more generally useful than what you were trying to write; it takes 2 objects, and passes all method calls (including arguments and block) on to BOTH of them. So you could do something like:
tee = Tee.new(File.open("log","w"), $stdout)
tee.puts "Hello world AND log file!"
I have a testing library to assist in testing logging:
require 'stringio'
module RedirectIo
def setup
$stderr = #stderr = StringIO.new
$stdin = #stdin = StringIO.new
$stdout = #stdout = StringIO.new
super
end
def teardown
$stderr = STDERR
$stdin = STDIN
$stdout = STDOUT
super
end
end
It is meant to be used like so:
require 'lib/redirect_io'
class FooTest < Test::Unit::TestCase
include RedirectIo
def test_logging
msg = 'bar'
Foo.new.log msg
assert_match /^#{TIMESTAMP_REGEX} #{msg}$/, #stdout.string, 'log message'
end
end
Of course, I have a unit test for my test library. :)
require 'lib/redirect_io'
class RedirectIoTest < Test::Unit::TestCase
def test_setup_and_teardown_are_mixed_in
%W{setup teardown}.each do |method|
assert_not_equal self.class, self.class.new(__method__).method(method).owner, "owner of method #{method}"
end
self.class.class_eval { include RedirectIo }
%W{setup teardown}.each do |method|
assert_equal RedirectIo, self.class.new(__method__).method(method).owner, "owner of method #{method}"
end
end
def test_std_streams_captured
%W{STDERR STDIN STDOUT}.each do |stream_name|
assert_equal eval(stream_name), self.class.new(__method__).instance_eval("$#{stream_name.downcase}"), stream_name
end
self.class.class_eval { include RedirectIo }
setup
%W{STDERR STDIN STDOUT}.each do |stream_name|
assert_not_equal eval(stream_name), self.class.new(__method__).instance_eval("$#{stream_name.downcase}"),
stream_name
end
teardown
%W{STDERR STDIN STDOUT}.each do |stream_name|
assert_equal eval(stream_name), self.class.new(__method__).instance_eval("$#{stream_name.downcase}"), stream_name
end
end
end
I'm having trouble figuring out how to test that RedirectIo.setup and teardown call super. Any ideas?
I figured out one way to do it, which actually makes the test code much cleaner as well!
require 'lib/redirect_io'
class RedirectIoTest < Test::Unit::TestCase
class RedirectIoTestSubjectSuper < Test::Unit::TestCase
def setup
#setup = true
end
def teardown
#teardown = true
end
end
class RedirectIoTestSubject < RedirectIoTestSubjectSuper
include RedirectIo
def test_fake; end
end
def test_setup_and_teardown_are_mixed_in
%W{setup teardown}.each do |method|
assert_not_equal RedirectIoTestSubject, self.method(method).owner, "owner of method #{method}"
end
%W{setup teardown}.each do |method|
assert_equal RedirectIo, RedirectIoTestSubject.new(:test_fake).method(method).owner, "owner of method #{method}"
end
end
def test_std_streams_captured
obj = RedirectIoTestSubject.new(:test_fake)
$stderr = STDERR
$stdin = STDIN
$stdout = STDOUT
obj.setup
%W{STDERR STDIN STDOUT}.each do |stream_name|
assert_not_equal eval(stream_name), eval("$#{stream_name.downcase}"), stream_name
end
obj.teardown
%W{STDERR STDIN STDOUT}.each do |stream_name|
assert_equal eval(stream_name), eval("$#{stream_name.downcase}"), stream_name
end
end
def test_setup_and_teardown_call_super
obj = RedirectIoTestSubject.new(:test_fake)
obj.setup
assert obj.instance_eval{#setup}, 'super called?'
obj.teardown
assert obj.instance_eval{#teardown}, 'super called?'
end
end