URI-Fetch-0.09/ 000755 000767 000767 00000000000 11520632644 014047 5 ustar 00btrott btrott 000000 000000 URI-Fetch-0.09/Changes 000644 000767 000767 00000005153 11520632532 015342 0 ustar 00btrott btrott 000000 000000 Revision history for URI::Fetch
0.09 2011.01.28
- Use $ua->env_proxy to load local proxy settings. (RT 53819)
- Skip tests if we don't have a network connection. (RT 28388, 59694)
- Removed sign() and auto_install() from Makefile.PL.
- Removed magic svn keywords.
- Added author tests (xt/) and modified SYNOPSIS for all modules to
make them pass the compilation test.
0.08 2006.07.24
- Don't overwrite the User-Agent field if the caller has set it and
provided the UserAgent argument. Thanks to Tatsuhiko Miyagawa
for the patch.
0.071 2006.06.25
- Fixed broken is_error (broken in 0.07). Thanks to Tatsuhiko for the
patch.
0.07 2006.06.18
- Use $res->header('Content-Type') instead of $res->content_type, since
the latter can return an array. Thanks to Tatsuhiko Miyagawa for the
patch.
- $res->is_success, is_error, and is_redirect previously threw an exception
when called with NoNetwork. They just work as 200 succesful request now,
when the response is taken back from the cache. Thanks to Tatsuhiko
Miyagawa for the patch.
0.06 2006.04.09
- Fixed issue where content-type was not stored in the cache, and was
thus blank on subsequent requests. Thanks to Tatsuhiko Miyagawa for
the patch.
- Fixed issue with caching redirected (304) URIs. Thanks to Tatsuhiko
Miyagawa for the patch.
- Added is_error, is_redirect, is_success convenience methods to
URI::Fetch::Response. Thanks to Tatsuhiko Miyagawa for the patch.
0.05 2006.02.24
- Added a ForceResponse option, which forces URI::Fetch->fetch to return
a URI::Fetch::Response object even if the HTTP request fails for an
unknown reason. Thanks to Tim Appnel for the patch.
0.04 2005.10.09
- Added Thaw and Freeze options, which allow you to define the
serialization and deserialization options that are used. Thanks to
Tim Appnel for the patch.
0.03 2005.05.27
- Added a NoNetwork option, allowing fetch to trust the cache completely
and skip the HTTP request; or the option to do this only if the version
in the cache is older than N seconds. [bradfitz]
- Added a CacheEntryGrep option, to allow for not caching certain
responses. [bradfitz]
- Documentation fixes & clarifications. [bradfitz]
0.02 2005.05.25
- Let the caller pass in their own UserAgent and ContentAlterHook.
[bradfitz]
- Be more strict about invalid parameters. [bradfitz]
- Documentation fixes for URI::Fetch->fetch in the SYNOPSIS. Thanks
to Naoya Ito for the note.
0.01 2004.12.31
- Initial distribution.
URI-Fetch-0.09/inc/ 000755 000767 000767 00000000000 11520632644 014620 5 ustar 00btrott btrott 000000 000000 URI-Fetch-0.09/lib/ 000755 000767 000767 00000000000 11520632644 014615 5 ustar 00btrott btrott 000000 000000 URI-Fetch-0.09/Makefile.PL 000644 000767 000767 00000000515 11520621147 016016 0 ustar 00btrott btrott 000000 000000 use inc::Module::Install;
name 'URI-Fetch';
all_from 'lib/URI/Fetch.pm';
readme_from 'lib/URI/Fetch.pm';
requires 'Class::ErrorHandler';
requires 'LWP';
requires 'URI';
requires 'Storable';
requires 'Compress::Zlib';
test_requires 'Test::More';
use_test_base;
auto_include_deps;
author_tests('xt');
auto_set_repository;
WriteAll;
URI-Fetch-0.09/MANIFEST 000644 000767 000767 00000001255 11520632641 015200 0 ustar 00btrott btrott 000000 000000 Changes
inc/Module/Install.pm
inc/Module/Install/AuthorTests.pm
inc/Module/Install/Base.pm
inc/Module/Install/Can.pm
inc/Module/Install/Fetch.pm
inc/Module/Install/Include.pm
inc/Module/Install/Makefile.pm
inc/Module/Install/Metadata.pm
inc/Module/Install/ReadmeFromPod.pm
inc/Module/Install/Repository.pm
inc/Module/Install/TestBase.pm
inc/Module/Install/Win32.pm
inc/Module/Install/WriteAll.pm
inc/Spiffy.pm
inc/Test/Base.pm
inc/Test/Base/Filter.pm
inc/Test/Builder.pm
inc/Test/Builder/Module.pm
inc/Test/More.pm
lib/URI/Fetch.pm
lib/URI/Fetch/Response.pm
Makefile.PL
MANIFEST This list of files
META.yml
README
t/00-compile.t
t/01-fetch.t
t/02-freezethaw.t
xt/pod.t
xt/synopsis.t
URI-Fetch-0.09/META.yml 000644 000767 000767 00000001177 11520632637 015330 0 ustar 00btrott btrott 000000 000000 ---
abstract: 'Smart URI fetching/caching'
author:
- Benjamin
build_requires:
ExtUtils::MakeMaker: 6.42
configure_requires:
ExtUtils::MakeMaker: 6.42
distribution_type: module
generated_by: 'Module::Install version 1.00'
license: perl
meta-spec:
url: http://module-build.sourceforge.net/META-spec-v1.4.html
version: 1.4
name: URI-Fetch
no_index:
directory:
- inc
- t
- xt
requires:
Class::ErrorHandler: 0
Compress::Zlib: 0
Filter::Util::Call: 0
LWP: 0
Storable: 0
URI: 0
perl: 5.8.1
resources:
license: http://dev.perl.org/licenses/
repository: git://github.com/btrott/URI-Fetch.git
version: 0.09
URI-Fetch-0.09/README 000644 000767 000767 00000016113 11520632637 014733 0 ustar 00btrott btrott 000000 000000 NAME
URI::Fetch - Smart URI fetching/caching
SYNOPSIS
use URI::Fetch;
## Simple fetch.
my $res = URI::Fetch->fetch('http://example.com/atom.xml')
or die URI::Fetch->errstr;
## Fetch using specified ETag and Last-Modified headers.
$res = URI::Fetch->fetch('http://example.com/atom.xml',
ETag => '123-ABC',
LastModified => time - 3600,
)
or die URI::Fetch->errstr;
## Fetch using an on-disk cache that URI::Fetch manages for you.
my $cache = Cache::File->new( cache_root => '/tmp/cache' );
$res = URI::Fetch->fetch('http://example.com/atom.xml',
Cache => $cache
)
or die URI::Fetch->errstr;
DESCRIPTION
*URI::Fetch* is a smart client for fetching HTTP pages, notably
syndication feeds (RSS, Atom, and others), in an intelligent, bandwidth-
and time-saving way. That means:
* GZIP support
If you have *Compress::Zlib* installed, *URI::Fetch* will
automatically try to download a compressed version of the content,
saving bandwidth (and time).
* *Last-Modified* and *ETag* support
If you use a local cache (see the *Cache* parameter to *fetch*),
*URI::Fetch* will keep track of the *Last-Modified* and *ETag*
headers from the server, allowing you to only download pages that
have been modified since the last time you checked.
* Proper understanding of HTTP error codes
Certain HTTP error codes are special, particularly when fetching
syndication feeds, and well-written clients should pay special
attention to them. *URI::Fetch* can only do so much for you in this
regard, but it gives you the tools to be a well-written client.
The response from *fetch* gives you the raw HTTP response code,
along with special handling of 4 codes:
* 200 (OK)
Signals that the content of a page/feed was retrieved
successfully.
* 301 (Moved Permanently)
Signals that a page/feed has moved permanently, and that your
database of feeds should be updated to reflect the new URI.
* 304 (Not Modified)
Signals that a page/feed has not changed since it was last
fetched.
* 410 (Gone)
Signals that a page/feed is gone and will never be coming back,
so you should stop trying to fetch it.
USAGE
URI::Fetch->fetch($uri, %param)
Fetches a page identified by the URI *$uri*.
On success, returns a *URI::Fetch::Response* object; on failure, returns
"undef".
*%param* can contain:
* LastModified
* ETag
*LastModified* and *ETag* can be supplied to force the server to
only return the full page if it's changed since the last request. If
you're writing your own feed client, this is recommended practice,
because it limits both your bandwidth use and the server's.
If you'd rather not have to store the *LastModified* time and *ETag*
yourself, see the *Cache* parameter below (and the SYNOPSIS above).
* Cache
If you'd like *URI::Fetch* to cache responses between requests,
provide the *Cache* parameter with an object supporting the Cache
API (e.g. *Cache::File*, *Cache::Memory*). Specifically, an object
that supports "$cache->get($key)" and "$cache->set($key, $value,
$expires)".
If supplied, *URI::Fetch* will store the page content, ETag, and
last-modified time of the response in the cache, and will pull the
content from the cache on subsequent requests if the page returns a
Not-Modified response.
* UserAgent
Optional. You may provide your own LWP::UserAgent instance. Look
into LWPx::ParanoidUserAgent if you're fetching URLs given to you by
possibly malicious parties.
* NoNetwork
Optional. Controls the interaction between the cache and HTTP
requests with If-Modified-Since/If-None-Match headers. Possible
behaviors are:
false (default)
If a page is in the cache, the origin HTTP server is always
checked for a fresher copy with an If-Modified-Since and/or
If-None-Match header.
1 If set to 1, the origin HTTP is never contacted, regardless of
the page being in cache or not. If the page is missing from
cache, the fetch method will return undef. If the page is in
cache, that page will be returned, no matter how old it is. Note
that setting this option means the URI::Fetch::Response object
will never have the http_response member set.
"N", where N > 1
The origin HTTP server is not contacted if the page is in cache
and the cached page was inserted in the last N seconds. If the
cached copy is older than N seconds, a normal HTTP request (full
or cache check) is done.
* ContentAlterHook
Optional. A subref that gets called with a scalar reference to your
content so you can modify the content before it's returned and
before it's put in cache.
For instance, you may want to only cache the
section of an
HTML document, or you may want to take a feed URL and cache only a
pre-parsed version of it. If you modify the scalarref given to your
hook and change it into a hashref, scalarref, or some blessed
object, that same value will be returned to you later on
not-modified responses.
* CacheEntryGrep
Optional. A subref that gets called with the *URI::Fetch::Response*
object about to be cached (with the contents already possibly
transformed by your "ContentAlterHook"). If your subref returns
true, the page goes into the cache. If false, it doesn't.
* Freeze
* Thaw
Optional. Subrefs that get called to serialize and deserialize,
respectively, the data that will be cached. The cached data should
be assumed to be an arbitrary Perl data structure, containing
(potentially) references to arrays, hashes, etc.
Freeze should serialize the structure into a scalar; Thaw should
deserialize the scalar into a data structure.
By default, *Storable* will be used for freezing and thawing the
cached data structure.
* ForceResponse
Optional. A boolean that indicates a *URI::Fetch::Response* should
be returned regardless of the HTTP status. By default "undef" is
returned when a response is not a "success" (200 codes) or one of
the recognized HTTP status codes listed above. The HTTP status
message can then be retreived using the "errstr" method on the
class.
LICENSE
*URI::Fetch* is free software; you may redistribute it and/or modify it
under the same terms as Perl itself.
AUTHOR & COPYRIGHT
Except where otherwise noted, *URI::Fetch* is Copyright 2004 Benjamin
Trott, ben+cpan@stupidfool.org. All rights reserved.
URI-Fetch-0.09/t/ 000755 000767 000767 00000000000 11520632644 014312 5 ustar 00btrott btrott 000000 000000 URI-Fetch-0.09/xt/ 000755 000767 000767 00000000000 11520632644 014502 5 ustar 00btrott btrott 000000 000000 URI-Fetch-0.09/xt/pod.t 000644 000767 000767 00000000201 11520614774 015446 0 ustar 00btrott btrott 000000 000000 use Test::More;
eval "use Test::Pod 1.00";
plan skip_all => "Test::Pod 1.00 required for testing POD" if $@;
all_pod_files_ok();
URI-Fetch-0.09/xt/synopsis.t 000644 000767 000767 00000000160 11520614774 016557 0 ustar 00btrott btrott 000000 000000 use Test::More;
eval "use Test::Synopsis";
plan skip_all => "Test::Synopsis required" if $@;
all_synopsis_ok();
URI-Fetch-0.09/t/00-compile.t 000644 000767 000767 00000000075 11520614461 016343 0 ustar 00btrott btrott 000000 000000 use strict;
use Test::More tests => 1;
use_ok 'URI::Fetch';
URI-Fetch-0.09/t/01-fetch.t 000644 000767 000767 00000012222 11520632247 016004 0 ustar 00btrott btrott 000000 000000 use strict;
use Test::More;
unless ( online() ) {
plan skip_all => 'Network access required for tests';
}
plan tests => 76;
use URI::Fetch;
use constant BASE => 'http://stupidfool.org/perl/feeds/';
use constant URI_OK => BASE . 'ok.xml';
use constant URI_MOVED => BASE . 'moved.xml';
use constant URI_GONE => BASE . 'gone.xml';
use constant URI_ERROR => BASE . 'error.xml';
my($res, $xml, $etag, $mtime);
## Test a basic fetch.
$res = URI::Fetch->fetch(URI_OK);
ok($res);
is($res->status, URI::Fetch::URI_OK());
is($res->http_status, 200);
ok($etag = $res->etag);
ok($mtime = $res->last_modified);
is($res->uri, URI_OK);
ok($xml = $res->content);
## Test a fetch using last-modified.
$res = URI::Fetch->fetch(URI_OK, LastModified => $mtime);
ok($res);
is($res->http_status, 304);
is($res->status, URI::Fetch::URI_NOT_MODIFIED());
is($res->content, undef);
## Test a fetch using etag.
$res = URI::Fetch->fetch(URI_OK, ETag => $etag);
ok($res);
is($res->http_status, 304);
is($res->status, URI::Fetch::URI_NOT_MODIFIED());
is($res->content, undef);
## Test a fetch using both.
$res = URI::Fetch->fetch(URI_OK, ETag => $etag, LastModified => $mtime);
ok($res);
is($res->http_status, 304);
is($res->status, URI::Fetch::URI_NOT_MODIFIED());
is($res->content, undef);
## Test a regular fetch using a cache.
my $cache = My::Cache->new;
$res = URI::Fetch->fetch(URI_OK, Cache => $cache);
ok($res);
is($res->http_status, 200);
ok($etag = $res->etag);
ok($mtime = $res->last_modified);
ok($xml = $res->content);
## Now hit the same URI again using the same cache, and hope to
## get back a not-modified response with the full content from the cache.
$res = URI::Fetch->fetch(URI_OK, Cache => $cache);
ok($res);
is($res->http_status, 304);
is($res->status, URI::Fetch::URI_NOT_MODIFIED());
is($res->etag, $etag);
is($res->last_modified, $mtime);
is($res->content, $xml);
## Test fetch of "moved permanently" resouce.
$res = URI::Fetch->fetch(URI_MOVED);
ok($res);
is($res->status, URI::Fetch::URI_MOVED_PERMANENTLY());
is($res->http_status, 200);
is($res->uri, URI_OK);
## Test fetch of "gone" resource.
$res = URI::Fetch->fetch(URI_GONE);
ok($res);
is($res->status, URI::Fetch::URI_GONE());
is($res->http_status, 410);
## Test fetch of unhandled error.
$res = URI::Fetch->fetch(URI_ERROR);
ok(!$res);
ok(URI::Fetch->errstr);
## Test ForceResponse.
$res = URI::Fetch->fetch(URI_ERROR, ForceResponse => 1);
isa_ok $res, 'URI::Fetch::Response';
is $res->http_status, 404;
ok $res->http_response;
## Test ContentAlterHook, wiping the cache
$cache = My::Cache->new;
$res = URI::Fetch->fetch(URI_OK, Cache => $cache, ContentAlterHook => sub { my $cref = shift; $$cref = "ALTERED."; });
ok($res);
is($res->http_status, 200);
ok($etag = $res->etag);
ok($mtime = $res->last_modified);
is($res->content, "ALTERED.");
## using the same cache, should still be altered
$res = URI::Fetch->fetch(URI_OK, Cache => $cache);
ok($res);
is($res->http_status, 304);
is($res->content, "ALTERED.");
## Test NoNetwork, wiping the cache
$cache = My::Cache->new;
## Content is not in cache, fetch should return undef
$res = URI::Fetch->fetch(URI_OK, Cache => $cache, NoNetwork => 1);
is($res, undef);
## Put the content in the cache.
$res = URI::Fetch->fetch(URI_OK, Cache => $cache);
ok($res);
is($res->http_status, 200);
ok($xml = $res->content);
$res = URI::Fetch->fetch(URI_OK, Cache => $cache, NoNetwork => 1);
ok($res);
is($res->status, URI::Fetch::URI_OK());
is($res->content, $xml);
ok(!$res->http_status); ## No http_status or http_response, because
ok(!$res->http_response); ## we skipped the HTTP request entirely.
ok($res->is_success); ## but still is_* should work
ok(!$res->is_error);
ok(!$res->is_redirect);
## Now sleep for 5 seconds, and try to get the content from the cache
## without a network connection, if the cached content is younger than
## 10 seconds. This should work.
sleep 5;
$res = URI::Fetch->fetch(URI_OK, Cache => $cache, NoNetwork => 10);
ok($res);
is($res->status, URI::Fetch::URI_OK());
is($res->content, $xml);
ok(!$res->http_status); ## No http_status or http_response, because
ok(!$res->http_response); ## we skipped the HTTP request entirely.
## Now try to get the content from the cache, but only if it is younger
## than 2 seconds. It is not, so we should make a full HTTP response
## with Etag and Last-modified, and get back a 304.
$res = URI::Fetch->fetch(URI_OK, Cache => $cache, NoNetwork => 2);
ok($res);
is($res->status, URI::Fetch::URI_NOT_MODIFIED());
is($res->http_status, 304);
ok($res->http_response);
is($res->content, $xml);
## Test CacheEntryGrep.
$cache = My::Cache->new;
$res = URI::Fetch->fetch(URI_OK, Cache => $cache, CacheEntryGrep => sub {
my($fetch) = @_;
$fetch->uri ne URI_OK; ## Do not cache this URI.
});
ok($res);
is($res->http_status, 200);
## Make sure the content was not cached (it would be 304 if it were).
$res = URI::Fetch->fetch(URI_OK, Cache => $cache);
ok($res);
is($res->http_status, 200);
sub online {
my $ua = LWP::UserAgent->new( env_proxy => 1, timeout => 30 );
my $res = $ua->get( 'http://google.com/' );
return $res->is_success ? 1 : 0;
}
package My::Cache;
sub new { bless {}, shift }
sub get { $_[0]->{ $_[1] } }
sub set { $_[0]->{ $_[1] } = $_[2] }
URI-Fetch-0.09/t/02-freezethaw.t 000644 000767 000767 00000002737 11520632333 017066 0 ustar 00btrott btrott 000000 000000 use strict;
use Test::More;
unless ( online() ) {
plan skip_all => 'Network access required for tests';
}
plan tests => 11;
use URI::Fetch;
use Data::Dumper;
use constant URI_OK => 'http://stupidfool.org/perl/feeds/ok.xml';
my($res, $xml, $etag, $mtime);
## Test a regular fetch using a cache and alternate freeze/thaw.
my $cache = My::Cache->new;
$res = URI::Fetch->fetch(URI_OK, Cache => $cache, Freeze=>\&freeze, Thaw=>\&thaw);
ok($res);
is($res->http_status, 200);
ok($etag = $res->etag);
ok($mtime = $res->last_modified);
ok($xml = $res->content);
## Now hit the same URI again using the same cache and see if it has the
## the correct info to get a 304 back.
$res = URI::Fetch->fetch(URI_OK, Cache => $cache, Freeze=>\&freeze, Thaw=>\&thaw);
ok($res);
is($res->http_status, 304);
is($res->status, URI::Fetch::URI_NOT_MODIFIED());
is($res->etag, $etag);
is($res->last_modified, $mtime);
is($res->content, $xml);
#--- alternate freeze/thaw routine
sub freeze {
my $data = shift; # ref to data structure
my $d = Data::Dumper->new([$data],['data']);
$d->Dump;
}
sub thaw {
my $data;
eval shift; # string from previous data dump
$data;
}
sub online {
my $ua = LWP::UserAgent->new( env_proxy => 1, timeout => 30 );
my $res = $ua->get( 'http://google.com/' );
return $res->is_success ? 1 : 0;
}
#--- simple in memory cache object
package My::Cache;
sub new { bless {}, shift }
sub get { $_[0]->{ $_[1] } }
sub set { $_[0]->{ $_[1] } = $_[2] }
URI-Fetch-0.09/lib/URI/ 000755 000767 000767 00000000000 11520632644 015254 5 ustar 00btrott btrott 000000 000000 URI-Fetch-0.09/lib/URI/Fetch/ 000755 000767 000767 00000000000 11520632644 016305 5 ustar 00btrott btrott 000000 000000 URI-Fetch-0.09/lib/URI/Fetch.pm 000644 000767 000767 00000026033 11520617123 016642 0 ustar 00btrott btrott 000000 000000 package URI::Fetch;
use strict;
use 5.008_001;
use base qw( Class::ErrorHandler );
use LWP::UserAgent;
use Carp qw( croak );
use URI;
use URI::Fetch::Response;
our $VERSION = '0.09';
our $HAS_ZLIB;
BEGIN {
$HAS_ZLIB = eval "use Compress::Zlib (); 1;";
}
use constant URI_OK => 200;
use constant URI_MOVED_PERMANENTLY => 301;
use constant URI_NOT_MODIFIED => 304;
use constant URI_GONE => 410;
sub fetch {
my $class = shift;
my($uri, %param) = @_;
# get user parameters
my $cache = delete $param{Cache};
my $ua = delete $param{UserAgent};
my $p_etag = delete $param{ETag};
my $p_lastmod = delete $param{LastModified};
my $content_hook = delete $param{ContentAlterHook};
my $p_no_net = delete $param{NoNetwork};
my $p_cache_grep = delete $param{CacheEntryGrep};
my $freeze = delete $param{Freeze};
my $thaw = delete $param{Thaw};
my $force = delete $param{ForceResponse};
croak("Unknown parameters: " . join(", ", keys %param))
if %param;
my $ref;
if ($cache) {
unless ($freeze && $thaw) {
require Storable;
$thaw = \&Storable::thaw;
$freeze = \&Storable::freeze;
}
if (my $blob = $cache->get($uri)) {
$ref = $thaw->($blob);
}
}
# NoNetwork support (see pod docs below for logic clarification)
if ($p_no_net) {
croak("Invalid NoNetworkValue (negative)") if $p_no_net < 0;
if ($ref && ($p_no_net == 1 || $ref->{CacheTime} > time() - $p_no_net)) {
my $fetch = URI::Fetch::Response->new;
$fetch->status(URI_OK);
$fetch->content($ref->{Content});
$fetch->etag($ref->{ETag});
$fetch->last_modified($ref->{LastModified});
$fetch->content_type($ref->{ContentType});
return $fetch;
}
return undef if $p_no_net == 1;
}
$ua ||= do {
my $ua = LWP::UserAgent->new;
$ua->agent(join '/', $class, $class->VERSION);
$ua->env_proxy;
$ua;
};
my $req = HTTP::Request->new(GET => $uri);
if ($HAS_ZLIB) {
$req->header('Accept-Encoding', 'gzip');
}
if (my $etag = ($p_etag || $ref->{ETag})) {
$req->header('If-None-Match', $etag);
}
if (my $ts = ($p_lastmod || $ref->{LastModified})) {
$req->if_modified_since($ts);
}
my $res = $ua->request($req);
my $fetch = URI::Fetch::Response->new;
$fetch->uri($uri);
$fetch->http_status($res->code);
$fetch->http_response($res);
$fetch->content_type($res->header('Content-Type'));
if ($res->previous && $res->previous->code == HTTP::Status::RC_MOVED_PERMANENTLY()) {
$fetch->status(URI_MOVED_PERMANENTLY);
$fetch->uri($res->previous->header('Location'));
} elsif ($res->code == HTTP::Status::RC_GONE()) {
$fetch->status(URI_GONE);
$fetch->uri(undef);
return $fetch;
} elsif ($res->code == HTTP::Status::RC_NOT_MODIFIED()) {
$fetch->status(URI_NOT_MODIFIED);
$fetch->content($ref->{Content});
$fetch->etag($ref->{ETag});
$fetch->last_modified($ref->{LastModified});
$fetch->content_type($ref->{ContentType});
return $fetch;
} elsif (!$res->is_success) {
return $force ? $fetch : $class->error($res->message);
} else {
$fetch->status(URI_OK);
}
$fetch->last_modified($res->last_modified);
$fetch->etag($res->header('ETag'));
my $content = $res->content;
if ($res->content_encoding && $res->content_encoding eq 'gzip') {
$content = Compress::Zlib::memGunzip($content);
}
# let caller-defined transform hook modify the result that'll be
# cached. perhaps the caller only wants the section of
# HTML, or wants to change the content to a parsed datastructure
# already serialized with Storable.
if ($content_hook) {
croak("ContentAlterHook is not a subref") unless ref $content_hook eq "CODE";
$content_hook->(\$content);
}
$fetch->content($content);
# cache by default, if there's a cache. but let callers cancel
# the cache action by defining a cache grep hook
if ($cache &&
($p_cache_grep ? $p_cache_grep->($fetch) : 1)) {
$cache->set($fetch->uri, $freeze->({
ETag => $fetch->etag,
LastModified => $fetch->last_modified,
Content => $fetch->content,
CacheTime => time(),
ContentType => $fetch->content_type,
}));
}
$fetch;
}
1;
__END__
=head1 NAME
URI::Fetch - Smart URI fetching/caching
=head1 SYNOPSIS
use URI::Fetch;
## Simple fetch.
my $res = URI::Fetch->fetch('http://example.com/atom.xml')
or die URI::Fetch->errstr;
## Fetch using specified ETag and Last-Modified headers.
$res = URI::Fetch->fetch('http://example.com/atom.xml',
ETag => '123-ABC',
LastModified => time - 3600,
)
or die URI::Fetch->errstr;
## Fetch using an on-disk cache that URI::Fetch manages for you.
my $cache = Cache::File->new( cache_root => '/tmp/cache' );
$res = URI::Fetch->fetch('http://example.com/atom.xml',
Cache => $cache
)
or die URI::Fetch->errstr;
=head1 DESCRIPTION
I is a smart client for fetching HTTP pages, notably
syndication feeds (RSS, Atom, and others), in an intelligent,
bandwidth- and time-saving way. That means:
=over 4
=item * GZIP support
If you have I installed, I will automatically
try to download a compressed version of the content, saving bandwidth (and
time).
=item * I and I support
If you use a local cache (see the I parameter to I),
I will keep track of the I and I headers
from the server, allowing you to only download pages that have been
modified since the last time you checked.
=item * Proper understanding of HTTP error codes
Certain HTTP error codes are special, particularly when fetching syndication
feeds, and well-written clients should pay special attention to them.
I can only do so much for you in this regard, but it gives
you the tools to be a well-written client.
The response from I gives you the raw HTTP response code, along with
special handling of 4 codes:
=over 4
=item * 200 (OK)
Signals that the content of a page/feed was retrieved
successfully.
=item * 301 (Moved Permanently)
Signals that a page/feed has moved permanently, and that
your database of feeds should be updated to reflect the new
URI.
=item * 304 (Not Modified)
Signals that a page/feed has not changed since it was last
fetched.
=item * 410 (Gone)
Signals that a page/feed is gone and will never be coming back,
so you should stop trying to fetch it.
=back
=back
=head1 USAGE
=head2 URI::Fetch->fetch($uri, %param)
Fetches a page identified by the URI I<$uri>.
On success, returns a I object; on failure, returns
C.
I<%param> can contain:
=over 4
=item * LastModified
=item * ETag
I and I can be supplied to force the server to only
return the full page if it's changed since the last request. If you're
writing your own feed client, this is recommended practice, because it
limits both your bandwidth use and the server's.
If you'd rather not have to store the I time and I
yourself, see the I parameter below (and the L above).
=item * Cache
If you'd like I to cache responses between requests, provide
the I parameter with an object supporting the L API (e.g.
I, I). Specifically, an object that supports
C<$cache-Eget($key)> and C<$cache-Eset($key, $value, $expires)>.
If supplied, I will store the page content, ETag, and
last-modified time of the response in the cache, and will pull the
content from the cache on subsequent requests if the page returns a
Not-Modified response.
=item * UserAgent
Optional. You may provide your own LWP::UserAgent instance. Look
into L if you're fetching URLs given to you
by possibly malicious parties.
=item * NoNetwork
Optional. Controls the interaction between the cache and HTTP
requests with If-Modified-Since/If-None-Match headers. Possible
behaviors are:
=over
=item false (default)
If a page is in the cache, the origin HTTP server is always checked
for a fresher copy with an If-Modified-Since and/or If-None-Match
header.
=item C<1>
If set to C<1>, the origin HTTP is never contacted, regardless of the
page being in cache or not. If the page is missing from cache, the
fetch method will return undef. If the page is in cache, that page
will be returned, no matter how old it is. Note that setting this
option means the L object will never have the
http_response member set.
=item C, where N E 1
The origin HTTP server is not contacted B the page is in cache
B the cached page was inserted in the last N seconds. If the
cached copy is older than N seconds, a normal HTTP request (full or
cache check) is done.
=back
=item * ContentAlterHook
Optional. A subref that gets called with a scalar reference to your
content so you can modify the content before it's returned and before
it's put in cache.
For instance, you may want to only cache the EheadE section of
an HTML document, or you may want to take a feed URL and cache only a
pre-parsed version of it. If you modify the scalarref given to your
hook and change it into a hashref, scalarref, or some blessed object,
that same value will be returned to you later on not-modified
responses.
=item * CacheEntryGrep
Optional. A subref that gets called with the I
object about to be cached (with the contents already possibly transformed by
your C). If your subref returns true, the page goes
into the cache. If false, it doesn't.
=item * Freeze
=item * Thaw
Optional. Subrefs that get called to serialize and deserialize, respectively,
the data that will be cached. The cached data should be assumed to be an
arbitrary Perl data structure, containing (potentially) references to
arrays, hashes, etc.
Freeze should serialize the structure into a scalar; Thaw should
deserialize the scalar into a data structure.
By default, I will be used for freezing and thawing the cached
data structure.
=item * ForceResponse
Optional. A boolean that indicates a I
should be returned regardless of the HTTP status. By
default C is returned when a response is not a
"success" (200 codes) or one of the recognized HTTP status
codes listed above. The HTTP status message can then be retreived
using the C method on the class.
=back
=head1 LICENSE
I is free software; you may redistribute it and/or modify it
under the same terms as Perl itself.
=head1 AUTHOR & COPYRIGHT
Except where otherwise noted, I is Copyright 2004 Benjamin
Trott, ben+cpan@stupidfool.org. All rights reserved.
=cut
URI-Fetch-0.09/lib/URI/Fetch/Response.pm 000644 000767 000767 00000005062 11520614730 020440 0 ustar 00btrott btrott 000000 000000 package URI::Fetch::Response;
use strict;
sub new {
my $class = shift;
my $feed = bless { }, $class;
$feed;
}
sub _var {
my $feed = shift;
my $var = shift;
$feed->{$var} = shift if @_;
$feed->{$var};
}
sub status { shift->_var('status', @_) }
sub http_status { shift->_var('http_status', @_) }
sub http_response { shift->_var('http_response', @_) }
sub etag { shift->_var('etag', @_) }
sub last_modified { shift->_var('last_modified', @_) }
sub uri { shift->_var('uri', @_) }
sub content { shift->_var('content', @_) }
sub content_type { shift->_var('content_type', @_) }
sub is_success {
my $response = shift;
return $response->http_response->is_success if $response->http_response;
return 1;
}
sub is_redirect {
my $response = shift;
return $response->http_response->is_redirect if $response->http_response;
return;
}
sub is_error {
my $response = shift;
return $response->http_response->is_error if $response->http_response;
return;
}
1;
__END__
=head1 NAME
URI::Fetch::Response - Feed response for URI::Fetch
=head1 SYNOPSIS
use URI::Fetch;
my $res = URI::Fetch->fetch('http://example.com/atom.xml')
or die URI::Fetch->errstr;
print $res->content;
=head1 DESCRIPTION
I encapsulates the response from fetching a feed
using I.
=head1 USAGE
=head2 $res->content
The contents of the feed.
=head2 $res->uri
The URI of the feed. If the feed was moved, this reflects the new URI;
otherwise, it will match the URI that you passed to I.
=head2 $res->etag
The ETag that was returned in the response, if any.
=head2 $res->last_modified
The Last-Modified date (in seconds since the epoch) that was returned in
the response, if any.
=head2 $res->status
The status of the response, which will match one of the following
enumerations:
=over 4
=item * URI::Fetch::URI_OK()
=item * URI::Fetch::URI_MOVED_PERMANENTLY()
=item * URI::Fetch::URI_GONE()
=item * URI::Fetch::URI_NOT_MODIFIED()
=back
=head2 $res->http_status
The HTTP status code from the response.
=head2 $res->http_response
The I object returned from the fetch.
=head2 $res->is_success
=head2 $res->is_redirect
=head2 $res->is_error
Wrappers around the C<$res-Eresponse> methods of the same name, for
convenience.
=head2 $res->content_type
The Content-Type header from the response.
=head1 AUTHOR & COPYRIGHT
Please see the I manpage for author, copyright, and license
information.
=cut
URI-Fetch-0.09/inc/Module/ 000755 000767 000767 00000000000 11520632644 016045 5 ustar 00btrott btrott 000000 000000 URI-Fetch-0.09/inc/Spiffy.pm 000644 000767 000767 00000036231 11520632637 016425 0 ustar 00btrott btrott 000000 000000 #line 1
package Spiffy;
use strict;
use 5.006001;
use warnings;
use Carp;
require Exporter;
our $VERSION = '0.30';
our @EXPORT = ();
our @EXPORT_BASE = qw(field const stub super);
our @EXPORT_OK = (@EXPORT_BASE, qw(id WWW XXX YYY ZZZ));
our %EXPORT_TAGS = (XXX => [qw(WWW XXX YYY ZZZ)]);
my $stack_frame = 0;
my $dump = 'yaml';
my $bases_map = {};
sub WWW; sub XXX; sub YYY; sub ZZZ;
# This line is here to convince "autouse" into believing we are autousable.
sub can {
($_[1] eq 'import' and caller()->isa('autouse'))
? \&Exporter::import # pacify autouse's equality test
: $_[0]->SUPER::can($_[1]) # normal case
}
# TODO
#
# Exported functions like field and super should be hidden so as not to
# be confused with methods that can be inherited.
#
sub new {
my $class = shift;
$class = ref($class) || $class;
my $self = bless {}, $class;
while (@_) {
my $method = shift;
$self->$method(shift);
}
return $self;
}
my $filtered_files = {};
my $filter_dump = 0;
my $filter_save = 0;
our $filter_result = '';
sub import {
no strict 'refs';
no warnings;
my $self_package = shift;
# XXX Using parse_arguments here might cause confusion, because the
# subclass's boolean_arguments and paired_arguments can conflict, causing
# difficult debugging. Consider using something truly local.
my ($args, @export_list) = do {
local *boolean_arguments = sub {
qw(
-base -Base -mixin -selfless
-XXX -dumper -yaml
-filter_dump -filter_save
)
};
local *paired_arguments = sub { qw(-package) };
$self_package->parse_arguments(@_);
};
return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
if $args->{-mixin};
$filter_dump = 1 if $args->{-filter_dump};
$filter_save = 1 if $args->{-filter_save};
$dump = 'yaml' if $args->{-yaml};
$dump = 'dumper' if $args->{-dumper};
local @EXPORT_BASE = @EXPORT_BASE;
if ($args->{-XXX}) {
push @EXPORT_BASE, @{$EXPORT_TAGS{XXX}}
unless grep /^XXX$/, @EXPORT_BASE;
}
spiffy_filter()
if ($args->{-selfless} or $args->{-Base}) and
not $filtered_files->{(caller($stack_frame))[1]}++;
my $caller_package = $args->{-package} || caller($stack_frame);
push @{"$caller_package\::ISA"}, $self_package
if $args->{-Base} or $args->{-base};
for my $class (@{all_my_bases($self_package)}) {
next unless $class->isa('Spiffy');
my @export = grep {
not defined &{"$caller_package\::$_"};
} ( @{"$class\::EXPORT"},
($args->{-Base} or $args->{-base})
? @{"$class\::EXPORT_BASE"} : (),
);
my @export_ok = grep {
not defined &{"$caller_package\::$_"};
} @{"$class\::EXPORT_OK"};
# Avoid calling the expensive Exporter::export
# if there is nothing to do (optimization)
my %exportable = map { ($_, 1) } @export, @export_ok;
next unless keys %exportable;
my @export_save = @{"$class\::EXPORT"};
my @export_ok_save = @{"$class\::EXPORT_OK"};
@{"$class\::EXPORT"} = @export;
@{"$class\::EXPORT_OK"} = @export_ok;
my @list = grep {
(my $v = $_) =~ s/^[\!\:]//;
$exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
} @export_list;
Exporter::export($class, $caller_package, @list);
@{"$class\::EXPORT"} = @export_save;
@{"$class\::EXPORT_OK"} = @export_ok_save;
}
}
sub spiffy_filter {
require Filter::Util::Call;
my $done = 0;
Filter::Util::Call::filter_add(
sub {
return 0 if $done;
my ($data, $end) = ('', '');
while (my $status = Filter::Util::Call::filter_read()) {
return $status if $status < 0;
if (/^__(?:END|DATA)__\r?$/) {
$end = $_;
last;
}
$data .= $_;
$_ = '';
}
$_ = $data;
my @my_subs;
s[^(sub\s+\w+\s+\{)(.*\n)]
[${1}my \$self = shift;$2]gm;
s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
[${1}${2}]gm;
s[^my\s+sub\s+(\w+)(\s+\{)(.*)((?s:.*?\n))\}\n]
[push @my_subs, $1; "\$$1 = sub$2my \$self = shift;$3$4\};\n"]gem;
my $preclare = '';
if (@my_subs) {
$preclare = join ',', map "\$$_", @my_subs;
$preclare = "my($preclare);";
}
$_ = "use strict;use warnings;$preclare${_};1;\n$end";
if ($filter_dump) { print; exit }
if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
$done = 1;
}
);
}
sub base {
push @_, -base;
goto &import;
}
sub all_my_bases {
my $class = shift;
return $bases_map->{$class}
if defined $bases_map->{$class};
my @bases = ($class);
no strict 'refs';
for my $base_class (@{"${class}::ISA"}) {
push @bases, @{all_my_bases($base_class)};
}
my $used = {};
$bases_map->{$class} = [grep {not $used->{$_}++} @bases];
}
my %code = (
sub_start =>
"sub {\n",
set_default =>
" \$_[0]->{%s} = %s\n unless exists \$_[0]->{%s};\n",
init =>
" return \$_[0]->{%s} = do { my \$self = \$_[0]; %s }\n" .
" unless \$#_ > 0 or defined \$_[0]->{%s};\n",
weak_init =>
" return do {\n" .
" \$_[0]->{%s} = do { my \$self = \$_[0]; %s };\n" .
" Scalar::Util::weaken(\$_[0]->{%s}) if ref \$_[0]->{%s};\n" .
" \$_[0]->{%s};\n" .
" } unless \$#_ > 0 or defined \$_[0]->{%s};\n",
return_if_get =>
" return \$_[0]->{%s} unless \$#_ > 0;\n",
set =>
" \$_[0]->{%s} = \$_[1];\n",
weaken =>
" Scalar::Util::weaken(\$_[0]->{%s}) if ref \$_[0]->{%s};\n",
sub_end =>
" return \$_[0]->{%s};\n}\n",
);
sub field {
my $package = caller;
my ($args, @values) = do {
no warnings;
local *boolean_arguments = sub { (qw(-weak)) };
local *paired_arguments = sub { (qw(-package -init)) };
Spiffy->parse_arguments(@_);
};
my ($field, $default) = @values;
$package = $args->{-package} if defined $args->{-package};
die "Cannot have a default for a weakened field ($field)"
if defined $default && $args->{-weak};
return if defined &{"${package}::$field"};
require Scalar::Util if $args->{-weak};
my $default_string =
( ref($default) eq 'ARRAY' and not @$default )
? '[]'
: (ref($default) eq 'HASH' and not keys %$default )
? '{}'
: default_as_code($default);
my $code = $code{sub_start};
if ($args->{-init}) {
my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
$code .= sprintf $fragment, $field, $args->{-init}, ($field) x 4;
}
$code .= sprintf $code{set_default}, $field, $default_string, $field
if defined $default;
$code .= sprintf $code{return_if_get}, $field;
$code .= sprintf $code{set}, $field;
$code .= sprintf $code{weaken}, $field, $field
if $args->{-weak};
$code .= sprintf $code{sub_end}, $field;
my $sub = eval $code;
die $@ if $@;
no strict 'refs';
*{"${package}::$field"} = $sub;
return $code if defined wantarray;
}
sub default_as_code {
require Data::Dumper;
local $Data::Dumper::Sortkeys = 1;
my $code = Data::Dumper::Dumper(shift);
$code =~ s/^\$VAR1 = //;
$code =~ s/;$//;
return $code;
}
sub const {
my $package = caller;
my ($args, @values) = do {
no warnings;
local *paired_arguments = sub { (qw(-package)) };
Spiffy->parse_arguments(@_);
};
my ($field, $default) = @values;
$package = $args->{-package} if defined $args->{-package};
no strict 'refs';
return if defined &{"${package}::$field"};
*{"${package}::$field"} = sub { $default }
}
sub stub {
my $package = caller;
my ($args, @values) = do {
no warnings;
local *paired_arguments = sub { (qw(-package)) };
Spiffy->parse_arguments(@_);
};
my ($field, $default) = @values;
$package = $args->{-package} if defined $args->{-package};
no strict 'refs';
return if defined &{"${package}::$field"};
*{"${package}::$field"} =
sub {
require Carp;
Carp::confess
"Method $field in package $package must be subclassed";
}
}
sub parse_arguments {
my $class = shift;
my ($args, @values) = ({}, ());
my %booleans = map { ($_, 1) } $class->boolean_arguments;
my %pairs = map { ($_, 1) } $class->paired_arguments;
while (@_) {
my $elem = shift;
if (defined $elem and defined $booleans{$elem}) {
$args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
? shift
: 1;
}
elsif (defined $elem and defined $pairs{$elem} and @_) {
$args->{$elem} = shift;
}
else {
push @values, $elem;
}
}
return wantarray ? ($args, @values) : $args;
}
sub boolean_arguments { () }
sub paired_arguments { () }
# get a unique id for any node
sub id {
if (not ref $_[0]) {
return 'undef' if not defined $_[0];
\$_[0] =~ /\((\w+)\)$/o or die;
return "$1-S";
}
require overload;
overload::StrVal($_[0]) =~ /\((\w+)\)$/o or die;
return $1;
}
#===============================================================================
# It's super, man.
#===============================================================================
package DB;
{
no warnings 'redefine';
sub super_args {
my @dummy = caller(@_ ? $_[0] : 2);
return @DB::args;
}
}
package Spiffy;
sub super {
my $method;
my $frame = 1;
while ($method = (caller($frame++))[3]) {
$method =~ s/.*::// and last;
}
my @args = DB::super_args($frame);
@_ = @_ ? ($args[0], @_) : @args;
my $class = ref $_[0] ? ref $_[0] : $_[0];
my $caller_class = caller;
my $seen = 0;
my @super_classes = reverse grep {
($seen or $seen = ($_ eq $caller_class)) ? 0 : 1;
} reverse @{all_my_bases($class)};
for my $super_class (@super_classes) {
no strict 'refs';
next if $super_class eq $class;
if (defined &{"${super_class}::$method"}) {
${"$super_class\::AUTOLOAD"} = ${"$class\::AUTOLOAD"}
if $method eq 'AUTOLOAD';
return &{"${super_class}::$method"};
}
}
return;
}
#===============================================================================
# This code deserves a spanking, because it is being very naughty.
# It is exchanging base.pm's import() for its own, so that people
# can use base.pm with Spiffy modules, without being the wiser.
#===============================================================================
my $real_base_import;
my $real_mixin_import;
BEGIN {
require base unless defined $INC{'base.pm'};
$INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
$real_base_import = \&base::import;
$real_mixin_import = \&mixin::import;
no warnings;
*base::import = \&spiffy_base_import;
*mixin::import = \&spiffy_mixin_import;
}
# my $i = 0;
# while (my $caller = caller($i++)) {
# next unless $caller eq 'base' or $caller eq 'mixin';
# croak <isa('Spiffy');
} @base_classes;
my $inheritor = caller(0);
for my $base_class (@base_classes) {
next if $inheritor->isa($base_class);
croak "Can't mix Spiffy and non-Spiffy classes in 'use base'.\n",
"See the documentation of Spiffy.pm for details\n "
unless $base_class->isa('Spiffy');
$stack_frame = 1; # tell import to use different caller
import($base_class, '-base');
$stack_frame = 0;
}
}
sub mixin {
my $self = shift;
my $target_class = ref($self);
spiffy_mixin_import($target_class, @_)
}
sub spiffy_mixin_import {
my $target_class = shift;
$target_class = caller(0)
if $target_class eq 'mixin';
my $mixin_class = shift
or die "Nothing to mixin";
eval "require $mixin_class";
my @roles = @_;
my $pseudo_class = join '-', $target_class, $mixin_class, @roles;
my %methods = spiffy_mixin_methods($mixin_class, @roles);
no strict 'refs';
no warnings;
@{"$pseudo_class\::ISA"} = @{"$target_class\::ISA"};
@{"$target_class\::ISA"} = ($pseudo_class);
for (keys %methods) {
*{"$pseudo_class\::$_"} = $methods{$_};
}
}
sub spiffy_mixin_methods {
my $mixin_class = shift;
no strict 'refs';
my %methods = spiffy_all_methods($mixin_class);
map {
$methods{$_}
? ($_, \ &{"$methods{$_}\::$_"})
: ($_, \ &{"$mixin_class\::$_"})
} @_
? (get_roles($mixin_class, @_))
: (keys %methods);
}
sub get_roles {
my $mixin_class = shift;
my @roles = @_;
while (grep /^!*:/, @roles) {
@roles = map {
s/!!//g;
/^!:(.*)/ ? do {
my $m = "_role_$1";
map("!$_", $mixin_class->$m);
} :
/^:(.*)/ ? do {
my $m = "_role_$1";
($mixin_class->$m);
} :
($_)
} @roles;
}
if (@roles and $roles[0] =~ /^!/) {
my %methods = spiffy_all_methods($mixin_class);
unshift @roles, keys(%methods);
}
my %roles;
for (@roles) {
s/!!//g;
delete $roles{$1}, next
if /^!(.*)/;
$roles{$_} = 1;
}
keys %roles;
}
sub spiffy_all_methods {
no strict 'refs';
my $class = shift;
return if $class eq 'Spiffy';
my %methods = map {
($_, $class)
} grep {
defined &{"$class\::$_"} and not /^_/
} keys %{"$class\::"};
my %super_methods;
%super_methods = spiffy_all_methods(${"$class\::ISA"}[0])
if @{"$class\::ISA"};
%{{%super_methods, %methods}};
}
# END of naughty code.
#===============================================================================
# Debugging support
#===============================================================================
sub spiffy_dump {
no warnings;
if ($dump eq 'dumper') {
require Data::Dumper;
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Indent = 1;
return Data::Dumper::Dumper(@_);
}
require YAML;
$YAML::UseVersion = 0;
return YAML::Dump(@_) . "...\n";
}
sub at_line_number {
my ($file_path, $line_number) = (caller(1))[1,2];
" at $file_path line $line_number\n";
}
sub WWW {
warn spiffy_dump(@_) . at_line_number;
return wantarray ? @_ : $_[0];
}
sub XXX {
die spiffy_dump(@_) . at_line_number;
}
sub YYY {
print spiffy_dump(@_) . at_line_number;
return wantarray ? @_ : $_[0];
}
sub ZZZ {
require Carp;
Carp::confess spiffy_dump(@_);
}
1;
__END__
#line 1066
URI-Fetch-0.09/inc/Test/ 000755 000767 000767 00000000000 11520632644 015537 5 ustar 00btrott btrott 000000 000000 URI-Fetch-0.09/inc/Test/Base/ 000755 000767 000767 00000000000 11520632644 016411 5 ustar 00btrott btrott 000000 000000 URI-Fetch-0.09/inc/Test/Base.pm 000644 000767 000767 00000043074 11520632637 016761 0 ustar 00btrott btrott 000000 000000 #line 1
# TODO:
#
package Test::Base;
use 5.006001;
use Spiffy 0.30 -Base;
use Spiffy ':XXX';
our $VERSION = '0.59';
my @test_more_exports;
BEGIN {
@test_more_exports = qw(
ok isnt like unlike is_deeply cmp_ok
skip todo_skip pass fail
eq_array eq_hash eq_set
plan can_ok isa_ok diag
use_ok
$TODO
);
}
use Test::More import => \@test_more_exports;
use Carp;
our @EXPORT = (@test_more_exports, qw(
is no_diff
blocks next_block first_block
delimiters spec_file spec_string
filters filters_delay filter_arguments
run run_compare run_is run_is_deeply run_like run_unlike
skip_all_unless_require is_deep run_is_deep
WWW XXX YYY ZZZ
tie_output no_diag_on_only
find_my_self default_object
croak carp cluck confess
));
field '_spec_file';
field '_spec_string';
field _filters => [qw(norm trim)];
field _filters_map => {};
field spec =>
-init => '$self->_spec_init';
field block_list =>
-init => '$self->_block_list_init';
field _next_list => [];
field block_delim =>
-init => '$self->block_delim_default';
field data_delim =>
-init => '$self->data_delim_default';
field _filters_delay => 0;
field _no_diag_on_only => 0;
field block_delim_default => '===';
field data_delim_default => '---';
my $default_class;
my $default_object;
my $reserved_section_names = {};
sub default_object {
$default_object ||= $default_class->new;
return $default_object;
}
my $import_called = 0;
sub import() {
$import_called = 1;
my $class = (grep /^-base$/i, @_)
? scalar(caller)
: $_[0];
if (not defined $default_class) {
$default_class = $class;
}
# else {
# croak "Can't use $class after using $default_class"
# unless $default_class->isa($class);
# }
unless (grep /^-base$/i, @_) {
my @args;
for (my $ii = 1; $ii <= $#_; ++$ii) {
if ($_[$ii] eq '-package') {
++$ii;
} else {
push @args, $_[$ii];
}
}
Test::More->import(import => \@test_more_exports, @args)
if @args;
}
_strict_warnings();
goto &Spiffy::import;
}
# Wrap Test::Builder::plan
my $plan_code = \&Test::Builder::plan;
my $Have_Plan = 0;
{
no warnings 'redefine';
*Test::Builder::plan = sub {
$Have_Plan = 1;
goto &$plan_code;
};
}
my $DIED = 0;
$SIG{__DIE__} = sub { $DIED = 1; die @_ };
sub block_class { $self->find_class('Block') }
sub filter_class { $self->find_class('Filter') }
sub find_class {
my $suffix = shift;
my $class = ref($self) . "::$suffix";
return $class if $class->can('new');
$class = __PACKAGE__ . "::$suffix";
return $class if $class->can('new');
eval "require $class";
return $class if $class->can('new');
die "Can't find a class for $suffix";
}
sub check_late {
if ($self->{block_list}) {
my $caller = (caller(1))[3];
$caller =~ s/.*:://;
croak "Too late to call $caller()"
}
}
sub find_my_self() {
my $self = ref($_[0]) eq $default_class
? splice(@_, 0, 1)
: default_object();
return $self, @_;
}
sub blocks() {
(my ($self), @_) = find_my_self(@_);
croak "Invalid arguments passed to 'blocks'"
if @_ > 1;
croak sprintf("'%s' is invalid argument to blocks()", shift(@_))
if @_ && $_[0] !~ /^[a-zA-Z]\w*$/;
my $blocks = $self->block_list;
my $section_name = shift || '';
my @blocks = $section_name
? (grep { exists $_->{$section_name} } @$blocks)
: (@$blocks);
return scalar(@blocks) unless wantarray;
return (@blocks) if $self->_filters_delay;
for my $block (@blocks) {
$block->run_filters
unless $block->is_filtered;
}
return (@blocks);
}
sub next_block() {
(my ($self), @_) = find_my_self(@_);
my $list = $self->_next_list;
if (@$list == 0) {
$list = [@{$self->block_list}, undef];
$self->_next_list($list);
}
my $block = shift @$list;
if (defined $block and not $block->is_filtered) {
$block->run_filters;
}
return $block;
}
sub first_block() {
(my ($self), @_) = find_my_self(@_);
$self->_next_list([]);
$self->next_block;
}
sub filters_delay() {
(my ($self), @_) = find_my_self(@_);
$self->_filters_delay(defined $_[0] ? shift : 1);
}
sub no_diag_on_only() {
(my ($self), @_) = find_my_self(@_);
$self->_no_diag_on_only(defined $_[0] ? shift : 1);
}
sub delimiters() {
(my ($self), @_) = find_my_self(@_);
$self->check_late;
my ($block_delimiter, $data_delimiter) = @_;
$block_delimiter ||= $self->block_delim_default;
$data_delimiter ||= $self->data_delim_default;
$self->block_delim($block_delimiter);
$self->data_delim($data_delimiter);
return $self;
}
sub spec_file() {
(my ($self), @_) = find_my_self(@_);
$self->check_late;
$self->_spec_file(shift);
return $self;
}
sub spec_string() {
(my ($self), @_) = find_my_self(@_);
$self->check_late;
$self->_spec_string(shift);
return $self;
}
sub filters() {
(my ($self), @_) = find_my_self(@_);
if (ref($_[0]) eq 'HASH') {
$self->_filters_map(shift);
}
else {
my $filters = $self->_filters;
push @$filters, @_;
}
return $self;
}
sub filter_arguments() {
$Test::Base::Filter::arguments;
}
sub have_text_diff {
eval { require Text::Diff; 1 } &&
$Text::Diff::VERSION >= 0.35 &&
$Algorithm::Diff::VERSION >= 1.15;
}
sub is($$;$) {
(my ($self), @_) = find_my_self(@_);
my ($actual, $expected, $name) = @_;
local $Test::Builder::Level = $Test::Builder::Level + 1;
if ($ENV{TEST_SHOW_NO_DIFFS} or
not defined $actual or
not defined $expected or
$actual eq $expected or
not($self->have_text_diff) or
$expected !~ /\n./s
) {
Test::More::is($actual, $expected, $name);
}
else {
$name = '' unless defined $name;
ok $actual eq $expected,
$name . "\n" . Text::Diff::diff(\$expected, \$actual);
}
}
sub run(&;$) {
(my ($self), @_) = find_my_self(@_);
my $callback = shift;
for my $block (@{$self->block_list}) {
$block->run_filters unless $block->is_filtered;
&{$callback}($block);
}
}
my $name_error = "Can't determine section names";
sub _section_names {
return @_ if @_ == 2;
my $block = $self->first_block
or croak $name_error;
my @names = grep {
$_ !~ /^(ONLY|LAST|SKIP)$/;
} @{$block->{_section_order}[0] || []};
croak "$name_error. Need two sections in first block"
unless @names == 2;
return @names;
}
sub _assert_plan {
plan('no_plan') unless $Have_Plan;
}
sub END {
run_compare() unless $Have_Plan or $DIED or not $import_called;
}
sub run_compare() {
(my ($self), @_) = find_my_self(@_);
$self->_assert_plan;
my ($x, $y) = $self->_section_names(@_);
local $Test::Builder::Level = $Test::Builder::Level + 1;
for my $block (@{$self->block_list}) {
next unless exists($block->{$x}) and exists($block->{$y});
$block->run_filters unless $block->is_filtered;
if (ref $block->$x) {
is_deeply($block->$x, $block->$y,
$block->name ? $block->name : ());
}
elsif (ref $block->$y eq 'Regexp') {
my $regexp = ref $y ? $y : $block->$y;
like($block->$x, $regexp, $block->name ? $block->name : ());
}
else {
is($block->$x, $block->$y, $block->name ? $block->name : ());
}
}
}
sub run_is() {
(my ($self), @_) = find_my_self(@_);
$self->_assert_plan;
my ($x, $y) = $self->_section_names(@_);
local $Test::Builder::Level = $Test::Builder::Level + 1;
for my $block (@{$self->block_list}) {
next unless exists($block->{$x}) and exists($block->{$y});
$block->run_filters unless $block->is_filtered;
is($block->$x, $block->$y,
$block->name ? $block->name : ()
);
}
}
sub run_is_deeply() {
(my ($self), @_) = find_my_self(@_);
$self->_assert_plan;
my ($x, $y) = $self->_section_names(@_);
for my $block (@{$self->block_list}) {
next unless exists($block->{$x}) and exists($block->{$y});
$block->run_filters unless $block->is_filtered;
is_deeply($block->$x, $block->$y,
$block->name ? $block->name : ()
);
}
}
sub run_like() {
(my ($self), @_) = find_my_self(@_);
$self->_assert_plan;
my ($x, $y) = $self->_section_names(@_);
for my $block (@{$self->block_list}) {
next unless exists($block->{$x}) and defined($y);
$block->run_filters unless $block->is_filtered;
my $regexp = ref $y ? $y : $block->$y;
like($block->$x, $regexp,
$block->name ? $block->name : ()
);
}
}
sub run_unlike() {
(my ($self), @_) = find_my_self(@_);
$self->_assert_plan;
my ($x, $y) = $self->_section_names(@_);
for my $block (@{$self->block_list}) {
next unless exists($block->{$x}) and defined($y);
$block->run_filters unless $block->is_filtered;
my $regexp = ref $y ? $y : $block->$y;
unlike($block->$x, $regexp,
$block->name ? $block->name : ()
);
}
}
sub skip_all_unless_require() {
(my ($self), @_) = find_my_self(@_);
my $module = shift;
eval "require $module; 1"
or Test::More::plan(
skip_all => "$module failed to load"
);
}
sub is_deep() {
(my ($self), @_) = find_my_self(@_);
require Test::Deep;
Test::Deep::cmp_deeply(@_);
}
sub run_is_deep() {
(my ($self), @_) = find_my_self(@_);
$self->_assert_plan;
my ($x, $y) = $self->_section_names(@_);
for my $block (@{$self->block_list}) {
next unless exists($block->{$x}) and exists($block->{$y});
$block->run_filters unless $block->is_filtered;
is_deep($block->$x, $block->$y,
$block->name ? $block->name : ()
);
}
}
sub _pre_eval {
my $spec = shift;
return $spec unless $spec =~
s/\A\s*<<<(.*?)>>>\s*$//sm;
my $eval_code = $1;
eval "package main; $eval_code";
croak $@ if $@;
return $spec;
}
sub _block_list_init {
my $spec = $self->spec;
$spec = $self->_pre_eval($spec);
my $cd = $self->block_delim;
my @hunks = ($spec =~ /^(\Q${cd}\E.*?(?=^\Q${cd}\E|\z))/msg);
my $blocks = $self->_choose_blocks(@hunks);
$self->block_list($blocks); # Need to set early for possible filter use
my $seq = 1;
for my $block (@$blocks) {
$block->blocks_object($self);
$block->seq_num($seq++);
}
return $blocks;
}
sub _choose_blocks {
my $blocks = [];
for my $hunk (@_) {
my $block = $self->_make_block($hunk);
if (exists $block->{ONLY}) {
diag "I found ONLY: maybe you're debugging?"
unless $self->_no_diag_on_only;
return [$block];
}
next if exists $block->{SKIP};
push @$blocks, $block;
if (exists $block->{LAST}) {
return $blocks;
}
}
return $blocks;
}
sub _check_reserved {
my $id = shift;
croak "'$id' is a reserved name. Use something else.\n"
if $reserved_section_names->{$id} or
$id =~ /^_/;
}
sub _make_block {
my $hunk = shift;
my $cd = $self->block_delim;
my $dd = $self->data_delim;
my $block = $self->block_class->new;
$hunk =~ s/\A\Q${cd}\E[ \t]*(.*)\s+// or die;
my $name = $1;
my @parts = split /^\Q${dd}\E +\(?(\w+)\)? *(.*)?\n/m, $hunk;
my $description = shift @parts;
$description ||= '';
unless ($description =~ /\S/) {
$description = $name;
}
$description =~ s/\s*\z//;
$block->set_value(description => $description);
my $section_map = {};
my $section_order = [];
while (@parts) {
my ($type, $filters, $value) = splice(@parts, 0, 3);
$self->_check_reserved($type);
$value = '' unless defined $value;
$filters = '' unless defined $filters;
if ($filters =~ /:(\s|\z)/) {
croak "Extra lines not allowed in '$type' section"
if $value =~ /\S/;
($filters, $value) = split /\s*:(?:\s+|\z)/, $filters, 2;
$value = '' unless defined $value;
$value =~ s/^\s*(.*?)\s*$/$1/;
}
$section_map->{$type} = {
filters => $filters,
};
push @$section_order, $type;
$block->set_value($type, $value);
}
$block->set_value(name => $name);
$block->set_value(_section_map => $section_map);
$block->set_value(_section_order => $section_order);
return $block;
}
sub _spec_init {
return $self->_spec_string
if $self->_spec_string;
local $/;
my $spec;
if (my $spec_file = $self->_spec_file) {
open FILE, $spec_file or die $!;
$spec = ;
close FILE;
}
else {
$spec = do {
package main;
no warnings 'once';
;
};
}
return $spec;
}
sub _strict_warnings() {
require Filter::Util::Call;
my $done = 0;
Filter::Util::Call::filter_add(
sub {
return 0 if $done;
my ($data, $end) = ('', '');
while (my $status = Filter::Util::Call::filter_read()) {
return $status if $status < 0;
if (/^__(?:END|DATA)__\r?$/) {
$end = $_;
last;
}
$data .= $_;
$_ = '';
}
$_ = "use strict;use warnings;$data$end";
$done = 1;
}
);
}
sub tie_output() {
my $handle = shift;
die "No buffer to tie" unless @_;
tie $handle, 'Test::Base::Handle', $_[0];
}
sub no_diff {
$ENV{TEST_SHOW_NO_DIFFS} = 1;
}
package Test::Base::Handle;
sub TIEHANDLE() {
my $class = shift;
bless \ $_[0], $class;
}
sub PRINT {
$$self .= $_ for @_;
}
#===============================================================================
# Test::Base::Block
#
# This is the default class for accessing a Test::Base block object.
#===============================================================================
package Test::Base::Block;
our @ISA = qw(Spiffy);
our @EXPORT = qw(block_accessor);
sub AUTOLOAD {
return;
}
sub block_accessor() {
my $accessor = shift;
no strict 'refs';
return if defined &$accessor;
*$accessor = sub {
my $self = shift;
if (@_) {
Carp::croak "Not allowed to set values for '$accessor'";
}
my @list = @{$self->{$accessor} || []};
return wantarray
? (@list)
: $list[0];
};
}
block_accessor 'name';
block_accessor 'description';
Spiffy::field 'seq_num';
Spiffy::field 'is_filtered';
Spiffy::field 'blocks_object';
Spiffy::field 'original_values' => {};
sub set_value {
no strict 'refs';
my $accessor = shift;
block_accessor $accessor
unless defined &$accessor;
$self->{$accessor} = [@_];
}
sub run_filters {
my $map = $self->_section_map;
my $order = $self->_section_order;
Carp::croak "Attempt to filter a block twice"
if $self->is_filtered;
for my $type (@$order) {
my $filters = $map->{$type}{filters};
my @value = $self->$type;
$self->original_values->{$type} = $value[0];
for my $filter ($self->_get_filters($type, $filters)) {
$Test::Base::Filter::arguments =
$filter =~ s/=(.*)$// ? $1 : undef;
my $function = "main::$filter";
no strict 'refs';
if (defined &$function) {
local $_ =
(@value == 1 and not defined($value[0])) ? undef :
join '', @value;
my $old = $_;
@value = &$function(@value);
if (not(@value) or
@value == 1 and defined($value[0]) and $value[0] =~ /\A(\d+|)\z/
) {
if ($value[0] && $_ eq $old) {
Test::Base::diag("Filters returning numbers are supposed to do munging \$_: your filter '$function' apparently doesn't.");
}
@value = ($_);
}
}
else {
my $filter_object = $self->blocks_object->filter_class->new;
die "Can't find a function or method for '$filter' filter\n"
unless $filter_object->can($filter);
$filter_object->current_block($self);
@value = $filter_object->$filter(@value);
}
# Set the value after each filter since other filters may be
# introspecting.
$self->set_value($type, @value);
}
}
$self->is_filtered(1);
}
sub _get_filters {
my $type = shift;
my $string = shift || '';
$string =~ s/\s*(.*?)\s*/$1/;
my @filters = ();
my $map_filters = $self->blocks_object->_filters_map->{$type} || [];
$map_filters = [ $map_filters ] unless ref $map_filters;
my @append = ();
for (
@{$self->blocks_object->_filters},
@$map_filters,
split(/\s+/, $string),
) {
my $filter = $_;
last unless length $filter;
if ($filter =~ s/^-//) {
@filters = grep { $_ ne $filter } @filters;
}
elsif ($filter =~ s/^\+//) {
push @append, $filter;
}
else {
push @filters, $filter;
}
}
return @filters, @append;
}
{
%$reserved_section_names = map {
($_, 1);
} keys(%Test::Base::Block::), qw( new DESTROY );
}
__DATA__
=encoding utf8
#line 1376
URI-Fetch-0.09/inc/Test/Builder/ 000755 000767 000767 00000000000 11520632644 017125 5 ustar 00btrott btrott 000000 000000 URI-Fetch-0.09/inc/Test/Builder.pm 000644 000767 000767 00000106345 11520632637 017476 0 ustar 00btrott btrott 000000 000000 #line 1
package Test::Builder;
use 5.006;
use strict;
use warnings;
our $VERSION = '0.96';
$VERSION = eval $VERSION; ## no critic (BuiltinFunctions::ProhibitStringyEval)
BEGIN {
if( $] < 5.008 ) {
require Test::Builder::IO::Scalar;
}
}
# Make Test::Builder thread-safe for ithreads.
BEGIN {
use Config;
# Load threads::shared when threads are turned on.
# 5.8.0's threads are so busted we no longer support them.
if( $] >= 5.008001 && $Config{useithreads} && $INC{'threads.pm'} ) {
require threads::shared;
# Hack around YET ANOTHER threads::shared bug. It would
# occasionally forget the contents of the variable when sharing it.
# So we first copy the data, then share, then put our copy back.
*share = sub (\[$@%]) {
my $type = ref $_[0];
my $data;
if( $type eq 'HASH' ) {
%$data = %{ $_[0] };
}
elsif( $type eq 'ARRAY' ) {
@$data = @{ $_[0] };
}
elsif( $type eq 'SCALAR' ) {
$$data = ${ $_[0] };
}
else {
die( "Unknown type: " . $type );
}
$_[0] = &threads::shared::share( $_[0] );
if( $type eq 'HASH' ) {
%{ $_[0] } = %$data;
}
elsif( $type eq 'ARRAY' ) {
@{ $_[0] } = @$data;
}
elsif( $type eq 'SCALAR' ) {
${ $_[0] } = $$data;
}
else {
die( "Unknown type: " . $type );
}
return $_[0];
};
}
# 5.8.0's threads::shared is busted when threads are off
# and earlier Perls just don't have that module at all.
else {
*share = sub { return $_[0] };
*lock = sub { 0 };
}
}
#line 117
our $Test = Test::Builder->new;
sub new {
my($class) = shift;
$Test ||= $class->create;
return $Test;
}
#line 139
sub create {
my $class = shift;
my $self = bless {}, $class;
$self->reset;
return $self;
}
#line 168
sub child {
my( $self, $name ) = @_;
if( $self->{Child_Name} ) {
$self->croak("You already have a child named ($self->{Child_Name}) running");
}
my $parent_in_todo = $self->in_todo;
# Clear $TODO for the child.
my $orig_TODO = $self->find_TODO(undef, 1, undef);
my $child = bless {}, ref $self;
$child->reset;
# Add to our indentation
$child->_indent( $self->_indent . ' ' );
$child->{$_} = $self->{$_} foreach qw{Out_FH Todo_FH Fail_FH};
if ($parent_in_todo) {
$child->{Fail_FH} = $self->{Todo_FH};
}
# This will be reset in finalize. We do this here lest one child failure
# cause all children to fail.
$child->{Child_Error} = $?;
$? = 0;
$child->{Parent} = $self;
$child->{Parent_TODO} = $orig_TODO;
$child->{Name} = $name || "Child of " . $self->name;
$self->{Child_Name} = $child->name;
return $child;
}
#line 211
sub subtest {
my $self = shift;
my($name, $subtests) = @_;
if ('CODE' ne ref $subtests) {
$self->croak("subtest()'s second argument must be a code ref");
}
# Turn the child into the parent so anyone who has stored a copy of
# the Test::Builder singleton will get the child.
my($error, $child, %parent);
{
# child() calls reset() which sets $Level to 1, so we localize
# $Level first to limit the scope of the reset to the subtest.
local $Test::Builder::Level = $Test::Builder::Level + 1;
$child = $self->child($name);
%parent = %$self;
%$self = %$child;
my $run_the_subtests = sub {
$subtests->();
$self->done_testing unless $self->_plan_handled;
1;
};
if( !eval { $run_the_subtests->() } ) {
$error = $@;
}
}
# Restore the parent and the copied child.
%$child = %$self;
%$self = %parent;
# Restore the parent's $TODO
$self->find_TODO(undef, 1, $child->{Parent_TODO});
# Die *after* we restore the parent.
die $error if $error and !eval { $error->isa('Test::Builder::Exception') };
local $Test::Builder::Level = $Test::Builder::Level + 1;
return $child->finalize;
}
#line 281
sub _plan_handled {
my $self = shift;
return $self->{Have_Plan} || $self->{No_Plan} || $self->{Skip_All};
}
#line 306
sub finalize {
my $self = shift;
return unless $self->parent;
if( $self->{Child_Name} ) {
$self->croak("Can't call finalize() with child ($self->{Child_Name}) active");
}
$self->_ending;
# XXX This will only be necessary for TAP envelopes (we think)
#$self->_print( $self->is_passing ? "PASS\n" : "FAIL\n" );
local $Test::Builder::Level = $Test::Builder::Level + 1;
my $ok = 1;
$self->parent->{Child_Name} = undef;
if ( $self->{Skip_All} ) {
$self->parent->skip($self->{Skip_All});
}
elsif ( not @{ $self->{Test_Results} } ) {
$self->parent->ok( 0, sprintf q[No tests run for subtest "%s"], $self->name );
}
else {
$self->parent->ok( $self->is_passing, $self->name );
}
$? = $self->{Child_Error};
delete $self->{Parent};
return $self->is_passing;
}
sub _indent {
my $self = shift;
if( @_ ) {
$self->{Indent} = shift;
}
return $self->{Indent};
}
#line 357
sub parent { shift->{Parent} }
#line 369
sub name { shift->{Name} }
sub DESTROY {
my $self = shift;
if ( $self->parent and $$ == $self->{Original_Pid} ) {
my $name = $self->name;
$self->diag(<<"FAIL");
Child ($name) exited without calling finalize()
FAIL
$self->parent->{In_Destroy} = 1;
$self->parent->ok(0, $name);
}
}
#line 393
our $Level;
sub reset { ## no critic (Subroutines::ProhibitBuiltinHomonyms)
my($self) = @_;
# We leave this a global because it has to be localized and localizing
# hash keys is just asking for pain. Also, it was documented.
$Level = 1;
$self->{Name} = $0;
$self->is_passing(1);
$self->{Ending} = 0;
$self->{Have_Plan} = 0;
$self->{No_Plan} = 0;
$self->{Have_Output_Plan} = 0;
$self->{Done_Testing} = 0;
$self->{Original_Pid} = $$;
$self->{Child_Name} = undef;
$self->{Indent} ||= '';
share( $self->{Curr_Test} );
$self->{Curr_Test} = 0;
$self->{Test_Results} = &share( [] );
$self->{Exported_To} = undef;
$self->{Expected_Tests} = 0;
$self->{Skip_All} = 0;
$self->{Use_Nums} = 1;
$self->{No_Header} = 0;
$self->{No_Ending} = 0;
$self->{Todo} = undef;
$self->{Todo_Stack} = [];
$self->{Start_Todo} = 0;
$self->{Opened_Testhandles} = 0;
$self->_dup_stdhandles;
return;
}
#line 472
my %plan_cmds = (
no_plan => \&no_plan,
skip_all => \&skip_all,
tests => \&_plan_tests,
);
sub plan {
my( $self, $cmd, $arg ) = @_;
return unless $cmd;
local $Level = $Level + 1;
$self->croak("You tried to plan twice") if $self->{Have_Plan};
if( my $method = $plan_cmds{$cmd} ) {
local $Level = $Level + 1;
$self->$method($arg);
}
else {
my @args = grep { defined } ( $cmd, $arg );
$self->croak("plan() doesn't understand @args");
}
return 1;
}
sub _plan_tests {
my($self, $arg) = @_;
if($arg) {
local $Level = $Level + 1;
return $self->expected_tests($arg);
}
elsif( !defined $arg ) {
$self->croak("Got an undefined number of tests");
}
else {
$self->croak("You said to run 0 tests");
}
return;
}
#line 527
sub expected_tests {
my $self = shift;
my($max) = @_;
if(@_) {
$self->croak("Number of tests must be a positive integer. You gave it '$max'")
unless $max =~ /^\+?\d+$/;
$self->{Expected_Tests} = $max;
$self->{Have_Plan} = 1;
$self->_output_plan($max) unless $self->no_header;
}
return $self->{Expected_Tests};
}
#line 551
sub no_plan {
my($self, $arg) = @_;
$self->carp("no_plan takes no arguments") if $arg;
$self->{No_Plan} = 1;
$self->{Have_Plan} = 1;
return 1;
}
#line 584
sub _output_plan {
my($self, $max, $directive, $reason) = @_;
$self->carp("The plan was already output") if $self->{Have_Output_Plan};
my $plan = "1..$max";
$plan .= " # $directive" if defined $directive;
$plan .= " $reason" if defined $reason;
$self->_print("$plan\n");
$self->{Have_Output_Plan} = 1;
return;
}
#line 636
sub done_testing {
my($self, $num_tests) = @_;
# If done_testing() specified the number of tests, shut off no_plan.
if( defined $num_tests ) {
$self->{No_Plan} = 0;
}
else {
$num_tests = $self->current_test;
}
if( $self->{Done_Testing} ) {
my($file, $line) = @{$self->{Done_Testing}}[1,2];
$self->ok(0, "done_testing() was already called at $file line $line");
return;
}
$self->{Done_Testing} = [caller];
if( $self->expected_tests && $num_tests != $self->expected_tests ) {
$self->ok(0, "planned to run @{[ $self->expected_tests ]} ".
"but done_testing() expects $num_tests");
}
else {
$self->{Expected_Tests} = $num_tests;
}
$self->_output_plan($num_tests) unless $self->{Have_Output_Plan};
$self->{Have_Plan} = 1;
# The wrong number of tests were run
$self->is_passing(0) if $self->{Expected_Tests} != $self->{Curr_Test};
# No tests were run
$self->is_passing(0) if $self->{Curr_Test} == 0;
return 1;
}
#line 687
sub has_plan {
my $self = shift;
return( $self->{Expected_Tests} ) if $self->{Expected_Tests};
return('no_plan') if $self->{No_Plan};
return(undef);
}
#line 704
sub skip_all {
my( $self, $reason ) = @_;
$self->{Skip_All} = $self->parent ? $reason : 1;
$self->_output_plan(0, "SKIP", $reason) unless $self->no_header;
if ( $self->parent ) {
die bless {} => 'Test::Builder::Exception';
}
exit(0);
}
#line 729
sub exported_to {
my( $self, $pack ) = @_;
if( defined $pack ) {
$self->{Exported_To} = $pack;
}
return $self->{Exported_To};
}
#line 759
sub ok {
my( $self, $test, $name ) = @_;
if ( $self->{Child_Name} and not $self->{In_Destroy} ) {
$name = 'unnamed test' unless defined $name;
$self->is_passing(0);
$self->croak("Cannot run test ($name) with active children");
}
# $test might contain an object which we don't want to accidentally
# store, so we turn it into a boolean.
$test = $test ? 1 : 0;
lock $self->{Curr_Test};
$self->{Curr_Test}++;
# In case $name is a string overloaded object, force it to stringify.
$self->_unoverload_str( \$name );
$self->diag(<<"ERR") if defined $name and $name =~ /^[\d\s]+$/;
You named your test '$name'. You shouldn't use numbers for your test names.
Very confusing.
ERR
# Capture the value of $TODO for the rest of this ok() call
# so it can more easily be found by other routines.
my $todo = $self->todo();
my $in_todo = $self->in_todo;
local $self->{Todo} = $todo if $in_todo;
$self->_unoverload_str( \$todo );
my $out;
my $result = &share( {} );
unless($test) {
$out .= "not ";
@$result{ 'ok', 'actual_ok' } = ( ( $self->in_todo ? 1 : 0 ), 0 );
}
else {
@$result{ 'ok', 'actual_ok' } = ( 1, $test );
}
$out .= "ok";
$out .= " $self->{Curr_Test}" if $self->use_numbers;
if( defined $name ) {
$name =~ s|#|\\#|g; # # in a name can confuse Test::Harness.
$out .= " - $name";
$result->{name} = $name;
}
else {
$result->{name} = '';
}
if( $self->in_todo ) {
$out .= " # TODO $todo";
$result->{reason} = $todo;
$result->{type} = 'todo';
}
else {
$result->{reason} = '';
$result->{type} = '';
}
$self->{Test_Results}[ $self->{Curr_Test} - 1 ] = $result;
$out .= "\n";
$self->_print($out);
unless($test) {
my $msg = $self->in_todo ? "Failed (TODO)" : "Failed";
$self->_print_to_fh( $self->_diag_fh, "\n" ) if $ENV{HARNESS_ACTIVE};
my( undef, $file, $line ) = $self->caller;
if( defined $name ) {
$self->diag(qq[ $msg test '$name'\n]);
$self->diag(qq[ at $file line $line.\n]);
}
else {
$self->diag(qq[ $msg test at $file line $line.\n]);
}
}
$self->is_passing(0) unless $test || $self->in_todo;
# Check that we haven't violated the plan
$self->_check_is_passing_plan();
return $test ? 1 : 0;
}
# Check that we haven't yet violated the plan and set
# is_passing() accordingly
sub _check_is_passing_plan {
my $self = shift;
my $plan = $self->has_plan;
return unless defined $plan; # no plan yet defined
return unless $plan !~ /\D/; # no numeric plan
$self->is_passing(0) if $plan < $self->{Curr_Test};
}
sub _unoverload {
my $self = shift;
my $type = shift;
$self->_try(sub { require overload; }, die_on_fail => 1);
foreach my $thing (@_) {
if( $self->_is_object($$thing) ) {
if( my $string_meth = overload::Method( $$thing, $type ) ) {
$$thing = $$thing->$string_meth();
}
}
}
return;
}
sub _is_object {
my( $self, $thing ) = @_;
return $self->_try( sub { ref $thing && $thing->isa('UNIVERSAL') } ) ? 1 : 0;
}
sub _unoverload_str {
my $self = shift;
return $self->_unoverload( q[""], @_ );
}
sub _unoverload_num {
my $self = shift;
$self->_unoverload( '0+', @_ );
for my $val (@_) {
next unless $self->_is_dualvar($$val);
$$val = $$val + 0;
}
return;
}
# This is a hack to detect a dualvar such as $!
sub _is_dualvar {
my( $self, $val ) = @_;
# Objects are not dualvars.
return 0 if ref $val;
no warnings 'numeric';
my $numval = $val + 0;
return $numval != 0 and $numval ne $val ? 1 : 0;
}
#line 933
sub is_eq {
my( $self, $got, $expect, $name ) = @_;
local $Level = $Level + 1;
if( !defined $got || !defined $expect ) {
# undef only matches undef and nothing else
my $test = !defined $got && !defined $expect;
$self->ok( $test, $name );
$self->_is_diag( $got, 'eq', $expect ) unless $test;
return $test;
}
return $self->cmp_ok( $got, 'eq', $expect, $name );
}
sub is_num {
my( $self, $got, $expect, $name ) = @_;
local $Level = $Level + 1;
if( !defined $got || !defined $expect ) {
# undef only matches undef and nothing else
my $test = !defined $got && !defined $expect;
$self->ok( $test, $name );
$self->_is_diag( $got, '==', $expect ) unless $test;
return $test;
}
return $self->cmp_ok( $got, '==', $expect, $name );
}
sub _diag_fmt {
my( $self, $type, $val ) = @_;
if( defined $$val ) {
if( $type eq 'eq' or $type eq 'ne' ) {
# quote and force string context
$$val = "'$$val'";
}
else {
# force numeric context
$self->_unoverload_num($val);
}
}
else {
$$val = 'undef';
}
return;
}
sub _is_diag {
my( $self, $got, $type, $expect ) = @_;
$self->_diag_fmt( $type, $_ ) for \$got, \$expect;
local $Level = $Level + 1;
return $self->diag(<<"DIAGNOSTIC");
got: $got
expected: $expect
DIAGNOSTIC
}
sub _isnt_diag {
my( $self, $got, $type ) = @_;
$self->_diag_fmt( $type, \$got );
local $Level = $Level + 1;
return $self->diag(<<"DIAGNOSTIC");
got: $got
expected: anything else
DIAGNOSTIC
}
#line 1026
sub isnt_eq {
my( $self, $got, $dont_expect, $name ) = @_;
local $Level = $Level + 1;
if( !defined $got || !defined $dont_expect ) {
# undef only matches undef and nothing else
my $test = defined $got || defined $dont_expect;
$self->ok( $test, $name );
$self->_isnt_diag( $got, 'ne' ) unless $test;
return $test;
}
return $self->cmp_ok( $got, 'ne', $dont_expect, $name );
}
sub isnt_num {
my( $self, $got, $dont_expect, $name ) = @_;
local $Level = $Level + 1;
if( !defined $got || !defined $dont_expect ) {
# undef only matches undef and nothing else
my $test = defined $got || defined $dont_expect;
$self->ok( $test, $name );
$self->_isnt_diag( $got, '!=' ) unless $test;
return $test;
}
return $self->cmp_ok( $got, '!=', $dont_expect, $name );
}
#line 1075
sub like {
my( $self, $this, $regex, $name ) = @_;
local $Level = $Level + 1;
return $self->_regex_ok( $this, $regex, '=~', $name );
}
sub unlike {
my( $self, $this, $regex, $name ) = @_;
local $Level = $Level + 1;
return $self->_regex_ok( $this, $regex, '!~', $name );
}
#line 1099
my %numeric_cmps = map { ( $_, 1 ) } ( "<", "<=", ">", ">=", "==", "!=", "<=>" );
sub cmp_ok {
my( $self, $got, $type, $expect, $name ) = @_;
my $test;
my $error;
{
## no critic (BuiltinFunctions::ProhibitStringyEval)
local( $@, $!, $SIG{__DIE__} ); # isolate eval
my($pack, $file, $line) = $self->caller();
# This is so that warnings come out at the caller's level
$test = eval qq[
#line $line "(eval in cmp_ok) $file"
\$got $type \$expect;
];
$error = $@;
}
local $Level = $Level + 1;
my $ok = $self->ok( $test, $name );
# Treat overloaded objects as numbers if we're asked to do a
# numeric comparison.
my $unoverload
= $numeric_cmps{$type}
? '_unoverload_num'
: '_unoverload_str';
$self->diag(<<"END") if $error;
An error occurred while using $type:
------------------------------------
$error
------------------------------------
END
unless($ok) {
$self->$unoverload( \$got, \$expect );
if( $type =~ /^(eq|==)$/ ) {
$self->_is_diag( $got, $type, $expect );
}
elsif( $type =~ /^(ne|!=)$/ ) {
$self->_isnt_diag( $got, $type );
}
else {
$self->_cmp_diag( $got, $type, $expect );
}
}
return $ok;
}
sub _cmp_diag {
my( $self, $got, $type, $expect ) = @_;
$got = defined $got ? "'$got'" : 'undef';
$expect = defined $expect ? "'$expect'" : 'undef';
local $Level = $Level + 1;
return $self->diag(<<"DIAGNOSTIC");
$got
$type
$expect
DIAGNOSTIC
}
sub _caller_context {
my $self = shift;
my( $pack, $file, $line ) = $self->caller(1);
my $code = '';
$code .= "#line $line $file\n" if defined $file and defined $line;
return $code;
}
#line 1199
sub BAIL_OUT {
my( $self, $reason ) = @_;
$self->{Bailed_Out} = 1;
$self->_print("Bail out! $reason");
exit 255;
}
#line 1212
{
no warnings 'once';
*BAILOUT = \&BAIL_OUT;
}
#line 1226
sub skip {
my( $self, $why ) = @_;
$why ||= '';
$self->_unoverload_str( \$why );
lock( $self->{Curr_Test} );
$self->{Curr_Test}++;
$self->{Test_Results}[ $self->{Curr_Test} - 1 ] = &share(
{
'ok' => 1,
actual_ok => 1,
name => '',
type => 'skip',
reason => $why,
}
);
my $out = "ok";
$out .= " $self->{Curr_Test}" if $self->use_numbers;
$out .= " # skip";
$out .= " $why" if length $why;
$out .= "\n";
$self->_print($out);
return 1;
}
#line 1267
sub todo_skip {
my( $self, $why ) = @_;
$why ||= '';
lock( $self->{Curr_Test} );
$self->{Curr_Test}++;
$self->{Test_Results}[ $self->{Curr_Test} - 1 ] = &share(
{
'ok' => 1,
actual_ok => 0,
name => '',
type => 'todo_skip',
reason => $why,
}
);
my $out = "not ok";
$out .= " $self->{Curr_Test}" if $self->use_numbers;
$out .= " # TODO & SKIP $why\n";
$self->_print($out);
return 1;
}
#line 1347
sub maybe_regex {
my( $self, $regex ) = @_;
my $usable_regex = undef;
return $usable_regex unless defined $regex;
my( $re, $opts );
# Check for qr/foo/
if( _is_qr($regex) ) {
$usable_regex = $regex;
}
# Check for '/foo/' or 'm,foo,'
elsif(( $re, $opts ) = $regex =~ m{^ /(.*)/ (\w*) $ }sx or
( undef, $re, $opts ) = $regex =~ m,^ m([^\w\s]) (.+) \1 (\w*) $,sx
)
{
$usable_regex = length $opts ? "(?$opts)$re" : $re;
}
return $usable_regex;
}
sub _is_qr {
my $regex = shift;
# is_regexp() checks for regexes in a robust manner, say if they're
# blessed.
return re::is_regexp($regex) if defined &re::is_regexp;
return ref $regex eq 'Regexp';
}
sub _regex_ok {
my( $self, $this, $regex, $cmp, $name ) = @_;
my $ok = 0;
my $usable_regex = $self->maybe_regex($regex);
unless( defined $usable_regex ) {
local $Level = $Level + 1;
$ok = $self->ok( 0, $name );
$self->diag(" '$regex' doesn't look much like a regex to me.");
return $ok;
}
{
## no critic (BuiltinFunctions::ProhibitStringyEval)
my $test;
my $context = $self->_caller_context;
local( $@, $!, $SIG{__DIE__} ); # isolate eval
$test = eval $context . q{$test = $this =~ /$usable_regex/ ? 1 : 0};
$test = !$test if $cmp eq '!~';
local $Level = $Level + 1;
$ok = $self->ok( $test, $name );
}
unless($ok) {
$this = defined $this ? "'$this'" : 'undef';
my $match = $cmp eq '=~' ? "doesn't match" : "matches";
local $Level = $Level + 1;
$self->diag( sprintf <<'DIAGNOSTIC', $this, $match, $regex );
%s
%13s '%s'
DIAGNOSTIC
}
return $ok;
}
# I'm not ready to publish this. It doesn't deal with array return
# values from the code or context.
#line 1443
sub _try {
my( $self, $code, %opts ) = @_;
my $error;
my $return;
{
local $!; # eval can mess up $!
local $@; # don't set $@ in the test
local $SIG{__DIE__}; # don't trip an outside DIE handler.
$return = eval { $code->() };
$error = $@;
}
die $error if $error and $opts{die_on_fail};
return wantarray ? ( $return, $error ) : $return;
}
#line 1472
sub is_fh {
my $self = shift;
my $maybe_fh = shift;
return 0 unless defined $maybe_fh;
return 1 if ref $maybe_fh eq 'GLOB'; # its a glob ref
return 1 if ref \$maybe_fh eq 'GLOB'; # its a glob
return eval { $maybe_fh->isa("IO::Handle") } ||
eval { tied($maybe_fh)->can('TIEHANDLE') };
}
#line 1515
sub level {
my( $self, $level ) = @_;
if( defined $level ) {
$Level = $level;
}
return $Level;
}
#line 1547
sub use_numbers {
my( $self, $use_nums ) = @_;
if( defined $use_nums ) {
$self->{Use_Nums} = $use_nums;
}
return $self->{Use_Nums};
}
#line 1580
foreach my $attribute (qw(No_Header No_Ending No_Diag)) {
my $method = lc $attribute;
my $code = sub {
my( $self, $no ) = @_;
if( defined $no ) {
$self->{$attribute} = $no;
}
return $self->{$attribute};
};
no strict 'refs'; ## no critic
*{ __PACKAGE__ . '::' . $method } = $code;
}
#line 1633
sub diag {
my $self = shift;
$self->_print_comment( $self->_diag_fh, @_ );
}
#line 1648
sub note {
my $self = shift;
$self->_print_comment( $self->output, @_ );
}
sub _diag_fh {
my $self = shift;
local $Level = $Level + 1;
return $self->in_todo ? $self->todo_output : $self->failure_output;
}
sub _print_comment {
my( $self, $fh, @msgs ) = @_;
return if $self->no_diag;
return unless @msgs;
# Prevent printing headers when compiling (i.e. -c)
return if $^C;
# Smash args together like print does.
# Convert undef to 'undef' so its readable.
my $msg = join '', map { defined($_) ? $_ : 'undef' } @msgs;
# Escape the beginning, _print will take care of the rest.
$msg =~ s/^/# /;
local $Level = $Level + 1;
$self->_print_to_fh( $fh, $msg );
return 0;
}
#line 1698
sub explain {
my $self = shift;
return map {
ref $_
? do {
$self->_try(sub { require Data::Dumper }, die_on_fail => 1);
my $dumper = Data::Dumper->new( [$_] );
$dumper->Indent(1)->Terse(1);
$dumper->Sortkeys(1) if $dumper->can("Sortkeys");
$dumper->Dump;
}
: $_
} @_;
}
#line 1727
sub _print {
my $self = shift;
return $self->_print_to_fh( $self->output, @_ );
}
sub _print_to_fh {
my( $self, $fh, @msgs ) = @_;
# Prevent printing headers when only compiling. Mostly for when
# tests are deparsed with B::Deparse
return if $^C;
my $msg = join '', @msgs;
my $indent = $self->_indent;
local( $\, $", $, ) = ( undef, ' ', '' );
# Escape each line after the first with a # so we don't
# confuse Test::Harness.
$msg =~ s{\n(?!\z)}{\n$indent# }sg;
# Stick a newline on the end if it needs it.
$msg .= "\n" unless $msg =~ /\n\z/;
return print $fh $indent, $msg;
}
#line 1787
sub output {
my( $self, $fh ) = @_;
if( defined $fh ) {
$self->{Out_FH} = $self->_new_fh($fh);
}
return $self->{Out_FH};
}
sub failure_output {
my( $self, $fh ) = @_;
if( defined $fh ) {
$self->{Fail_FH} = $self->_new_fh($fh);
}
return $self->{Fail_FH};
}
sub todo_output {
my( $self, $fh ) = @_;
if( defined $fh ) {
$self->{Todo_FH} = $self->_new_fh($fh);
}
return $self->{Todo_FH};
}
sub _new_fh {
my $self = shift;
my($file_or_fh) = shift;
my $fh;
if( $self->is_fh($file_or_fh) ) {
$fh = $file_or_fh;
}
elsif( ref $file_or_fh eq 'SCALAR' ) {
# Scalar refs as filehandles was added in 5.8.
if( $] >= 5.008 ) {
open $fh, ">>", $file_or_fh
or $self->croak("Can't open scalar ref $file_or_fh: $!");
}
# Emulate scalar ref filehandles with a tie.
else {
$fh = Test::Builder::IO::Scalar->new($file_or_fh)
or $self->croak("Can't tie scalar ref $file_or_fh");
}
}
else {
open $fh, ">", $file_or_fh
or $self->croak("Can't open test output log $file_or_fh: $!");
_autoflush($fh);
}
return $fh;
}
sub _autoflush {
my($fh) = shift;
my $old_fh = select $fh;
$| = 1;
select $old_fh;
return;
}
my( $Testout, $Testerr );
sub _dup_stdhandles {
my $self = shift;
$self->_open_testhandles;
# Set everything to unbuffered else plain prints to STDOUT will
# come out in the wrong order from our own prints.
_autoflush($Testout);
_autoflush( \*STDOUT );
_autoflush($Testerr);
_autoflush( \*STDERR );
$self->reset_outputs;
return;
}
sub _open_testhandles {
my $self = shift;
return if $self->{Opened_Testhandles};
# We dup STDOUT and STDERR so people can change them in their
# test suites while still getting normal test output.
open( $Testout, ">&STDOUT" ) or die "Can't dup STDOUT: $!";
open( $Testerr, ">&STDERR" ) or die "Can't dup STDERR: $!";
# $self->_copy_io_layers( \*STDOUT, $Testout );
# $self->_copy_io_layers( \*STDERR, $Testerr );
$self->{Opened_Testhandles} = 1;
return;
}
sub _copy_io_layers {
my( $self, $src, $dst ) = @_;
$self->_try(
sub {
require PerlIO;
my @src_layers = PerlIO::get_layers($src);
binmode $dst, join " ", map ":$_", @src_layers if @src_layers;
}
);
return;
}
#line 1912
sub reset_outputs {
my $self = shift;
$self->output ($Testout);
$self->failure_output($Testerr);
$self->todo_output ($Testout);
return;
}
#line 1938
sub _message_at_caller {
my $self = shift;
local $Level = $Level + 1;
my( $pack, $file, $line ) = $self->caller;
return join( "", @_ ) . " at $file line $line.\n";
}
sub carp {
my $self = shift;
return warn $self->_message_at_caller(@_);
}
sub croak {
my $self = shift;
return die $self->_message_at_caller(@_);
}
#line 1978
sub current_test {
my( $self, $num ) = @_;
lock( $self->{Curr_Test} );
if( defined $num ) {
$self->{Curr_Test} = $num;
# If the test counter is being pushed forward fill in the details.
my $test_results = $self->{Test_Results};
if( $num > @$test_results ) {
my $start = @$test_results ? @$test_results : 0;
for( $start .. $num - 1 ) {
$test_results->[$_] = &share(
{
'ok' => 1,
actual_ok => undef,
reason => 'incrementing test number',
type => 'unknown',
name => undef
}
);
}
}
# If backward, wipe history. Its their funeral.
elsif( $num < @$test_results ) {
$#{$test_results} = $num - 1;
}
}
return $self->{Curr_Test};
}
#line 2026
sub is_passing {
my $self = shift;
if( @_ ) {
$self->{Is_Passing} = shift;
}
return $self->{Is_Passing};
}
#line 2048
sub summary {
my($self) = shift;
return map { $_->{'ok'} } @{ $self->{Test_Results} };
}
#line 2103
sub details {
my $self = shift;
return @{ $self->{Test_Results} };
}
#line 2132
sub todo {
my( $self, $pack ) = @_;
return $self->{Todo} if defined $self->{Todo};
local $Level = $Level + 1;
my $todo = $self->find_TODO($pack);
return $todo if defined $todo;
return '';
}
#line 2159
sub find_TODO {
my( $self, $pack, $set, $new_value ) = @_;
$pack = $pack || $self->caller(1) || $self->exported_to;
return unless $pack;
no strict 'refs'; ## no critic
my $old_value = ${ $pack . '::TODO' };
$set and ${ $pack . '::TODO' } = $new_value;
return $old_value;
}
#line 2179
sub in_todo {
my $self = shift;
local $Level = $Level + 1;
return( defined $self->{Todo} || $self->find_TODO ) ? 1 : 0;
}
#line 2229
sub todo_start {
my $self = shift;
my $message = @_ ? shift : '';
$self->{Start_Todo}++;
if( $self->in_todo ) {
push @{ $self->{Todo_Stack} } => $self->todo;
}
$self->{Todo} = $message;
return;
}
#line 2251
sub todo_end {
my $self = shift;
if( !$self->{Start_Todo} ) {
$self->croak('todo_end() called without todo_start()');
}
$self->{Start_Todo}--;
if( $self->{Start_Todo} && @{ $self->{Todo_Stack} } ) {
$self->{Todo} = pop @{ $self->{Todo_Stack} };
}
else {
delete $self->{Todo};
}
return;
}
#line 2284
sub caller { ## no critic (Subroutines::ProhibitBuiltinHomonyms)
my( $self, $height ) = @_;
$height ||= 0;
my $level = $self->level + $height + 1;
my @caller;
do {
@caller = CORE::caller( $level );
$level--;
} until @caller;
return wantarray ? @caller : $caller[0];
}
#line 2301
#line 2315
#'#
sub _sanity_check {
my $self = shift;
$self->_whoa( $self->{Curr_Test} < 0, 'Says here you ran a negative number of tests!' );
$self->_whoa( $self->{Curr_Test} != @{ $self->{Test_Results} },
'Somehow you got a different number of results than tests ran!' );
return;
}
#line 2336
sub _whoa {
my( $self, $check, $desc ) = @_;
if($check) {
local $Level = $Level + 1;
$self->croak(<<"WHOA");
WHOA! $desc
This should never happen! Please contact the author immediately!
WHOA
}
return;
}
#line 2360
sub _my_exit {
$? = $_[0]; ## no critic (Variables::RequireLocalizedPunctuationVars)
return 1;
}
#line 2372
sub _ending {
my $self = shift;
return if $self->no_ending;
return if $self->{Ending}++;
my $real_exit_code = $?;
# Don't bother with an ending if this is a forked copy. Only the parent
# should do the ending.
if( $self->{Original_Pid} != $$ ) {
return;
}
# Ran tests but never declared a plan or hit done_testing
if( !$self->{Have_Plan} and $self->{Curr_Test} ) {
$self->is_passing(0);
$self->diag("Tests were run but no plan was declared and done_testing() was not seen.");
}
# Exit if plan() was never called. This is so "require Test::Simple"
# doesn't puke.
if( !$self->{Have_Plan} ) {
return;
}
# Don't do an ending if we bailed out.
if( $self->{Bailed_Out} ) {
$self->is_passing(0);
return;
}
# Figure out if we passed or failed and print helpful messages.
my $test_results = $self->{Test_Results};
if(@$test_results) {
# The plan? We have no plan.
if( $self->{No_Plan} ) {
$self->_output_plan($self->{Curr_Test}) unless $self->no_header;
$self->{Expected_Tests} = $self->{Curr_Test};
}
# Auto-extended arrays and elements which aren't explicitly
# filled in with a shared reference will puke under 5.8.0
# ithreads. So we have to fill them in by hand. :(
my $empty_result = &share( {} );
for my $idx ( 0 .. $self->{Expected_Tests} - 1 ) {
$test_results->[$idx] = $empty_result
unless defined $test_results->[$idx];
}
my $num_failed = grep !$_->{'ok'}, @{$test_results}[ 0 .. $self->{Curr_Test} - 1 ];
my $num_extra = $self->{Curr_Test} - $self->{Expected_Tests};
if( $num_extra != 0 ) {
my $s = $self->{Expected_Tests} == 1 ? '' : 's';
$self->diag(<<"FAIL");
Looks like you planned $self->{Expected_Tests} test$s but ran $self->{Curr_Test}.
FAIL
$self->is_passing(0);
}
if($num_failed) {
my $num_tests = $self->{Curr_Test};
my $s = $num_failed == 1 ? '' : 's';
my $qualifier = $num_extra == 0 ? '' : ' run';
$self->diag(<<"FAIL");
Looks like you failed $num_failed test$s of $num_tests$qualifier.
FAIL
$self->is_passing(0);
}
if($real_exit_code) {
$self->diag(<<"FAIL");
Looks like your test exited with $real_exit_code just after $self->{Curr_Test}.
FAIL
$self->is_passing(0);
_my_exit($real_exit_code) && return;
}
my $exit_code;
if($num_failed) {
$exit_code = $num_failed <= 254 ? $num_failed : 254;
}
elsif( $num_extra != 0 ) {
$exit_code = 255;
}
else {
$exit_code = 0;
}
_my_exit($exit_code) && return;
}
elsif( $self->{Skip_All} ) {
_my_exit(0) && return;
}
elsif($real_exit_code) {
$self->diag(<<"FAIL");
Looks like your test exited with $real_exit_code before it could output anything.
FAIL
$self->is_passing(0);
_my_exit($real_exit_code) && return;
}
else {
$self->diag("No tests run!\n");
$self->is_passing(0);
_my_exit(255) && return;
}
$self->is_passing(0);
$self->_whoa( 1, "We fell off the end of _ending()" );
}
END {
$Test->_ending if defined $Test;
}
#line 2560
1;
URI-Fetch-0.09/inc/Test/More.pm 000644 000767 000767 00000041404 11520632637 017004 0 ustar 00btrott btrott 000000 000000 #line 1
package Test::More;
use 5.006;
use strict;
use warnings;
#---- perlcritic exemptions. ----#
# We use a lot of subroutine prototypes
## no critic (Subroutines::ProhibitSubroutinePrototypes)
# Can't use Carp because it might cause use_ok() to accidentally succeed
# even though the module being used forgot to use Carp. Yes, this
# actually happened.
sub _carp {
my( $file, $line ) = ( caller(1) )[ 1, 2 ];
return warn @_, " at $file line $line\n";
}
our $VERSION = '0.96';
$VERSION = eval $VERSION; ## no critic (BuiltinFunctions::ProhibitStringyEval)
use Test::Builder::Module;
our @ISA = qw(Test::Builder::Module);
our @EXPORT = qw(ok use_ok require_ok
is isnt like unlike is_deeply
cmp_ok
skip todo todo_skip
pass fail
eq_array eq_hash eq_set
$TODO
plan
done_testing
can_ok isa_ok new_ok
diag note explain
subtest
BAIL_OUT
);
#line 164
sub plan {
my $tb = Test::More->builder;
return $tb->plan(@_);
}
# This implements "use Test::More 'no_diag'" but the behavior is
# deprecated.
sub import_extra {
my $class = shift;
my $list = shift;
my @other = ();
my $idx = 0;
while( $idx <= $#{$list} ) {
my $item = $list->[$idx];
if( defined $item and $item eq 'no_diag' ) {
$class->builder->no_diag(1);
}
else {
push @other, $item;
}
$idx++;
}
@$list = @other;
return;
}
#line 217
sub done_testing {
my $tb = Test::More->builder;
$tb->done_testing(@_);
}
#line 289
sub ok ($;$) {
my( $test, $name ) = @_;
my $tb = Test::More->builder;
return $tb->ok( $test, $name );
}
#line 367
sub is ($$;$) {
my $tb = Test::More->builder;
return $tb->is_eq(@_);
}
sub isnt ($$;$) {
my $tb = Test::More->builder;
return $tb->isnt_eq(@_);
}
*isn't = \&isnt;
#line 411
sub like ($$;$) {
my $tb = Test::More->builder;
return $tb->like(@_);
}
#line 426
sub unlike ($$;$) {
my $tb = Test::More->builder;
return $tb->unlike(@_);
}
#line 471
sub cmp_ok($$$;$) {
my $tb = Test::More->builder;
return $tb->cmp_ok(@_);
}
#line 506
sub can_ok ($@) {
my( $proto, @methods ) = @_;
my $class = ref $proto || $proto;
my $tb = Test::More->builder;
unless($class) {
my $ok = $tb->ok( 0, "->can(...)" );
$tb->diag(' can_ok() called with empty class or reference');
return $ok;
}
unless(@methods) {
my $ok = $tb->ok( 0, "$class->can(...)" );
$tb->diag(' can_ok() called with no methods');
return $ok;
}
my @nok = ();
foreach my $method (@methods) {
$tb->_try( sub { $proto->can($method) } ) or push @nok, $method;
}
my $name = (@methods == 1) ? "$class->can('$methods[0]')" :
"$class->can(...)" ;
my $ok = $tb->ok( !@nok, $name );
$tb->diag( map " $class->can('$_') failed\n", @nok );
return $ok;
}
#line 572
sub isa_ok ($$;$) {
my( $object, $class, $obj_name ) = @_;
my $tb = Test::More->builder;
my $diag;
if( !defined $object ) {
$obj_name = 'The thing' unless defined $obj_name;
$diag = "$obj_name isn't defined";
}
else {
my $whatami = ref $object ? 'object' : 'class';
# We can't use UNIVERSAL::isa because we want to honor isa() overrides
my( $rslt, $error ) = $tb->_try( sub { $object->isa($class) } );
if($error) {
if( $error =~ /^Can't call method "isa" on unblessed reference/ ) {
# Its an unblessed reference
$obj_name = 'The reference' unless defined $obj_name;
if( !UNIVERSAL::isa( $object, $class ) ) {
my $ref = ref $object;
$diag = "$obj_name isn't a '$class' it's a '$ref'";
}
}
elsif( $error =~ /Can't call method "isa" without a package/ ) {
# It's something that can't even be a class
$obj_name = 'The thing' unless defined $obj_name;
$diag = "$obj_name isn't a class or reference";
}
else {
die <isa on your $whatami and got some weird error.
Here's the error.
$error
WHOA
}
}
else {
$obj_name = "The $whatami" unless defined $obj_name;
if( !$rslt ) {
my $ref = ref $object;
$diag = "$obj_name isn't a '$class' it's a '$ref'";
}
}
}
my $name = "$obj_name isa $class";
my $ok;
if($diag) {
$ok = $tb->ok( 0, $name );
$tb->diag(" $diag\n");
}
else {
$ok = $tb->ok( 1, $name );
}
return $ok;
}
#line 651
sub new_ok {
my $tb = Test::More->builder;
$tb->croak("new_ok() must be given at least a class") unless @_;
my( $class, $args, $object_name ) = @_;
$args ||= [];
$object_name = "The object" unless defined $object_name;
my $obj;
my( $success, $error ) = $tb->_try( sub { $obj = $class->new(@$args); 1 } );
if($success) {
local $Test::Builder::Level = $Test::Builder::Level + 1;
isa_ok $obj, $class, $object_name;
}
else {
$tb->ok( 0, "new() died" );
$tb->diag(" Error was: $error");
}
return $obj;
}
#line 736
sub subtest($&) {
my ($name, $subtests) = @_;
my $tb = Test::More->builder;
return $tb->subtest(@_);
}
#line 760
sub pass (;$) {
my $tb = Test::More->builder;
return $tb->ok( 1, @_ );
}
sub fail (;$) {
my $tb = Test::More->builder;
return $tb->ok( 0, @_ );
}
#line 823
sub use_ok ($;@) {
my( $module, @imports ) = @_;
@imports = () unless @imports;
my $tb = Test::More->builder;
my( $pack, $filename, $line ) = caller;
my $code;
if( @imports == 1 and $imports[0] =~ /^\d+(?:\.\d+)?$/ ) {
# probably a version check. Perl needs to see the bare number
# for it to work with non-Exporter based modules.
$code = <