Reverting module changes

M

Michal Kwiatkowski

Hi,

I want to mock standard Kernel.system with my own method and then
revert back to the original version. I was able to do the former, but
not the latter. Currently I'm overwriting Kernel.system method
definition with:

def system_should_return what
Kernel.module_eval "def system(*args) #{what.inspect} end"
end

I don't a have idea how going back to the original state can be done.
Thanks in advance for any help on this.

Cheers,
mk
 
M

Mat Schaffer

Hi,

I want to mock standard Kernel.system with my own method and then
revert back to the original version. I was able to do the former, but
not the latter. Currently I'm overwriting Kernel.system method
definition with:

def system_should_return what
Kernel.module_eval "def system(*args) #{what.inspect} end"
end

I don't a have idea how going back to the original state can be done.
Thanks in advance for any help on this.

Maybe there's something more elegant. But I'd first alias the
original method, redefine it, then when I was done, I'd redefine it
again to just call the alias.
-Mat
 
M

Michal Kwiatkowski

Mat said:
Maybe there's something more elegant. But I'd first alias the
original method, redefine it, then when I was done, I'd redefine it
again to just call the alias.

Thanks, aliasing was what I was looking for. Does code below look OK or
can be improved?

def system_should_return what
Kernel.module_eval <<-EOV
alias_method :eek:rig_system, :system
def system(*args)
#{what.inspect}
end
EOV
end

def restore_system_behaviour
Kernel.module_eval <<-EOV
def system()
orig_system
end
EOV
end

Cheers,
mk
 
D

dblack

Hi --

Thanks, aliasing was what I was looking for. Does code below look OK or
can be improved?

def system_should_return what
Kernel.module_eval <<-EOV
alias_method :eek:rig_system, :system
def system(*args)
#{what.inspect}
end
EOV
end

def restore_system_behaviour
Kernel.module_eval <<-EOV
def system()
orig_system
end
EOV
end

I like to avoid the string version of module_eval (and similar), and
use the block version instead, where possible. To do that with your
code, you could do:

def system_should_return(what)
Kernel.module_eval do
alias_method :eek:rig_system, :system
define_method:)system) {|*args| what.inspect }
end
end

def restore_system_behaviour
Kernel.module_eval do
alias_method :system, :eek:rig_system
end
end

Note also that there's a library, available via RAA, that lets you do
temporary changes to core behaviors:

http://raa.ruby-lang.org/project/import_module/

Actually there a couple of such libraries (including my Ruby
Behaviors), but that one is the most full-featured. It might come in
handy if you do a lot of this or need thread safety.


David

--
Q. What's a good holiday present for the serious Rails developer?
A. RUBY FOR RAILS by David A. Black (http://www.manning.com/black)
aka The Ruby book for Rails developers!
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)
 
M

Michal Kwiatkowski

I like to avoid the string version of module_eval (and similar), and
use the block version instead, where possible. To do that with your
code, you could do:

def system_should_return(what)
Kernel.module_eval do
alias_method :eek:rig_system, :system
define_method:)system) {|*args| what.inspect }
end
end

def restore_system_behaviour
Kernel.module_eval do
alias_method :system, :eek:rig_system
end
end

I've tried to make a more versatile version of this that works for any
module and method. I've ended up with the following code:

class Module
class Stub
def initialize procedure
@procedure = procedure
end

def and_return value
@procedure.call value
end
end

def override! method
Stub.new(lambda do |value|
alias_method(("orig_" + method.to_s).to_sym, method)
define_method(method) { value }
end)
end

def restore! method
alias_method(method, ("orig_" + method.to_s).to_sym)
end
end

With this code, to override the system method you can write (syntax
inspired by RSpec):

Kernel.override!:)system).and_return false

and then to restore:

Kernel.restore! :system

Can you suggest any improvements to this code?
Note also that there's a library, available via RAA, that lets you do
temporary changes to core behaviors:

http://raa.ruby-lang.org/project/import_module/

Wow, very cool. Thanks!

Cheers,
mk
 
E

Eric Hodel

I want to mock standard Kernel.system with my own method and then
revert back to the original version. I was able to do the former, but
not the latter. Currently I'm overwriting Kernel.system method
definition with:

def system_should_return what
Kernel.module_eval "def system(*args) #{what.inspect} end"
end

I don't a have idea how going back to the original state can be done.
Thanks in advance for any help on this.

Its simpler to not overwrite Kernel#system.

If your class looks something like:

$ cat runner.rb
class Runner

def run(command)
puts command
system command
end

end

Use open classes and inheritance to add a system that works when you
want it. Restoring the real system for the Runner class is as simple
as removing the method again.

$ cat test_runner.rb
require 'test/unit'
require 'runner'

class Runner
attr_accessor :commands, :results
def system(command)
@commands << command
@results.shift
end
end

class TestRunner < Test::Unit::TestCase

def setup
@runner = Runner.new
@runner.commands = []
@runner.results = []
end

def test_run
@runner.results << false

assert_equal false, @runner.run("exit 1")

assert @runner.results.empty?
assert_equal 1, @runner.commands.length
assert_equal 'exit 1', @runner.commands.first
end

end

$ ruby test_runner.rb
Loaded suite test_runner
Started
exit 1
 
J

James Mead

The Stubba portion of Mocha (http://mocha.rubyforge.org) allows you to
temporarily replace the implementation of a Module method within a
test.

require 'test/unit'
require 'test/unit/ui/console/testrunner'
require 'rubygems'
require 'stubba'

class Test1 < Test::Unit::TestCase

def test_should_force_system_to_return_false
Kernel.stubs:)system).returns(false)
assert_equal false, Kernel.system('echo')
end

end

class Test2 < Test::Unit::TestCase

def test_should_not_be_affected_by_other_test
assert_equal true, Kernel.system('echo')
end

end

class OrderedTests < Test::Unit::TestCase

def self.suite
suite = Test::Unit::TestSuite.new('OrderedTests')
suite << Test1.suite
suite << Test2.suite
end

end

Test::Unit::UI::Console::TestRunner.run(OrderedTests)
 
D

dblack

Hi --

I've tried to make a more versatile version of this that works for any
module and method. I've ended up with the following code:

class Module
class Stub
def initialize procedure
@procedure = procedure
end

def and_return value
@procedure.call value
end
end

def override! method
Stub.new(lambda do |value|
alias_method(("orig_" + method.to_s).to_sym, method)
define_method(method) { value }
end)
end

def restore! method
alias_method(method, ("orig_" + method.to_s).to_sym)
end
end

With this code, to override the system method you can write (syntax
inspired by RSpec):

Kernel.override!:)system).and_return false

and then to restore:

Kernel.restore! :system

Can you suggest any improvements to this code?

A couple of things, but be warned I am still early in my coffee today
:)

You don't need to_sym in those calls to alias_method; it will take a
string. In fact, you can do this:

"orig_#{method}"

which will work for either.

The ! is generally used for methods that come in pairs: override and
override! It indicates that one is the "dangerous" version of the
operation. In this case, there's only one of each, and the name
itself describes what's happening, so I'd lose the !'s. (I'll keep
them in my examples below, though.)

I would avoid "and_return", which is a kind of odd method name (it
leaves me thinking: don't methods always return something?). It's
better just to tell the method what you want, and not string it across
multiple method calls. Just give it two arguments, or a hash of
arguments.

Here's another version, for your reading pleasure. Use only as
desired.

class Module

def override(opts)
method, value = opts.values_at:)method, :value)
alias_method "orig_#{method}", method
define_method(method) { value }
end

def restore(method)
alias_method method,"orig_#{method}"
end

end

Kernel.override:)method => "system", :value => 3)
puts system
Kernel.restore:)system)
system("date")

What would be more general would be a way to specify the new behavior
arbitrarily, with a code block, which the import-module library does.


David

--
Q. What's a good holiday present for the serious Rails developer?
A. RUBY FOR RAILS by David A. Black (http://www.manning.com/black)
aka The Ruby book for Rails developers!
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,221
Messages
2,571,134
Members
47,748
Latest member
LyleMondra

Latest Threads

Top