How to set breakpoint on a particular file in Perl program? - perl

My Perl program looks like:
foo/
foo/bar/
for/bar/test.pm
foo.pm
foo/bar.pm
test.pl
and use perl test.pl to start the program.
I want to debug a sub in foo/bar/test.pm. How to set a breakpoint on that sub?

To debug a perl script, use the -d switch to invoke the debugger.
perl -d test.pl
Within the debugger you can use b <line no> to set a breakpoint in the current file. Sometimes it is a hassle to set a breakpoint in a file that hasn't been loaded yet or that was loaded a long time ago, so you can also put the line
$DB::single = 1;
anywhere in any perl program, and the debugger will break immediately after it executes that line. This is also a good way (the only way?) to set a breakpoint in code that will be run at compile time.

Just use the fully qualified name of the sub as the argument to b:
b foo::bar::test::subname
Example:
$ perl -d -e'use CGI; CGI->new'
...
main::(-e:1): use CGI; CGI->new
DB<1> b CGI::new
DB<2> r
CGI::new(.../CGI.pm:337):
337: my($class,#initializer) = #_;
DB<2> q

You can enter f Module.pm to tell the debugger to look at that particular module. Once you've done that, b line_number will stop at that line in Module.pm.
Once the original script has passed use Module, then b subroutine will stop at that subroutine. The only catch here is that you can't make your first two debugger commands f Module.pm; b subroutine because when the script begins, it hasn't passed use Module, so Module.pm hasn't loaded yet, which means that perl can't see that Module.pm has subroutine.

Related

How can I debug a single Perl unit test sub?

I have the usual MakeMaker module with a t/ tests directory, and I can run a single test file with e.g., prove -I lib t/my-test.t.
My tests use Test::Class and Test::More and subs (with the modulino technique from Effective Perl), like this:
use strict;
use warnings;
use base 'Test::Class';
use Test::More;
__PACKAGE__->runtests() unless caller;
sub set_up : Test(setup) {
# ...
}
sub test_something : Test {
is(MyModule::some_sub(1), 1);
}
# ...more test subs...
Now I want to use the Perl debugger to investigate a test sub that shows a problem in my module. I want to only run test_something in the debugger, without running all the other test subs in the .t file.
prove does not seem to have such an option.
perl -d -I lib t/my-test.t runs all tests, unless I change my modulino to call the setup method and then the actual test method instead of __PACKAGE__->runtests():
unless (caller) {
set_up();
test_something();
done_testing();
}
How can I run only one test sub without modifying the code?
To avoid running all of your tests, caller must be defined when your test script is loaded. Try something like this:
$ perl -Ilib -de 1
DB<1> do 't/my-test.t'
DB<2> set_up()
DB<3> b test_something_else
DB<4> test_something_else()
... step through test_something_else() function ...
DB<16> done_testing()

continue command ignores breakpoints

I'm getting errors using CPAN and, as output is rather unhelpful, resorted to debugging this.
$ perl -d /usr/bin/cpan CPAN
Loading DB routines from perl5db.pl version 1.33
<...>
main::(/usr/bin/cpan:2): eval 'exec /usr/bin/perl -S $0 ${1+"$#"}'
main::(/usr/bin/cpan:3): if $running_under_some_shell;
DB<1> b /usr/lib/perl5/5.14/HTTP/Tiny.pm:125
DB<2> L
/usr/bin/cpan:
2: eval 'exec /usr/bin/perl -S $0 ${1+"$#"}'
break if (/usr/lib/perl5/5.14/HTTP/Tiny.pm:125)
DB<2> c
After c, the program runs to completion, ignoring the breakpoint.
/usr/lib/perl5/5.14/HTTP/Tiny.pm:125 is the following line, outside any conditional blocks.
my $request = {
method => $method,
scheme => $scheme,
host_port => ($port == $DefaultPort{$scheme} ? $host : "$host:$port"),
uri => $path_query,
headers => {},
};
Setting a break on line 142 (that's causing errors that I'm debugging) makes no difference.
From the L output, it looks like the breakpoint is set on the current line rather than the one I need. But, perldebug lists b file:line as valid syntax.
That syntax might be a newer addition, I wasn't able to get it working here either. I have 5.8.8 on Unix and 5.10.1 on Windows and both don't seem to work. I get the breakpoint on my current line as you do (it treats the whole string as a condition).
I'd suggest using another syntax as below.
Break on the method:
b HTTP::Tiny::some_method
Select the file, break on the line (it matches on paths, not module namespaces):
f HTTP/Tiny
b 125
If they are loaded at runtime with require or eval, the debugger won't know about the module yet so you can just type use HTTP::Tiny in the debugger first to force it to load.

Invoking Perl debugger so that it runs until first breakpoint

When I invoke my Perl debugger with perl -d myscript.pl, the debugger starts, but it does not execute any code until I press n (next) or c (continue).
Is there anyway to invoke the debugger and have it run through the code by default until it hits a breakpoint?
If so, is there any statement that I can use in my code as a breakpoint to have the debugger stop when it hits it?
Update:
Here is what I have in my .perldb file:
print "Reading ~/.perldb options.\n";
push #DB::typeahead, "c";
parse_options("NonStop=1");
Here is my hello_world.pl file:
use strict;
use warnings;
print "Hello world.\n";
$DB::single=1;
print "How are you?";
Here is the debugging session from running: perl -d hello_world.pl:
Reading ~/.perldb options.
Hello world
main::(hello_world.pl:6): print "How are you?";
auto(-1) DB<1> c
Debugged program terminated. Use q to quit or R to restart,
use o inhibit_exit to avoid stopping after program termination,
h q, h R or h o to get additional info.
DB<1> v
9563 9564
9565 sub at_exit {
9566==> "Debugged program terminated. Use `q' to quit or `R' to restart.";
9567 }
9568
9569 package DB; # Do not trace this 1; below!
DB<1>
In other words, my debugger skips print "How are you?", and instead stops once the program finishes, which is not what I want.
What I want is have the debugger run my code without stopping anywhere (nor at the beginning, nor at the end of my script), unless I explicitly have a $DB::single=1; statement, in which case I would like it to stop before running the next line. Any ways to do this?
For reference, I am using:
$perl --version
This is perl 5, version 14, subversion 1 (v5.14.1) built for x86_64-linux
Put
$DB::single = 1;
before any statement to set a permanent breakpoint in your code.
This works with compile-time code, too, and may be the only good way to set a breakpoint during the compile phase.
To have the debugger automatically start your code, you can manipulate the #DB::typeahead array in either a .perldb file or in a compile-time (BEGIN) block in your code. For example:
# .perldb file
push #DB::typeahead, "c";
or
BEGIN { push #DB::typeahead, "p 'Hello!'", "c" }
...
$DB::single = 1;
$x = want_to_stop_here();
There is also a "NonStop" option that you could set in .perldb or in the PERLDB_OPTS environment variable:
PERLDB_OPTS=NonStop perl -d myprogram.pl
All of this (and much more) is discussed deep in the bowels of perldebug and perl5db.pl
Update:
To address the issues raised in the most recent update. Use the following in ./perldb:
print "Reading ~/.perldb options.\n";
push #DB::typeahead, "c";
parse_options("inhibit_exit=0");
Also check out Enbugger. And while on the topic of debuggers, see Devel::Trepan which also works with Enbugger.

perl: run another script without exec/system

Is it possible to include/source another perl script, or launch it as a "sub"?
This works, but looks ugly:
test2.pl:
print "I'm in test2.pl; args: #ARGV\n";
test.pl:
#!/usr/bin/perl
use File::Temp qw/tempdir/;
use File::Copy qw/copy/;
my $tmplib;
use lib ($tmplib = tempdir()) . (
copy("./test2.pl", "$tmplib/test2.pm") ? "" : (die "$!")
);
use test2;
x
$ ./test.pl a b c
I'm in test2.pl; args: a b c
It sounds like you want the do operator, although it also sounds like very bad design.
This is what the documentation says.
do EXPR Uses the value of EXPR as a filename and executes the contents
of the file as a Perl script.
do 'stat.pl';
is just like
eval `cat stat.pl`;
You can use do to run another Perl script in the same interpreter:
do 'test2.pl';
This will reuse the command line parameters from the outer script. To pass different parameters, you can override #ARGV locally, like:
{
local #ARGV = qw(par1 par2 par3);
do 'test2.pl';
}

How can I use Perl 5.10 features inside the debugger?

I am unable to evaluate 'modern Perl' code inside the Perl debugger. It works OK when debugging the code in a file, but not from the prompt.
Minimal example:
# Activating 5-10 features with -E (it works)
$ perl -E 'say "x"'
x
# Calling the debugger with -E
# It works for infile code, but for prompt line code...
$ perl -dEbug Loading DB routines from perl5db.pl version 1.33
DB say "x"
String found where operator expected at (eval 16)[/local-perl/lib/5.12.1/perl5db.pl:638] line 2, near "say "x""
at (eval 16)[/local-perl/lib/5.12.1/perl5db.pl:638] line 2
eval '($#, $!, $^E, $,, $/, $\\, $^W) = #saved;package main; $^D = $^D | $DB::db_stop;say "x";
(Note: the same happens with "use feature ':5.10'".)
Am I missing something?
I found a reference to the issue here, but it's about a year old. However, the relevant portion of the Perl source hasn't changed since and can be seen here. Essentially, if you take a look at toke.c in the Perl source, you see the following:
if (PL_perldb) {
/* Generate a string of Perl code to load the debugger.
* If PERL5DB is set, it will return the contents of that,
* otherwise a compile-time require of perl5db.pl. */
const char * const pdb = PerlEnv_getenv("PERL5DB");
...
}
...
if (PL_minus_E)
sv_catpvs(PL_linestr,
"use feature ':5." STRINGIFY(PERL_VERSION) "';");
Basically, the debugger is loaded before the -E flag is processed, so the features aren't yet enabled when the debugger gets loaded. The gist of this is that you can't currently use -E with the -d command. If you want to use say, switch, or any other feature from the debug prompt, you have to do it like this:
DB<1> use feature 'say'; say "x"
x
The closest I've seen to a solution is:
copy perl5db.pl from your PERL5LIB to either somewhere in PERL5LIB or the current directory, with a different name, say myperl5db.pl
2. Edit myperl5db.pl to have use feature ':5.10'; (or just 'state', or just 'say') on the first line.
3. Set the environment variable PERL5DB to "BEGIN { require 'myperl5db.pl' }"
Which I found at PerlMonks.