-
Notifications
You must be signed in to change notification settings - Fork 3
Home
- Overview
- The EE abbreviation
- Intercepting only escaped exceptions
- Intercepting only exceptions that bubble out of your program
- The intercept method
- Recovering from exceptions
- The
skip_until
andskip_while
decorators - Intercepting exceptions at the raise-site
- Using pry-exception_explorer inside Pry itself
- Intercepting C-level exceptions
pry-exception_explorer
is a plugin for the Pry REPL to intercept exceptions in your code. Once inside the session you have the opportunity to uncover the cause of an error, and even to correct it, before letting your program continue. It is based on similar error consoles found in the Lisp and Smalltalk worlds.
As an alternative to using the full module name PryExceptionExplorer
, you can use the constant EE
instead.
If you do not want to intercept exceptions at the point they're raised, you can use the EE.wrap
method to enclose a piece of code in a block and only intercept the exceptions that bubble out of it.
Example: Will not be intercepted
require 'pry-exception_explorer'
EE.intercept(ArgumentError)
EE.wrap do
begin
raise ArgumentError
rescue ArgumentError
end
end
Example: Will be intercepted
require 'pry-exception_explorer'
EE.intercept(ArgumentError)
EE.wrap { raise ArgumentError }
If you prefer to intercept only those exceptions that would bubble out of your program (killing your program in the process) you can invoke the pry
executable with the -w
switch. This causes the pry-exception_explorer plugin to wrap your entire program in an EE.wrap
and intercept any exception that escapes.
Note that to tune the exceptions intercepted in this way via the EE.intercept
method you will need to require pry-exception_explorer
inside your project's local .pryrc
and define the EE.intercept
method there.
Example: Intercepting all exceptions that bubble out of a program
crow:pry john$ pry -w ./my_program.rb
This method allows the user to define the situations where an exception interception occurs. This method can be invoked in two ways.
The general form takes a block argument. The block is passed both the frame where the exception was raised, and the exception itself. The user then creates an assertion (a stack-assertion) based on these attributes. If the assertion is later satisfied by a raised exception, that exception will be intercepted.
The first parameter yielded to the block is a PryExceptionExplorer::LazyFrame instance (representing the context where the exception was raised), the second is the exception object.
The PryExceptionExplorer::LazyFrame
instance supports a number of methods that expose details of the stack frame:
-
self
theself
of the stack frame. -
method_name
the method name (as a Symbol) of the frame's method (if it exists). -
klass
the class of the frame'sself
-
prev
the previous stack frame (as another instance ofLazyFrame
).
The assertion is known as a stack assertion
as the user can assert on the state of any frame in the call-stack, selecting the precise frame they want by using the LazyFrame#prev
method.
Example: Asserting that the exception must be a RuntimeError
and the frame method is alpha
and its caller beta
:
EE.intercept do |frame, ex|
ex.is_a?(RuntimeError) && frame.method_name == :alpha
&& frame.prev.method_name == :beta
end
In the second form, the method simply takes an exception class, or a number of exception classes. If one of these exceptions is raised, it will be intercepted.
Example: Intercept all ArgumentError
and NoMethodError
exceptions
EE.intercept(ArgumentError, NoMethodError)
When you are in the context of an exception, you can investigate the cause of the error by inspecting the contents of variables and analysing code. You can even change the values of variables (local or otherwise). This doesn't just apply to the exception context, you can change the value of variables in parent frames too.
While in the session if you modify state such that you've fixed the cause of the exception you can then safely continue your program by entering the continue-exception
command - your program should continue as normal as if no exception occurred.
Note that if you simply exit a session via the exit
command or by pressing ^D
the exception will go on to be raised properly.
Example: Fixing an exception state in a parent frame and continuing the program with continue-exception
[3] (pry) main: 0> hello
RuntimeError: Error, did not receive a String!
from (pry):7:in `check_for_string'
[4] (pry) main: 0> enter-exception
Frame number: 0/14
Frame type: method
From: (pry) @ line 7 in Object#check_for_string:
2: x = 5
3: check_for_string(x)
4: puts "got x, confirmed that it is the string: #{x}"
5: end
6: def check_for_string(var)
=> 7: raise "Error, did not receive a String!" if !var.is_a?(String)
8: end
9: hello
[5] (pry) main: 0> up
Frame number: 1/14
Frame type: method
From: (pry) @ line 3 in Object#hello:
1: def hello
2: x = 5
=> 3: check_for_string(x)
4: puts "got x, confirmed that it is the string: #{x}"
5: end
6: def check_for_string(var)
7: raise "Error, did not receive a String!" if !var.is_a?(String)
8: end
[6] (pry) main: 0> x = "john"
=> "john"
[7] (pry) main: 0> continue-exception
got x, confirmed that it is the string: john
The EE.intercept
method returns an instance of PryExceptionExplorer::Intercept, onto this we can optionally chain the skip
, skip_until
and skip_while
methods. These methods determine where in the call-stack the Pry session will start.
This can be useful when an exception is raised fairly deep down in code but a session at a parent stack frame could be more useful to the user. See the Plymouth project for an interesting application of this.
Example: Start the session on nearest stack frame with the method name run
EE.intercept(ArgumentError).skip_until { |frame| frame.method_name == :run }
When you require pry-exception_explorer
in your program it is disabled by default. Once you enable it using EE.inline!
it will automatically intercept exceptions at the point the raise
method is called, regardless of whether that exception is properly handled (rescued) later on in the code.
In the example below we configure EE
to intercept every ArgumentError
, at the point the raise
occurs:
Example: This will be intercepted even though it's 'rescued'
require 'pry-exception_explorer'
EE.inline!
EE.intercept(ArgumentError)
begin
raise ArgumentError
rescue ArgumentError
end
When Pry
is started with the pry
executable the pry-exception_explorer plugin is active. However, when an exception is raised inside the Pry REPL it will not automatically cause a session to start in the exception context. Instead you have to explicitly type the command enter-exception
to enter the context of the most recent exception. This is because you are already in an interactive session and so you can be more selective over exactly which exceptions you wish to enter. To exit an exception and return to your previous Pry context use the exit-exception
command.
[3] (pry) main: 0> hello
RuntimeError: yo
from (pry):5:in `goodbye'
[4] (pry) main: 0> enter-exception
Frame number: 0/14
Frame type: method
From: (pry) @ line 5 in Object#goodbye:
1: def hello
2: goodbye
3: end
4: def goodbye
=> 5: raise "yo"
6: end
7: hello
[5] (pry) main: 0>
Internal exceptions such as NoMethodError
s and NameError
s can be intercepted.
Example: Hook the 1/0 exception
[2] (pry) main: 0> hello
ZeroDivisionError: hooked exception (pry)
from (pry):2:in `/'
[3] (pry) main: 0> enter-exception
Frame number: 0/13
Frame type: method
From: (pry) @ line 2 in Object#hello:
1: def hello
=> 2: 1/0
3: end
4: hello
[4] (pry) main: 0>