How to test whether any exception was rescued? - ruby-on-rails

Is there a way to find out whether any exception was raised and rescued during the execution of some code?
Preferably in tests written with ActiveSupport::TestCase and not RSpec
Is there any global ruby exception stack or something, which I could check?

If you want to be a monster, you can instrument the errors themselves:
class StandardError
##called = false
def initialize
##called = true
super
end
def self.called
##called
end
end
#test it out like so:
def raise_arg_error
raise ArgumentError
rescue
end
puts ArgumentError.called #false
raise_arg_error
puts ArgumentError.called #true
Great for ad hoc sanity checks. Terrible for production code.

As clarified in the comments, OP needed it for debug purposes as opposed to write tests with it.
Kernel#set_trace_func lets you intercept low level events such as an error being raised:
set_trace_func(proc do |event, *_|
puts 'Raised!' if event == 'raise'
end)
raise 'Oh, no!' rescue :foo
You can run #set_trace_func before the code you were trying to debug. If no exception was raised, but a raise was registered by the hook - someone rescued it.
This can create some noise depending on what you are executing. Fortunately, you can filter it down by file and line:
set_trace_func(proc do |event, file, line, *_|
if event == 'raise' && file == 'test.rb' && line == 42
puts "Raised on #{file}:#{line}!"
end
end)
It even gives you the binding, so you can drop down a debugger:
require 'irb'
set_trace_func(proc do |event, *_, error_binding, _|
error_binding.irb if event == 'raise'
end)
def foo
bar = 42
raise 'Oh, no!' rescue :baz
end
foo
# Opens an irb
# > bar # => 42

Related

Rails quitting loop but not logging anything even though rescue StandardError is present - mystery

I have a method like so:
def self.perform
total_done = 0
all_user_roles = UserRole.all
user_roles_count = all_user_roles.count
Rails.logger.info "Going to loop #{user_roles_count} times"
all_user_roles.each do |user_role|
Rails.logger.info "starting the loop..."
if user_role.invalid?
Rails.logger.info "Skipping, role invalid."
next
end
role_name = RoleService.send(
:role_name,
role: user_role[:role],
project: user_role.project
)
existing_role = RoleService.get_for_user(user_role.user, project: user_role.project)
if existing_role.present?
Rails.logger.info "Skipping role because it already exists."
next
end
RoleService.set_for_user(
user_role.user,
project: user_role.project
)
migrated_roles += 1
end
Rails.logger.info "DONE"
rescue StandardError => e
Rails.logger.error("Error!")
end
To my surprise, I see it should loop 1000 times:
>>> Going to loop 1000 times
...but neither the DONE message nor the Error at the end are logged. I see it runs 105 times, and then no more messages in the log.
What could be happening here? Is there a way for something to be raised from one of those service calls and not be caught by the general rescue at the end?
I'm running out of ideas...
Thanks!

Handle exception in ruby on rails

