Build all packages removed dependencies of libtest-exception-perl libtest-warn-perl...
[dh-make-perl] / dev / i386 / libperl-critic-perl / libperl-critic-perl-1.088 / lib / Perl / Critic / TestUtils.pm
diff --git a/dev/i386/libperl-critic-perl/libperl-critic-perl-1.088/lib/Perl/Critic/TestUtils.pm b/dev/i386/libperl-critic-perl/libperl-critic-perl-1.088/lib/Perl/Critic/TestUtils.pm
new file mode 100644 (file)
index 0000000..f260c78
--- /dev/null
@@ -0,0 +1,542 @@
+##############################################################################
+#      $URL: http://perlcritic.tigris.org/svn/perlcritic/trunk/Perl-Critic/lib/Perl/Critic/TestUtils.pm $
+#     $Date: 2008-07-03 10:19:10 -0500 (Thu, 03 Jul 2008) $
+#   $Author: clonezone $
+# $Revision: 2489 $
+##############################################################################
+
+package Perl::Critic::TestUtils;
+
+use 5.006001;
+use strict;
+use warnings;
+use English qw(-no_match_vars);
+use Readonly;
+
+use base 'Exporter';
+
+use File::Path ();
+use File::Spec ();
+use File::Spec::Unix ();
+use File::Temp ();
+use File::Find qw( find );
+
+use Perl::Critic;
+use Perl::Critic::Config;
+use Perl::Critic::Exception::Fatal::Generic qw{ &throw_generic };
+use Perl::Critic::Exception::Fatal::Internal qw{ &throw_internal };
+use Perl::Critic::Utils qw{ :severities :data_conversion policy_long_name };
+use Perl::Critic::PolicyFactory (-test => 1);
+
+our $VERSION = '1.088';
+
+Readonly::Array our @EXPORT_OK => qw(
+    pcritique pcritique_with_violations
+    critique  critique_with_violations
+    fcritique fcritique_with_violations
+    subtests_in_tree
+    should_skip_author_tests
+    get_author_test_skip_message
+    starting_points_including_examples
+    bundled_policy_names
+    names_of_policies_willing_to_work
+);
+
+#-----------------------------------------------------------------------------
+# If the user already has an existing perlcriticrc file, it will get
+# in the way of these test.  This little tweak to ensures that we
+# don't find the perlcriticrc file.
+
+sub block_perlcriticrc {
+    no warnings 'redefine';  ## no critic (ProhibitNoWarnings);
+    *Perl::Critic::UserProfile::_find_profile_path = sub { return }; ## no critic (ProtectPrivateVars)
+    return 1;
+}
+
+#-----------------------------------------------------------------------------
+# Criticize a code snippet using only one policy.  Returns the violations.
+
+sub pcritique_with_violations {
+    my($policy, $code_ref, $config_ref) = @_;
+    my $c = Perl::Critic->new( -profile => 'NONE' );
+    $c->add_policy(-policy => $policy, -config => $config_ref);
+    return $c->critique($code_ref);
+}
+
+#-----------------------------------------------------------------------------
+# Criticize a code snippet using only one policy.  Returns the number
+# of violations
+
+sub pcritique {  ##no critic(ArgUnpacking)
+    return scalar pcritique_with_violations(@_);
+}
+
+#-----------------------------------------------------------------------------
+# Criticize a code snippet using a specified config.  Returns the violations.
+
+sub critique_with_violations {
+    my ($code_ref, $config_ref) = @_;
+    my $c = Perl::Critic->new( %{$config_ref} );
+    return $c->critique($code_ref);
+}
+
+#-----------------------------------------------------------------------------
+# Criticize a code snippet using a specified config.  Returns the
+# number of violations
+
+sub critique {  ##no critic(ArgUnpacking)
+    return scalar critique_with_violations(@_);
+}
+
+#-----------------------------------------------------------------------------
+# Like pcritique_with_violations, but forces a PPI::Document::File context.
+# The $filename arg is a Unix-style relative path, like 'Foo/Bar.pm'
+
+Readonly::Scalar my $TEMP_FILE_PERMISSIONS => oct 700;
+
+sub fcritique_with_violations {
+    my($policy, $code_ref, $filename, $config_ref) = @_;
+    my $c = Perl::Critic->new( -profile => 'NONE' );
+    $c->add_policy(-policy => $policy, -config => $config_ref);
+
+    my $dir = File::Temp::tempdir( 'PerlCritic-tmpXXXXXX', TMPDIR => 1 );
+    $filename ||= 'Temp.pm';
+    my @fileparts = File::Spec::Unix->splitdir($filename);
+    if (@fileparts > 1) {
+        my $subdir = File::Spec->catdir($dir, @fileparts[0..$#fileparts-1]);
+        File::Path::mkpath($subdir, 0, $TEMP_FILE_PERMISSIONS);
+    }
+    my $file = File::Spec->catfile($dir, @fileparts);
+    if (open my $fh, '>', $file) {
+        print {$fh} ${$code_ref};
+        close $fh or throw_generic "unable to close $file: $!";
+    }
+
+    # Use eval so we can clean up before throwing an exception in case of
+    # error.
+    my @v = eval {$c->critique($file)};
+    my $err = $EVAL_ERROR;
+    File::Path::rmtree($dir, 0, 1);
+    if ($err) {
+        throw_generic $err;
+    }
+    return @v;
+}
+
+#-----------------------------------------------------------------------------
+# Like pcritique, but forces a PPI::Document::File context.  The
+# $filename arg is a Unix-style relative path, like 'Foo/Bar.pm'
+
+sub fcritique {  ##no critic(ArgUnpacking)
+    return scalar fcritique_with_violations(@_);
+}
+
+sub subtests_in_tree {
+    my $start = shift;
+
+    my %subtests;
+
+    find( {wanted => sub {
+               return if ! -f $_;
+               my ($fileroot) = m{(.+)[.]run\z}mx;
+               return if !$fileroot;
+               my @pathparts = File::Spec->splitdir($fileroot);
+               if (@pathparts < 2) {
+                   throw_internal 'confusing policy test filename ' . $_;
+               }
+               my $policy = join q{::}, @pathparts[-2, -1]; ## no critic (MagicNumbers)
+
+               my @subtests = _subtests_from_file( $_ );
+               $subtests{ $policy } = [ @subtests ];
+           }, no_chdir => 1}, $start );
+    return \%subtests;
+}
+
+# Answer whether author test should be run.
+#
+# Note: this code is duplicated in
+# t/tlib/Perl/Critic/TestUtilitiesWithMinimalDependencies.pm.
+# If you change this here, make sure to change it there.
+
+sub should_skip_author_tests {
+    return not $ENV{TEST_AUTHOR_PERL_CRITIC}
+}
+
+sub get_author_test_skip_message {
+    ## no critic (RequireInterpolation);
+    return 'Author test.  Set $ENV{TEST_AUTHOR_PERL_CRITIC} to a true value to run.';
+}
+
+
+sub starting_points_including_examples {
+    return (-e 'blib' ? 'blib' : 'lib', 'examples');
+}
+
+# The internal representation of a subtest is just a hash with some
+# named keys.  It could be an object with accessors for safety's sake,
+# but at this point I don't see why.
+
+sub _subtests_from_file {
+    my $test_file = shift;
+
+    my %valid_keys = hashify qw( name failures parms TODO error filename optional_modules );
+
+    return if -z $test_file;  # Skip if the Policy has a regular .t file.
+
+    open my $fh, '<', $test_file   ## no critic (RequireBriefOpen)
+      or throw_internal "Couldn't open $test_file: $OS_ERROR";
+
+    my @subtests;
+
+    my $incode = 0;
+    my $subtest;
+    my $lineno;
+    while ( <$fh> ) {
+        ++$lineno;
+        chomp;
+        my $inheader = /^## name/ .. /^## cut/; ## no critic(RegularExpression)
+
+        my $line = $_;
+
+        if ( $inheader ) {
+            $line =~ m/\A [#]/mx or throw_internal "Code before cut: $test_file";
+            my ($key,$value) = $line =~ m/\A [#][#] [ ] (\S+) (?:\s+(.+))? /mx;
+            next if !$key;
+            next if $key eq 'cut';
+            if ( not $valid_keys{$key} ) {
+                throw_internal "Unknown key $key in $test_file";
+            }
+
+            if ( $key eq 'name' ) {
+                if ( $subtest ) { # Stash any current subtest
+                    push @subtests, _finalize_subtest( $subtest );
+                    undef $subtest;
+                }
+                $subtest->{lineno} = $lineno;
+                $incode = 0;
+            }
+            if ($incode) {
+                throw_internal "Header line found while still in code: $test_file";
+            }
+            $subtest->{$key} = $value;
+        }
+        elsif ( $subtest ) {
+            $incode = 1;
+            # Don't start a subtest if we're not in one
+            push @{$subtest->{code}}, $line;
+        }
+        elsif (@subtests) {
+            ## don't complain if we have not yet hit the first test
+            throw_internal "Got some code but I'm not in a subtest: $test_file";
+        }
+    }
+    close $fh or throw_generic "unable to close $test_file: $!";
+    if ( $subtest ) {
+        if ( $incode ) {
+            push @subtests, _finalize_subtest( $subtest );
+        }
+        else {
+            throw_internal "Incomplete subtest in $test_file";
+        }
+    }
+
+    return @subtests;
+}
+
+sub _finalize_subtest {
+    my $subtest = shift;
+
+    if ( $subtest->{code} ) {
+        $subtest->{code} = join "\n", @{$subtest->{code}};
+    }
+    else {
+        throw_internal "$subtest->{name} has no code lines";
+    }
+    if ( !defined $subtest->{failures} ) {
+        throw_internal "$subtest->{name} does not specify failures";
+    }
+    if ($subtest->{parms}) {
+        $subtest->{parms} = eval $subtest->{parms}; ## no critic(StringyEval)
+        if ($EVAL_ERROR) {
+            throw_internal
+                "$subtest->{name} has an error in the 'parms' property:\n"
+                  . $EVAL_ERROR;
+        }
+        if ('HASH' ne ref $subtest->{parms}) {
+            throw_internal
+                "$subtest->{name} 'parms' did not evaluate to a hashref";
+        }
+    } else {
+        $subtest->{parms} = {};
+    }
+
+    if (defined $subtest->{error}) {
+        if ( $subtest->{error} =~ m{ \A / (.*) / \z }xms) {
+            $subtest->{error} = eval {qr/$1/}; ##no critic (RegularExpressions::)
+            if ($EVAL_ERROR) {
+                throw_internal
+                    "$subtest->{name} 'error' has a malformed regular expression";
+            }
+        }
+    }
+
+    return $subtest;
+}
+
+sub bundled_policy_names {
+    require ExtUtils::Manifest;
+    my $manifest = ExtUtils::Manifest::maniread();
+    my @policy_paths = map {m{\A lib/(Perl/Critic/Policy/.*).pm \z}mx} keys %{$manifest};
+    my @policies = map { join q{::}, split m{/}mx, $_} @policy_paths;
+    return sort @policies;
+}
+
+sub names_of_policies_willing_to_work {
+    my %configuration = @_;
+
+    my @policies_willing_to_work =
+        Perl::Critic::Config
+            ->new( %configuration )
+            ->policies();
+
+    return map { ref $_ } @policies_willing_to_work;
+}
+
+1;
+
+__END__
+
+#-----------------------------------------------------------------------------
+
+=pod
+
+=for stopwords subtest subtests
+
+=head1 NAME
+
+Perl::Critic::TestUtils - Utility functions for testing new Policies.
+
+=head1 SYNOPSIS
+
+  use Perl::Critic::TestUtils qw(critique pcritique fcritique);
+
+  my $code = '<<END_CODE';
+  package Foo::Bar;
+  $foo = frobulator();
+  $baz = $foo ** 2;
+  1;
+  END_CODE
+
+  # Critique code against all loaded policies...
+  my $perl_critic_config = { -severity => 2 };
+  my $violation_count = critique( \$code, $perl_critic_config);
+
+  # Critique code against one policy...
+  my $custom_policy = 'Miscellanea::ProhibitFrobulation'
+  my $violation_count = pcritique( $custom_policy, \$code );
+
+  # Critique code against one filename-related policy...
+  my $custom_policy = 'Modules::RequireFilenameMatchesPackage'
+  my $violation_count = fcritique( $custom_policy, \$code, 'Foo/Bar.pm' );
+
+=head1 DESCRIPTION
+
+This module is used by L<Perl::Critic> only for self-testing. It
+provides a few handy subroutines for testing new Perl::Critic::Policy
+modules.  Look at the test scripts that ship with Perl::Critic for
+more examples of how to use these subroutines.
+
+=head1 EXPORTS
+
+=over
+
+=item block_perlcriticrc()
+
+If a user has a F<~/.perlcriticrc> file, this can interfere with testing.
+This handy method disables the search for that file -- simply call it at the
+top of your F<.t> program.  Note that this is not easily reversible, but that
+should not matter.
+
+=item critique_with_violations( $code_string_ref, $config_ref )
+
+Test a block of code against the specified Perl::Critic::Config instance (or
+C<undef> for the default).  Returns the violations that occurred.
+
+=item critique( $code_string_ref, $config_ref )
+
+Test a block of code against the specified Perl::Critic::Config instance (or
+C<undef> for the default).  Returns the number of violations that occurred.
+
+=item pcritique_with_violations( $policy_name, $code_string_ref, $config_ref )
+
+Like C<critique_with_violations()>, but tests only a single policy instead of
+the whole bunch.
+
+=item pcritique( $policy_name, $code_string_ref, $config_ref )
+
+Like C<critique()>, but tests only a single policy instead of the whole bunch.
+
+=item fcritique_with_violations( $policy_name, $code_string_ref, $filename, $config_ref )
+
+Like C<pcritique_with_violations()>, but pretends that the code was loaded
+from the specified filename.  This is handy for testing policies like
+C<Modules::RequireFilenameMatchesPackage> which care about the filename that
+the source derived from.
+
+The C<$filename> parameter must be a relative path, not absolute.  The file
+and all necessary subdirectories will be created via L<File::Temp> and will be
+automatically deleted.
+
+=item fcritique( $policy_name, $code_string_ref, $filename, $config_ref )
+
+Like C<pcritique()>, but pretends that the code was loaded from the specified
+filename.  This is handy for testing policies like
+C<Modules::RequireFilenameMatchesPackage> which care about the filename that
+the source derived from.
+
+The C<$filename> parameter must be a relative path, not absolute.  The file
+and all necessary subdirectories will be created via L<File::Temp> and will be
+automatically deleted.
+
+=item subtests_in_tree( $dir )
+
+Searches the specified directory recursively for F<.run> files.  Each one
+found is parsed and a hash-of-list-of-hashes is returned.  The outer hash is
+keyed on policy short name, like C<Modules::RequireEndWithOne>.  The inner
+hash specifies a single test to be handed to C<pcritique()> or C<fcritique()>,
+including the code string, test name, etc.  See below for the syntax of the
+F<.run> files.
+
+=item should_skip_author_tests()
+
+Answers whether author tests should run.
+
+=item get_author_test_skip_message()
+
+Returns a string containing the message that should be emitted when a test
+is skipped due to it being an author test when author tests are not enabled.
+
+=item starting_points_including_examples()
+
+Returns a list of the directories contain code that needs to be tested when it
+is desired that the examples be included.
+
+=item bundled_policy_names()
+
+Returns a list of Policy packages that come bundled with this package.  This
+functions by searching F<MANIFEST> for F<lib/Perl/Critic/Policy/*.pm> and
+converts the results to package names.
+
+=item names_of_policies_willing_to_work( %configuration )
+
+Returns a list of the packages of policies that are willing to function on
+the current system using the specified configuration.
+
+=back
+
+=head1 F<.run> file information
+
+Testing a policy follows a very simple pattern:
+
+    * Policy name
+        * Subtest name
+        * Optional parameters
+        * Number of failures expected
+        * Optional exception expected
+        * Optional filename for code
+
+Each of the subtests for a policy is collected in a single F<.run> file, with
+test properties as comments in front of each code block that describes how we expect
+Perl::Critic to react to the code.  For example, say you have a policy called
+Variables::ProhibitVowels:
+
+    (In file t/Variables/ProhibitVowels.run)
+
+    ## name Basics
+    ## failures 1
+    ## cut
+
+    my $vrbl_nm = 'foo';    # Good, vowel-free name
+    my $wango = 12;         # Bad, pronouncable name
+
+
+    ## name Sometimes Y
+    ## failures 1
+    ## cut
+
+    my $yllw = 0;       # "y" not a vowel here
+    my $rhythm = 12;    # But here it is
+
+These are called "subtests", and two are shown above.  The beauty of
+incorporating multiple subtests in a file is that the F<.run> is itself a
+(mostly) valid Perl file, and not hidden in a HEREDOC, so your editor's
+color-coding still works, and it is much easier to work with the code and the
+POD.
+
+If you need to pass any configuration parameters for your subtest, do so like
+this:
+
+    ## parms { allow_y => '0' }
+
+Note that all the values in this hash must be strings because that's what
+Perl::Critic will hand you from a F<.perlcriticrc>.
+
+If it's a TODO subtest (probably because of some weird corner of
+PPI that we exercised that Adam is getting around to fixing, right?),
+then make a C<##TODO> entry.
+
+    ## TODO Should pass when PPI 1.xxx comes out
+
+If the code is expected to trigger an exception in the policy, indicate that
+like so:
+
+    ## error 1
+
+If you want to test the error message, mark it with C</.../> to indicate a
+C<like()> test:
+
+    ## error /Can't load Foo::Bar/
+
+If the policy you are testing cares about the filename of the code, you can
+indicate that C<fcritique> should be used like so (see C<fcritique> for more
+details):
+
+    ## filename lib/Foo/Bar.pm
+
+The value of C<parms> will get C<eval>ed and passed to C<pcritique()>,
+so be careful.
+
+Note that nowhere within the F<.run> file itself do you specify the
+policy that you're testing.  That's implicit within the filename.
+
+=head1 BUGS AND CAVEATS AND TODO ITEMS
+
+Test that we have a t/*/*.run for each lib/*/*.pm
+
+Allow us to specify the nature of the failures, and which one.  If
+there are 15 lines of code, and six of them fail, how do we know
+they're the right six?
+
+=head1 AUTHOR
+
+Chris Dolan <cdolan@cpan.org>
+and the rest of the L<Perl::Critic> team.
+
+=head1 COPYRIGHT
+
+Copyright (c) 2005-2008 Chris Dolan.  All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.  The full text of this license
+can be found in the LICENSE file included with this module.
+
+=cut
+
+# Local Variables:
+#   mode: cperl
+#   cperl-indent-level: 4
+#   fill-column: 78
+#   indent-tabs-mode: nil
+#   c-indentation-style: bsd
+# End:
+# ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :