Table of Contents
Using a debugger in Perl unit tests
It can be difficult to use the Perl debugger to debug Evergreen's Perl code while the system is running. One workaround is to run a unit test that calls the Perl code you'd like to debug. This approach allows you to provide specific known values to the subroutines in question.
The following steps work for unit tests (i.e. the tests in Open-ILS/src/perlmods/t). I don't think they work for the live tests. These are written using the docker dev containers. If you use a different development environment, you'll probably have to adjust them.
If there is a test that already calls the subroutine you're interested in
- Note the filename of the test, and the line number that calls the Perl code you'd like to debug. In our case, we'd like to investigate
OpenILS::Application::Storage::QueryParser::remove_search_field
subroutine, which is currently called on line 25 of t/21-QueryParser.t. - Start a docker dev container using the instructions on docker hub
docker ps
# gives you a list of running containers. Find the ID or name of the one you just starteddocker exec -it container_id_or_name bash
su opensrf
cd /home/opensrf/repos/Evergreen/Open-ILS/src/perlmods
perl -Ilib -d -T t/21-QueryParser.t # -T is only necessary if the first line of the test specifies perl -T
- Congratulations! You are now in the debugger!
- Type
n
then enter a few times until you reach the context of a line in t/21-QueryParser.t - Add a breakpoint for the line that calls the code you want to explore (in our case, line 25):
b 25
- Enter
c
to proceed to the breakpoint - The context should now say:
main::(t/21-QueryParser.t:25): $QParser->remove_search_field('author', 'personal');
- Enter
s
to step into the query parser's remove_search_field method! - To get more context of the surrounding code, enter
l
(lower case L) - To move forward, enter
n
- If you want to know the value of a particular variable or expression, enter
x
then the expression. For example, you could enterx $pkg->search_fields->{'author'}
to get a list of the current author search fields in the parser. - If you need to make some changes to the source code, do so in a separate editor. Then enter
R
to restart the test, which will now take your changes into account. Maybe. This can be buggy in my experience. - When you are done exploring: enter
q
If you have failing tests
Sometimes your test code or production code will have an error that causes it to die. Sometimes this will cause multiple tests or subtests to fail. If this happens, you can run into frustrating situations where you set a breakpoint within a failing test, enter c
, and instead of allowing you to debug that line, you get the message "Debugged program terminated." To avoid this:
- Make sure to put your breakpoint before or at the first line that is failing, or
- If you want to debug, say the second failing test, you can use Test::More's ''SKIP'' feature to skip the first test before starting your debugging session and putting a breakpoint on the second failing test.
If there is not an existing unit test for the code you want to explore
Most of Evergreen's code is unfortunately not (yet) covered by a unit test. However, in some cases you can throw together a simple unit test for the purposes of debugging, and it has the added benefit of improving our test coverage. This will be easier for simple subroutines that don't depend on many other parts of the code.
Testing and debugging simple subroutines and methods
OpenILS::Application::Acq::EDI::nice_string
doesn't have a test, let's test it!
use warnings; use strict; use Test::More tests => 2; use_ok 'OpenILS::Application::Acq::EDI'; my $edi = OpenILS::Application::Acq::EDI->new; $edi->nice_string('Hello'); pass();
And once we have finished our debugging, we can turn this into a legit test by replacing `pass()` with a real assertion.
use warnings; use strict; use Test::More tests => 2; use_ok 'OpenILS::Application::Acq::EDI'; my $edi = OpenILS::Application::Acq::EDI->new; is $edi->nice_string('Hello'), 'Hello', 'String Hello is left intact';
Run it with prove, and then let's submit that patch to boost our code coverage!
Testing and debugging methods with dependencies
If it relies on a complex object, but you can pass it in, you can use Test::MockObject
. If it relies on a complex object but it is setup somewhere you can't control in your test (e.g. the initializer creates a new CSTORE editor), use Test::MockModule
.
Further resources
* The Perl 5 Debugger by Ricardo Signes (youtube video) – a good talk on the Perl debugger