I called a method #txt.watch inside model from worker and Inside watch() there is an array of parameters(parameters = self.parameters). Each parameter have unique reference id.
I want to rescue each exception error for each parameter from inside worker.
class TextWorker
def perform(id)
#txt = WriteTxt.find(id)
begin
#txt.watch
total_complete_watch = if #txt.job_type == 'movie'
#txt.total_count
else
#txt.tracks.where(status:'complete').size
end
#txt.completed = total_completed_games
#txt.complete = (total_complete_games == #txt.total_count)
#txt.completed_at = Time.zone.now if #txt.complete
#txt.zipper if #txt.complete
#txt.save
FileUtils.rm_rf #txt.base_dir if #txt.complete
rescue StandardError => e
#How to find errors for each reference_id here
raise e
end
end
end
Is there any way to do. Thanks u very much.
I assume self.parameters are in your Model class instance. In that case, do as follows and you can reference them.
begin
#txt.watch
rescue StandardError
p #parameters # => self.parameters in the Model context
raise
end
Note:
As a rule of thumb, it is recommended to limit the scope of rescue as narrow as possible. Do not include statements which should not raise Exceptions in your main clause (such as, #txt.save and FileUtils.rm_rf in your case). Also, it is far better to limit the class of an exception; for example, rescue Encoding::CompatibilityError instead of EncodingError, or EncodingError instaed of StandardError, and so on. Or, an even better way is to define your own Exception class and raise it deliberately.

Continue assertion after failures in ruby

My assertion example is below,
class test < Test::Unit::TestCase
def test_users
begin
assert_equal(user.name, 'John')
assert_equal(user.age, 30)
assert_equal(user.zipcode, 500002)
rescue Exception
raise
end
end
end
If any one of assertions fails, i should move on to process the next one and collect the failure assertions and show failures the end of the result.
I have used add_failure method, its working for looping condition
rescue Test::Unit::AssertionFailedError => e
add_failure(e.message, e.backtrace)
Can any one help ?
A good unit test should test exactly one thing, specifically to avoid problems like you just face. This test case will report on all failed tests, and not just the first failed test:
class MyTest < Test::Unit::TestCase
def test_user_name
assert_equal(user.name, 'John')
end
def test_user_age
assert_equal(user.age, 30)
end
def test_user_zipcode
assert_equal(user.zipcode, 500002)
end
end
Your main problem is that assert_equal ends up calling assert (as shown below) and assert will raise an ArgumentException.
File test/unit/assertions.rb, line 144
def assert_equal(exp, act, msg = nil)
msg = message(msg) {
# omitted code to save space
}
assert(exp == act, msg)
end
File test/unit/assertions.rb, line 29
def assert(test, msg = UNASSIGNED)
case msg
when UNASSIGNED
msg = nil
when String, Proc
else
bt = caller.reject { |s| s.rindex(MINI_DIR, 0) }
raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt
end
super
end
You could extend Test::Unit::Assertions and provide an assertion that does not raise ArgumentError, which is what is stopping continuation past the failed assertion.
See this question for advice on going that direction and adding in safe assertions.
Please find this code for Continue assertion after failures in Ruby :
def raise_and_rescue
begin
puts 'I am before the raise.'
raise 'An error has occured.'
puts 'I am after the raise.'
rescue
puts 'I am rescued.'
end
puts 'I am after the begin block.'
end
Output :
ruby p045handexcp.rb
I am before the raise.
I am rescued.
I am after the begin block.
Exit code: 0

Sidekiq/Airbrake only post exception when retries extinguished

I would like Airbrake to only be notified of errors when the retries are exhausted, but I can't seem to think of a way to implement it...
I can add a sidekiq_retries_exhausted hook to send the error to AirBrake but the only way I can think of catching the actual failures is to add a middleware that swallows the error, but then, the job will be marked as a success if there is no error... then there will never be any retries..
Hope that makes sense!
I managed to implement this with a Sidekiq middleware that is inserted at the start of the list:
class RaiseOnRetriesExtinguishedMiddleware
include Sidekiq::Util
def call(worker, msg, queue)
yield
rescue Exception => e
bubble_exception(msg, e)
end
private
def bubble_exception(msg, e)
max_retries = msg['retries'] || Sidekiq::Middleware::Server::RetryJobs::DEFAULT_MAX_RETRY_ATTEMPTS
retry_count = msg['retry_count'] || 0
last_try = !msg['retry'] || retry_count == max_retries - 1
raise e if last_try
end
def retry_middleware
#retry_middleware ||= Sidekiq::Middleware::Server::RetryJobs.new
end
end
If its the last try and its thrown an exception, it'll let it bubble up (to Airbrake) otherwise it won't. This doesn't affect failure recording as that happens later in the chain.
As shown here (not my code):
Airbrake.configure do |config|
config.api_key = '...'
config.ignore_by_filter do |exception_data|
exception_data[:parameters] &&
exception_data[:parameters]['retry_count'].to_i > 0
end
end
I ran into the exact same thing, and wanted to keep it out of AirBrake. Here is what I did, which is easy to read and simple:
class TaskWorker
include Sidekiq::Worker
class RetryLaterNotAnError < RuntimeError
end
def perform task_id
task = Task.find(task_id)
task.do_cool_stuff
if task.finished?
#log.debug "Nothing to do for task #{task_id}"
return false
else
raise RetryLaterNotAnError, task_id
end
end
end
And then, to get Airbrake to ignore it:
Airbrake.configure do |config|
config.ignore << 'RetryLaterNotAnError'
end
Voila!
Here is how we do it for Bugsnag, which you can customise for Airbrake.
# config/initializers/00_core_ext.rb
class StandardError
def skip_bugsnag?
!!#skip_bugsnag
end
def skip_bugsnag!
#skip_bugsnag = true
return self
end
end
# config/initializers/bugsnag.rb
config.ignore_classes << lambda { |e| e.respond_to?(:skip_bugsnag?) && e.skip_bugsnag? }
# In Sidekiq Jobs
raise ErrorToRetryButNotReport.new("some message").skip_bugsnag!
# Or if the error is raised by a third party
begin
# some code that calls a third-party method
rescue ErrorToRetryButNotReport => e
e.skip_bugsnag!
raise
end
You can then manually choose to send the error from sidekiq_retries_exhausted.

How do I make the loop finish when an error is thrown inside?

If I have a loop, and somewhere in the loop, I get an exception or error. How do I keep the loop going?
Foos.each do |foo|
....
# Random error/exception thrown here
....
end
Should I have a rescue block in the loop? Will that make the loop finish? Or is there a better alternative?
You can use add a begin/rescue block. I am not sure there is other ways to do keep loop going if an error is raised.
4.times do |i|
begin
raise if i == 2
puts i
rescue
puts "an error happened but I'm not done yet."
end
end
# 0
# 1
# an error happened but I'm not done yet.
# 3
#=> 4
Since your title in the other hand ask for a way to ends the loop.
If you want the loop to ends in the rescue, you can use break.
4.times do |i|
begin
raise if i == 2
puts i
rescue
puts "an error happened and I'm done."
break
end
end
# 0
# 1
# an error happened and I'm done.
#=> nil

Resources