A piggy bank of commands, fixes, succinct reviews, some mini articles and technical opinions from a (mostly) Perl developer.

Jump to

Quick reference

Showing posts with label modules. Show all posts
Showing posts with label modules. Show all posts

Test that all your module dependencies are listed in the cpanfile

Put this at xt/author/cpanfile.t: Thanks Anton & the team.

How to work with dist.ini as a user

You are a developer who needs to work with a Perl package. Instead of a cpanfile it has a dist.ini. Pop quiz, hot shot: What do you do?

Answer:

  • cpm install Dist::Zilla
  • dzil build
  • Follow instructions to install dependencies using cpanm
    • Or if you prefer cpm (and you should), then try this: dzil listdeps | xargs cpm install
  • Or: dzil authordeps --missing | xargs cpm install

Perl module preferences

Preferences:
  • Never use Switch, it has some truly awful bugs.
  • Use Try::Tiny instead of TryCatch, it's less magical/scary.
  • Instead of JSON, use Cpanel::JSON::XS or JSON::XS
    • It's safer to explicitly name the package being used, because JSON picks one depending on what's already installed.

Use cpm to manage your Perl dependencies

cpm is much faster than cpanm because it makes HTTP requests in parallel and caches modules between runs. The output is also infinitely more clear than cpanm.

# Define the local lib directory
unset PERL5LIB; unset PERL_LOCAL_LIB_ROOT; eval $(perl -Mlocal::lib=./local)

# Install cpm using cpanm
cpanm -nf -L local App::cpm # 35 modules

# Then manually install any problematic modules (as necessary) that have been moved in/out of core
cpm install Module::Build
cpm install Module::Pluggable
cpm install Archive::Extract
cpm install CGI::Cookie
cpm install CGI

# Finally fetch all the CPAN and other dependencies
export DARKPAN=https://username:passwork@mydarkpan.example.com
cpm install --resolver 02packages,$DARKPAN --resolver metadb

Note: --resolver is experimental. The standard argument is --mirror

Thanks HypnoMark

# Another form
cpm install --show-build-log-on-failure --resolver metadb --resolver 02packages,$PINTO_MIRROR --feature=client

# s/PINTO_MIRROR/DARKPAN/

How to use local::lib to install Perl modules locally

First:

cd project_dir
eval $( perl -Mlocal::lib=local )

Install dependencies:

cpanm -L local --installdeps .

Or individual modules:

cpanm -L local Some::Module

Perl modules for SOAP

Options/notes:
  • W3C::Soap - not good
  • SOAP::Lite - not as good as XML::Compile
  • XML::Compile - recommended
    • XML::Compile::SOAP
    • XML::Compile::SOAP11
    • XML::Compile::WSDL11
...but generally SOAP isn't much fun.
Expect the WSDL to be wrong, you may need to store a "fixed" copy locally.

How to install perl modules into a local directory

instead of:
perl Makefile.PL
try:
perl Makefile.PL PREFIX=/path/to/your/directory/perllibs
or:
perl Build.PL PREFIX=/path/to/your/directory/perllibs

Mock modules & Rules of mocking for Perl tests

