How do I test yield_control multiple times to a block that raises an error? - ruby

I'd like to use the yield_control matcher in RSpec like so
expect {|b| some_method(&b) }.to yield_control.exactly(5).times
However, I'd like the block to raise an error. Is there an RSpec construct that allows you to do this?
Basically, I'm looking for something like an RSpec mock object that allows you to raise an error whenever the block is yielded to and count the number of times it was yielded to.
I understand there are other ways of doing it by defining my own block that keeps track of how many times it has been called, but I was hoping there is a built-in that does something like this.
Example:
def some_method(&block)
attempt = 0
begin
attempt += 1
yield
rescue => e
retry if attempt < 3
end
end
I'd like to test that control is yielded a certain number of times when the block raises an error. I understand that this is one way to do it:
number_of_times = 0
some_method do
number_of_times += 1
raise StandardError
end
expect(number_of_times).to eq(3)
But I was hoping I could use the expect {..}.to yield_control syntax.

Are you expecting the block to raise an error? There is an Rspec matcher for that: https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers/raise-error-matcher

Related

How to prevent problems with `return` from block when using Ruby `yield`

As every Ruby programmer eventually discovers, calling blocks or procs that contain return statements can be dangerous as this might exit your current context:
def some_method(&_block)
puts 1
yield
# The following line will never be executed in this example
# as the yield is actually a `yield-and-return`.
puts 3
end
def test
some_method do
puts 2
return
end
end
test
# This prints "1\n2\n" instead of "1\n2\n3\n"
In cases you want to be absolutely sure some of your code runs after you called a block or proc, you can use a begin ... ensure construct. But since ensure is also called if there is an exception during yield, it requires a little more work.
I've created a tiny module that deals with this problem in two different ways:
Using safe_yield, it is detected whether the yielded block or proc actually returns using the return keyword. If so, it raises an exception.
unknown_block = proc do
return
end
ReturnSafeYield.safe_yield(unknown_block)
# => Raises a UnexpectedReturnException exception
Using call_then_yield, you can call a block and then make sure that a second block is executed, even if the first block contains a return statement.
unknown_block = proc do
return
end
ReturnSafeYield.call_then_yield(unknown_block) do
# => This line is called even though the above block contains a `return`.
end
I'm considering to create a quick Gem out of this, or is there any built-in solution to prevent quick return from the nested block which I missed?
There is a built-in solution to detect whether a block contains a return statement.
You can use RubyVM::InstructionSequence.disasm to disassemble the block passed in by the user, then search it for throw 1, which represents a return statement.
Here's a sample implementation:
def safe_yield(&block)
if RubyVM::InstructionSequence.disasm(block) =~ /^\d+ throw +1$/
raise LocalJumpError
end
block.call
end
Here's how you might incorporate it into your library:
def library_method(&block)
safe_yield(&block)
puts "library_method succeeded"
rescue LocalJumpError
puts "library_method encountered illegal return but resumed execution"
end
And here's the user experience for a well-behaved and a misbehaving user:
def nice_user_method
library_method { 1 + 1 }
end
nice_user_method
# library_method succeeded
def naughty_user_method
library_method { return false if rand > 0.5 }
end
naughty_user_method
# library_method encountered illegal return but resumed execution
Commentary:
Using raise LocalJumpError/rescue LocalJumpError gets around the issues you encountered when using a blanket ensure.
I chose LocalJumpError because it seems relevant, and because (I think!) there is no possible Ruby code that would result in LocalJumpError being raised "naturally" in this context. If that turns out to be false, you can easily substitute your own new exception class.

Ruby - "Do" loop and "rescue"

I'm using the Microsoft computer vision API. The API can recognise faces and gives data on how many people are in an image, what estimated age they are, and what estimated gender. However, I have a "do" loop which I can't "rescue." Here's the code below:
values = json_data['faces'].map do |result|
Here's the error I receive:
C:/Users/KVadher/Desktop/micr1.rb:122:in `block in <main>': undefined method `[]' for nil:NilClass (NoMethodError)
I want my code to look something like this:
begin
values = json_data['faces'].map do |result|
rescue
end
However, when I do this, I get the following error:
C:/Users/USERNAME/Desktop/micr1.rb:123: syntax error, unexpected keyword_rescue
How do I pass my code if a request doesn't apply to it?
You map block should have end
begin
values = json_data['faces'].map do |result|
# ...
end
rescue
end
As Alexander points out, the lack of an end to the do statement explains the unexpected keyword error.
However, using rescue in this way is not good practice. It will effectively mask any problems that occur in the future. You should always be specific in what you rescue. So this would be better:
begin
values = json_data['faces'].map do |result|
...
end
rescue NoMethodError
end
However, the error is telling you that json_data is nil. So to handle this issue, a simpler solution is:
if json_data
values = json_data['faces'].map do |result|
...
end
else
values = [] # or whatever you want values to be if there are none
end

