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
Related
There has to be a better way to do this:
require 'log4r'
class PaddedLogger
attr_accessor :logger, :padding
def initialize(args)
#logger = Log4r::Logger.new args[:name]
#padding = args[:padding]
end
def debug(system, message)
system = "[#{system}]"
#logger.debug "#{system.ljust(#padding)}#{message}"
end
def info(system, message)
system = "[#{system}]"
#logger.info "#{system.ljust(#padding)}#{message}"
end
def warn(system, message)
system = "[#{system}]"
#logger.warn "#{system.ljust(#padding)}#{message}"
end
def error(system, message)
system = "[#{system}]"
#logger.error "#{system.ljust(#padding + 3)}#{message}"
end
def fatal(system, message)
system = "[#{system}]"
#logger.fatal "#{system.ljust(#padding + 3)}#{message}"
end
end
It's just a wrapping class to help me get my log4r logs appearing in a specific format, and it works exactly as I need it too, but surely Ruby's meta-programming magic can simplify this so I don't have to constantly repeat myself.
Something like this maybe?
require 'log4r'
class PaddedLogger
attr_accessor :logger, :padding
def initialize(args)
#logger = Log4r::Logger.new args[:name]
#padding = args[:padding]
end
[:debug, :info, :warn, :error, :fatal].each do |reason|
define_method reason do |system, message|
system = "[#{system}]"
#logger.send reason, "#{system.ljust(#padding)}#{message}"
end
end
end
And while you're at it, why not collapse the two lines in the define_method block to one?
#logger.send reason, "[#{system}]".ljust(#padding) + message
Here is and alternative solution using method_missing:
class TestClass
##methods = [:upcase, :other_methods_to_catch]
def initialize
#message = 'hello'
end
def method_missing name, *args
super unless ##methods.include? name
#message.send(name, *args)
end
end
x = TestClass.new
p x.upcase #=> HELLO
So witness and observe the following code, my questions is why do i never make it to the on_connect after starting the cool.io loop in send_to_server, the l.run should fire off the request as per the documented example on the github, and how the code handles incoming connections in module Server #socket.attach(l)
l.run
which does work and accepts the incoming data and sends it to my parser, which does work and fires off all the way up until the aforementioned send_to_server. So what is going on here?
require 'cool.io'
require 'http/parser'
require 'uri'
class Hash
def downcase_key
keys.each do |k|
store(k.downcase, Array === (v = delete(k)) ? v.map(&:downcase_key) : v)
end
self
end
end
module ShadyProxy
extend self
module ClientParserCallbacks
extend self
def on_message_complete(conn)
lambda do
puts "on_message_complete"
PluginHooks.before_request_to_server(conn)
end
end
def on_headers_complete(conn)
lambda do |headers|
conn.headers = headers
end
end
def on_body(conn)
lambda do |chunk|
conn.body << chunk
end
end
end
module PluginHooks
extend self
def before_request_to_server(conn)
# modify request here
conn.parser.headers.delete "Proxy-Connection"
conn.parser.headers.downcase_key
send_to_server(conn)
end
def send_to_server(conn)
parser = conn.parser
uri = URI::parse(parser.request_url)
l = Coolio::Loop.default
puts uri.scheme + "://" + uri.host
c = ShadyHttpClient.connect(uri.scheme + "://" + uri.host,uri.port).attach(l)
c.connection_reference = conn
c.request(parser.http_method,uri.request_uri)
l.run
end
def before_reply_to_client(conn)
end
end
class ShadyHttpClient < Coolio::HttpClient
def connection_reference=(conn)
puts "haz conneciton ref"
#connection_reference = conn
end
def connection_reference
#connection_reference
end
def on_connect
super
#never gets here
#headers = nil
#body = ''
#buffer = ''
end
def on_connect_failed
super
# never gets here either
end
def on_response_header(header)
#headers = header
end
def on_body_data(data)
puts "on data?"
#body << data
STDOUT.write data
end
def on_request_complete
puts "Headers"
puts #headers
puts "Body"
puts #body
end
def on_error(reason)
STDERR.puts "Error: #{reason}"
end
end
class ShadyProxyConnection < Cool.io::TCPSocket
attr_accessor :headers, :body, :buffer, :parser
def on_connect
#headers = nil
#body = ''
#buffer = ''
#parser = Http::Parser.new
#parser.on_message_complete = ClientParserCallbacks.on_message_complete(self)
#parser.on_headers_complete = ClientParserCallbacks.on_headers_complete(self)
#parser.on_body = ClientParserCallbacks.on_body(self)
end
def on_close
puts "huh?"
end
def on_read(data)
#buffer << data
#parser << data
end
end
module Server
def run(opts)
begin
# Start our server to handle connections (will raise things on errors)
l = Coolio::Loop.new
#socket = Cool.io::TCPServer.new(opts[:host],opts[:port], ShadyProxy::ShadyProxyConnection)
#socket.attach(l)
l.run
# Handle every request in another thread
loop do
Thread.new s = #socket.accept
end
# CTRL-C
rescue Interrupt
puts 'Got Interrupt..'
# Ensure that we release the socket on errors
ensure
if #socket
#socket.close
puts 'Socked closed..'
end
puts 'Quitting.'
end
end
module_function :run
end
end
ShadyProxy::Server.run(:host => '0.0.0.0',:port => 1234)
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)
I want to be able to define a block, and later evaluate that block from within a dynamically generated module/class. It seems like I could accomplish this somehow using eval and block.binding, but I haven't figured it out.
I have this as the base:
def define_module(name, &block)
name = name.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
parts = name.split("::")
parts.each_with_index do |part, index|
sub_name = parts[0..index].join("::")
eval("module #{sub_name}; end")
end
clazz = eval(name)
clazz.class_eval(&block) if block_given?
clazz
end
def add_module(name, &block)
module_block = block
define_module(name).class_eval <<-EOF
def self.included(base)
base.class_eval do
# something like this, I'm stuck
instance_eval(&#{module_block})
end
end
EOF
end
And I want to use it like this:
add_module("My::Library") do
def a_method
"added 'a_method'"
end
end
class ::User
include My::Library
end
user = ::User.new
assert_equal "added 'a_method'", user.a_method
Is there any way to do something like that?
This works:
def add_module(name, &block)
define_module(name).class_eval do
class << self; self; end.send(:define_method, :included) { |base|
base.class_eval(&block)
}
end
end
add_module("My::Library") do
def a_method
"added 'a_method'"
end
end
class ::User
include My::Library
end
user = ::User.new
user.a_method #=> "added a_method"
EDIT:
Why don't you just do this instead? Much simpler, and it's actually the job of a module:
def add_module(name, &block)
define_module(name).class_eval(&block)
end
I'm trying to add logging to a method from the outside (Aspect-oriented-style)
class A
def test
puts "I'm Doing something..."
end
end
class A # with logging!
alias_method :test_orig, :test
def test
puts "Log Message!"
test_orig
end
end
a = A.new
a.test
The above works alright, except that if I ever needed to do alias the method again, it goes into an infinite loop. I want something more like super, where I could extend it as many times as I needed, and each extension with alias its parent.
Another alternative is to use unbound methods:
class A
original_test = instance_method(:test)
define_method(:test) do
puts "Log Message!"
original_test.bind(self).call
end
end
class A
original_test = instance_method(:test)
counter = 0
define_method(:test) do
counter += 1
puts "Counter = #{counter}"
original_test.bind(self).call
end
end
irb> A.new.test
Counter = 1
Log Message!
#=> #....
irb> A.new.test
Counter = 2
Log Message!
#=> #.....
This has the advantage that it doesn't pollute the namespace with additional method names, and is fairly easily abstracted, if you want to make a class method add_logging or what have you.
class Module
def add_logging(*method_names)
method_names.each do |method_name|
original_method = instance_method(method_name)
define_method(method_name) do |*args,&blk|
puts "logging #{method_name}"
original_method.bind(self).call(*args,&blk)
end
end
end
end
class A
add_logging :test
end
Or, if you wanted to be able to do a bunch of aspects w/o a lot of boiler plate, you could write a method that writes aspect-adding methods!
class Module
def self.define_aspect(aspect_name, &definition)
define_method(:"add_#{aspect_name}") do |*method_names|
method_names.each do |method_name|
original_method = instance_method(method_name)
define_method(method_name, &(definition[method_name, original_method]))
end
end
end
# make an add_logging method
define_aspect :logging do |method_name, original_method|
lambda do |*args, &blk|
puts "Logging #{method_name}"
original_method.bind(self).call(*args, &blk)
end
end
# make an add_counting method
global_counter = 0
define_aspect :counting do |method_name, original_method|
local_counter = 0
lambda do |*args, &blk|
global_counter += 1
local_counter += 1
puts "Counters: global##{global_counter}, local##{local_counter}"
original_method.bind(self).call(*args, &blk)
end
end
end
class A
def test
puts "I'm Doing something..."
end
def test1
puts "I'm Doing something once..."
end
def test2
puts "I'm Doing something twice..."
puts "I'm Doing something twice..."
end
def test3
puts "I'm Doing something thrice..."
puts "I'm Doing something thrice..."
puts "I'm Doing something thrice..."
end
def other_tests
puts "I'm Doing something else..."
end
add_logging :test, :test2, :test3
add_counting :other_tests, :test1, :test3
end
First choice: subclass instead of overriding:
class AWithLogging < A\
def test
puts "Log Message!"
super
end
end
Second choice: name your orig methods more carefully:
class A # with logging!
alias_method :test_without_logging, :test
def test
puts "Log Message!"
test_without_logging
end
end
Then another aspect uses a different orig name:
class A # with frobnication!
alias_method :test_without_frobnication, :test
def test
Frobnitz.frobnicate(self)
test_without_frobnication
end
end