Mock modules

  • Test2::V0 - I really must start using this soon. See Test2::Mock and possibly Test2::Tools::Mock
  • Test::MockObject - Works. Has set_isa() to pass Moose constraints. Start with an empty object and add methods as needed. This one.
    • see also Test::MockObject::Extends 
  • Test::MockModule - "Override subroutines in a module for unit testing".
    • Does not fool Moose (modules appear as Test::MockModule instead of what they are).
    • Only overrides the methods you say.
    • Has strict mode (can't accidentally mock non-existent methods)
    • Has an 'original()' function for wrapping methods (source):
      • my $mock = Test::MockModule->new("MyModule");
        $mock->redefine("something", sub { my ($self, $args) = @_; push @record_for_testing_later, $args;
                return $mock->original(@_);
        });
  • DBD::Mock - don't use this, use Test::DBIx::Class (with DBIx::Class::Fixtures) or a real test database.
  • Test::Mock::Class - looks clunky
  • Mock::Quick - another one, syntax may be better. "less side effects", doesn't reload modules.
    • To call original un-mocked method, use (source):
    • my $mock = qtakeover 'Some::Module' => (
          id => sub {
              my ( $mock_self, @args ) = @_;
              $counter->{id}++ if scalar @args;
              return $mock_self->MQ_CONTROL->original('id')->( $mock_self, @args );
          },
      );

Reasons to use Mock::Quick

Monkey patching modules

  • Manually overriding subroutines AKA monkey patching - risky. What if you miss a method? Same as what Test::MockModule does.
    • example: { package X; *method_name = sub { return 'foo'; } }
  • Class::Monkey
  • Monkey::Patch
  • Mojo::Util::monkey_patch
  • Sub::Override
  • Mock::Sub - another one
  • Mock::MonkeyPatch
    • Can easily call original sub with Mock::MonkeyPatch::ORIGINAL

Rules of mocking

Don't monkey patch. Don't modify the symbol table directly like this:
*Acme::Foo::method = sub { return "mock you"; }

Instead use Test::MockModule or Sub::Override or similar. Reasons:
  • If you type the method name wrong, or if it gets moved, it becomes so obvious that it can't be missed
  • You won't continue to test code that doesn't exist anymore
The first rule of mocking is: Don't mock.

Instead use dependency injection. If you think you want to use a mock:
  • instead expose the object as an attribute,
  • and pass in one mock object once
  • this is easier to develop & maintain than mocking different things in every test
  • and less prone to leakage between tests
Or if you need a mock method to be in place when an app is instantiated, add logic in the attribute to load a custom object from config, or default to the real object if nothing found in config:
  • create a test class like Foo::Test that inherits from Foo
  • override the dangerous methods
  • add test code: my $test_foo = Foo::Test->(@test_args)
  • and then: $t = Test::Mojo->new("App", { foo => $test_foo });
  • instantiate like this: $class = $config->foo || Foo->new(@args)
  • if necessary, the main library and test library should share an abstract interface (implemented via a role)
Only if all the above fail, use MockModule. Note that scope can matter when using set_isa()

Which Perl modules are installed?

To run: perl script.pl $(cat list_of_modules.txt)

#!/usr/bin/perl
use strict;
use warnings;

foreach my $mod (@ARGV) {
(my $fn="$mod.pm")=~s|::|/|g; # Foo::Bar::Baz => Foo/Bar/Baz.pm
if (eval { require $fn; 1; }) {
print "Module $mod loaded ok\n";
} else {
print "Could not load $mod. Error Message: $@\n";
}
}

# Thanks Perl Monks

Also try typing perldoc perllocal to see a list of all installed modules.

Date/Time perl modules

  • Date::Manip - Big. Needs several step sto convert to/from eopch seconds. But you could just subtract 1 week's worth of seconds.
  • Date::Calc - Less big, simpler. Has Add_Delta_Days. Doesn't generate BST from epoch seconds (on Mac).
  • POSIX - has strftime. Can easily generate epoch seconds from the current time. Lacking in conversion to date?
  • DateTime - The best and easiest.

How to use Catalyst to automatically create Perl DBIx::Class modules

UNTESTED:

1. install Catalyst

2. pretend like you're going to create a catalyst app

catalyst.pl MyFakeApp

3.

cd MyFakeApp
./script/myfakeapp_create.pl model MyModelName DBIC::Schema \
MyApp::SchemaClass create=static dbi:mysql:... user password


http://search.cpan.org/~rkitover/Catalyst-Model-DBIC-Schema-0.29/lib/Catalys
t/Helper/Model/DBIC/Schema.pm



one I did earlier

perl ./script/tagindexer_create.pl model RequestDB DBIC::Schema
TagIndexer::Schema create=static
'dbi:mysql:database=request;host=[ip address];port=3306' tagindexer
tagindexer

Reloading mod_perl modules automatically when they change

Beware of using Apache::StatINC or Apache::Reload, especially is the module in question is a module acting as a configuration file. A severe shortage of fun can occur.

Log4perl

    use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init($ERROR);
    DEBUG "This doesn't go anywhere";
ERROR "This gets logged";

Perl test comparison methods

use Test::More tests => 23;
# or
use Test::More qw(no_plan);
ok($got eq $expected, $test_name);
like ($got, qr/expected/, $test_name);
is_deeply($got_complex_structure, $expected_complex_structure, $test_name);
Compare data structures
eq_or_diff $got, $expected, "description"
Test::Lazy
No descriptions required
# Will evaluate the code and check it:
try('qw/a/' => eq => 'a');
# Don't evaluate, but still compare:
check(1 => is => 1);

Everybody do the DBI

  use DBI;

@driver_names = DBI->available_drivers;
%drivers = DBI->installed_drivers;
@data_sources = DBI->data_sources($driver_name, \%attr);

$dbh = DBI->connect($data_source, $username, $auth, \%attr);

$rv = $dbh->do($statement);
$rv = $dbh->do($statement, \%attr);
$rv = $dbh->do($statement, \%attr, @bind_values);

$ary_ref = $dbh->selectall_arrayref($statement);
$hash_ref = $dbh->selectall_hashref($statement, $key_field);

$ary_ref = $dbh->selectcol_arrayref($statement);
$ary_ref = $dbh->selectcol_arrayref($statement, \%attr);

@row_ary = $dbh->selectrow_array($statement);
$ary_ref = $dbh->selectrow_arrayref($statement);
$hash_ref = $dbh->selectrow_hashref($statement);

$sth = $dbh->prepare($statement);
$sth = $dbh->prepare_cached($statement);

$rc = $sth->bind_param($p_num, $bind_value);
$rc = $sth->bind_param($p_num, $bind_value, $bind_type);
$rc = $sth->bind_param($p_num, $bind_value, \%attr);

$rv = $sth->execute;
$rv = $sth->execute(@bind_values);
$rv = $sth->execute_array(\%attr, ...);

$rc = $sth->bind_col($col_num, \$col_variable);
$rc = $sth->bind_columns(@list_of_refs_to_vars_to_bind);

@row_ary = $sth->fetchrow_array;
$ary_ref = $sth->fetchrow_arrayref;
$hash_ref = $sth->fetchrow_hashref;

$ary_ref = $sth->fetchall_arrayref;
$ary_ref = $sth->fetchall_arrayref( $slice, $max_rows );

$hash_ref = $sth->fetchall_hashref( $key_field );

$rv = $sth->rows;

$rc = $dbh->begin_work;
$rc = $dbh->commit;
$rc = $dbh->rollback;

$quoted_string = $dbh->quote($string);

$rc = $h->err;
$str = $h->errstr;
$rv = $h->state;

$rc = $dbh->disconnect;

Test scripts

Even a set of test scripts often benefit from modularised separate libraries, configuration files and dedicated data files. And logging.

Importing a module's namespace in Perl

Ever get this error for a module you've written?

Use of inherited AUTOLOAD for non-method X() is deprecated

Put this at the top of your module:

package My::Module;

use Exporter;
our @ISA = 'Exporter';
our @EXPORT = qw(put the methods here);

And then in the main program:

use My::Module qw( the methods to import );

keywords: export import exporting