Can I assert inside a begin rescue end block?

I have something that I am testing that I will know its working if it fails. Is there a better way to code this in ruby with test-unit than what I have in my example below?
begin
x = Method.shouldFail
assert_true(false)
rescue Test::Unit::AssertionFailedError
assert_true(false) #stop test, this is a failure
rescue => e
assert_equal(400, e.code)
end
This seems very clunky is there a better way to write this? I would expect that Method.shouldFail would always fail but it might not. And I would assume that in the last rescue block e.code should always be 400 but it could be something else.
You can use assert_raise to test that particular exceptions are being thrown.

Why doesn't MiniTest::Spec have a wont_raise assertion?

Ruby's Test::Unit has assert_nothing_raised. Test::Unit has been replaced by MiniTest. Why don't MiniTest's assertions / expectations have anything parallel to this? For example you can expect must_raise but not wont_raise.
MiniTest does implement assert_nothing_raised in its Test::Unit compatibility layer, but in its own tests (MiniTest::Unit and MiniTest::Spec) it does not implement any test like this. The reason is, the programmer argues, that testing for nothing raised is not a test of anything; you never expect anything to be raised in a test, except when you are testing for an exception. If an unexpected (uncaught) exception occurs in the code for a test, you'll get an exception reported in good order by the test and you'll know you have a problem.
Example:
require 'minitest/autorun'
describe "something" do
it "does something" do
Ooops
end
end
Output:
Run options: --seed 41521
# Running tests:
E
Finished tests in 0.000729s, 1371.7421 tests/s, 0.0000 assertions/s.
1) Error:
test_0001_does_something(something):
NameError: uninitialized constant Ooops
untitled:5:in `block (2 levels) in <main>'
1 tests, 0 assertions, 0 failures, 1 errors, 0 skips
Which is exactly what you wanted to know. If you were expecting nothing to be raised, you didn't get it and you've been told so.
So, the argument here is: do not use assert_nothing_raised! It's just a meaningless crutch. See, for example:
https://github.com/seattlerb/minitest/issues/70
https://github.com/seattlerb/minitest/issues/159
http://blog.zenspider.com/blog/2012/01/assert_nothing_tested.html
On the other hand, clearly assert_nothing_raised corresponds to some intuition among users, since so many people expect a wont_raise to go with must_raise, etc. In particular one would like to focus an assertion on this, not merely a test. Luckily, MiniTest is extremely minimalist and flexible, so if you want to add your own routine, you can. So you can write a method that tests for no exception and returns a known outcome if there is no exception, and now you can assert for that known outcome.
For example (I'm not saying this is perfect, just showing the idea):
class TestMyRequire < MiniTest::Spec
def testForError # pass me a block and I'll tell you if it raised
yield
"ok"
rescue
$!
end
it "blends" do
testForError do
something_or_other
end.must_equal "ok"
end
end
The point is not that this is a good or bad idea but that it was never the responsibility of MiniTest to do it for you.
If you need it:
# test_helper.rb
module Minitest::Assertions
def assert_nothing_raised(*)
yield
end
end
And to use it:
def test_unknown_setter
assert_nothing_raised do
result.some_silly_column_name = 'value'
end
end
This bothered me enough to dig into the MiniTest sources and provide an implementation in my spec_helper.rb file:
module MiniTest
module Assertions
def refute_raises *exp
msg = "#{exp.pop}.\n" if String === exp.last
begin
yield
rescue MiniTest::Skip => e
return e if exp.include? MiniTest::Skip
raise e
rescue Exception => e
exp = exp.first if exp.size == 1
flunk "unexpected exception raised: #{e}"
end
end
end
module Expectations
infect_an_assertion :refute_raises, :wont_raise
end
end
Hope this proves helpful to someone else who also needs wont_raise. Cheers! :)

DRY way of re-raising same set of exceptions in multiple places

short:
Is there a way in Ruby to DRY-ify this:
def entry_point_one
begin
do_something
rescue MySyntaxErrorOne, MySyntaxErrorTwo, MySyntaxErrorEtc => syn_err
raise syn_err.exception(syn_err.message)
end
end
def entry_point_two
begin
do_something_else
rescue MySyntaxErrorOne, MySyntaxErrorTwo, MySyntaxErrorEtc => syn_err
raise syn_err.exception(syn_err.message)
end
end
longer:
I'm building an interpreter. This interpreter can be called using different entry points. If I feed this interpreter a 'dirty' string, I expect it to raise an error. However, it would be nice if I don't get spammed by the by the entire back trace of every method called directly or indirectly by do_something, especially since the interpreter makes use of recursion.
As you can see in the above snippet, I already know a way to re raise an error and thereby removing the back trace. What I would like do is remove the duplication in the above example. The closest I have come thus far is this:
def entry_point_one
re_raise_known_exceptions {do_something}
end
def entry_point_two
re_raise_known_exceptions {do_something_else}
end
def re_raise_known_exceptions
yield
rescue MySyntaxErrorOne, MySyntaxErrorTwo, MySyntaxErrorEtc => syn_err
raise syn_err.exception(syn_err.message)
end
But that makes the method re-raise-known-exceptions show up in the back trace.
edit: I guess what I want would be something like a C pre-processing macro
You can just use the splat on an array.
Straight from IRB:
COMMON_ERRORS = [ArgumentError, RuntimeError] # add your own
def f
yield
rescue *COMMON_ERRORS => err
puts "Got an error of type #{err.class}"
end
f{ raise ArgumentError.new }
Got an error of type ArgumentError
f{ raise 'abc' }
Got an error of type RuntimeError
while thinking about it a bit more, I came up with this:
interpreter_block {do_something}
def interpreter_block
yield
rescue ExceptionOne, ExceptionTwo, ExceptionEtc => exc
raise exc.exception(exc.message)
end
Although it's still not quiet what I would like to have, at least now the extra entry in the back trace has become somewhat better looking.
It might be slightly evil, but I think you can simply remove the line from the backtrace ;-)
COMMON_ERRORS = [ArgumentError, RuntimeError]
def interpreter_block
yield
rescue *COMMON_ERRORS => err
err.backtrace.delete_if{ |line| line=~/interpreter_block/ }
raise err
end
I'm not sure it's such a good idea though. You'll have a hell of a lot of fun debugging your interpreter afterward ;-)
Side note: Treetop may be of interest to you.
This is a touch hackish, but as far as cleaning up the backtrace goes, something like this works nicely:
class Interpreter
def method1
error_catcher{ puts 1 / 0 }
end
def error_catcher
yield
rescue => err
err.set_backtrace(err.backtrace - err.backtrace[1..2])
raise err
end
end
The main trick is this line err.set_backtrace(err.backtrace - err.backtrace[1..2]). Without it, we get the following (from IRB):
ZeroDivisionError: divided by 0
from (irb):43:in `/'
from (irb):43:in `block in method1'
from (irb):47:in `error_catcher'
from (irb):43:in `method1'
from (irb):54
from /Users/peterwagenet/.ruby_versions/ruby-1.9.1-p129/bin/irb:12:in `<main>'
What we don't want in there are the second and third lines. So we remove them, ending up with:
ZeroDivisionError: divided by 0
from (irb):73:in `/'
from (irb):73:in `method1'
from (irb):84
from /Users/peterwagenet/.ruby_versions/ruby-1.9.1-p129/bin/irb:12:in `<main>'
If you have all of the information you need in the exceptions, and you do not need the backtrace at all, you can just define your own error and raise that, instead of reraising the existing exception. This will give it a fresh backtrace. (Of course, presumably your sample code is incomplete and there is other processing happening in the rescue block -- otherwise your best bet is to just let the error bubble up naturally.)
class MyError < StandardError; end
def interpreter_block
yield
rescue ExceptionOne, ExceptionTwo, ExceptionEtc => exc
raise MyError
end

Resources