From: Florian Ragwitz Date: Tue, 19 Jan 2010 16:07:45 +0000 (+0000) Subject: Merge branch 'action_args' X-Git-Tag: 5.80019~13 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=commitdiff_plain;h=fe80b10d804f3c2e3508e144c0e4371e2b43f483;hp=4d4e5de8d391ea3351093be5a1bed02b3d29c32c Merge branch 'action_args' action_args: And another minor tweak. Some more doc tweaking. tweaked docs based on IRC suggestions added documentation for the configuration option "action_args". Allow passing extra args to action constructors using action_args config. Add tests for passing extra arguments to action constructors. Create branch action_args --- diff --git a/Changes b/Changes index 98c5890..44bbd97 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,133 @@ # This file documents the revision history for Perl extension Catalyst. +5.80018 2010-01-12 22:24:20 + + Bug fixed: + - Call ->canonical on URI derived from $ENV{REQUEST_URI} to get + paths correctly decoded. This bug was previously hidden by a bug + in HTTP::Request::AsCGI + + Documentation: + - Clarify that uri_for_action works on private paths, with example. + + Deprecations: + - Saying use Catalyst::Test; (without an application name or () to stop + the importer running is now deprecated and will issue a warning. + You should be saying use Catalyst::Test (); + +5.80017 2010-01-10 02:27:29 + + Documentation: + - Fix docs for ->forward method when passed a class name - this should + be a component name (e.g. View::HTML, not a full class name, like + MyApp::View::HTML). + + Bug fixes: + - --daemon and -d options to Catalyst::Script::FastCGI are fixed. + - Fix the debug dump for applications which use Catalyst::Plugin::Session + (RT#52898) + - Fix regression in the case where mod_rewrite is being used to rewrite + requests into a path below your application base introduced with the + %2F related fixes in 5.80014_02. + - Do not crash on SIGHUP if Catalyst::Engine::HTTP->run is not passed the + argv key in the options hash. + - Correctly pass the arguments to Catalyst::Script::Server through to + Catalyst::Engine::HTTP->run so that the server can restart itself + with the correct options on SIGHUP. + - Require new MooseX::MethodAttributes to be compatible with Moose + versions >= 0.93_01 + - Require new MooseX::Role::WithOverloading to be compatible with Moose + versions >= 0.93_01 + + Cleanups: + - Stop suppressing warnings from Class::C3::Adopt::NEXT now that most plugins + have been updated to not use NEXT. If you get warnings then please upgrade + your components or log a bug with the component author if an upgrade is + not available. The Class::C3::Adopt::NEXT documentation contains information + about how to suppress the warnings in your application if you need to. + +5.80016 2009-12-11 23:23:33 + + Bug fixes: + + - Fix slurping a file to work correctly with binary on Win32 in the + encoding test controller. + + Bug fixes in the new scripts (for applications which have been upgraded): + + - Allow --restartdirectory as an option for the Server script, for + backwards compatibility. (Dave Rolsky) + - The --host option for the server script defaulted to localhost, rather + than listening on all interfaces, which was the previous default. (Dave + Rolsky) + - Restore -p option for pid file in the FastCGI server script. + - Fix the script environment variables MYAPP_PORT and MYAPP_RELOAD RT#52604 + - Fix aliasing applications under non-root paths with mod_rewrite in + some apache versions where %ENV{SCRIPT_NAME} is set to the real name of + the script, by using $ENV{REDIRECT_URL} which contains the non-rewritten + URI. + - Fix usage display when myapp_create.pl is run with no arguments. RT#52630 + + New features: + + - The __MOP__ hash element is suppressed from being dumped fully + (and instead stringified) when dumping the error screen to be + less packed with information of no use. + + Documentation: + + - Fix Pod nits (RT#52370) + +5.80015 2009-12-02 15:13:54 + Bug fixes: + - Fix bug in Catalyst::Engine which would cause a request parsing to end + prematurely in the hypothetical case where calling $engine->read returned + the single character '0'. + - Fix failing tests when combined with new HTTP::Request::AsCGI + + Documentation: + - Improved documentation on read and read_chunk methods in Catalyst::Engine. + - Fix reversal of SCRIPT_NAME and PATH_INFO in previously correct nginx + FastCGI documentation introduced in _02. + +5.80014_02 2009-12-01 00:55:23 + Bug fixes: + - Fix reporting the wrong Content-Length if the response body is an + upgraded string. Strings mean the same thing whether or not they are + upgraded, may get upgraded even after they are encoded, and will + produce the same output either way, but bytes::length returns too big + values for upgraded strings containing characters >127 + - Fix t/live_fork.t with bleadperl (RT#52100) + - Set $ENV{PATH_INFO} from $ENV{REQUEST_URI} combined with + $ENV{SCRIPT_NAME} if possible. This is many web servers always fully + decode PATH_INFO including URI reserved characters. This allows us to + tell foo%2cbar from foo%252cbar, and fixes issues with %2F in paths + being incorrectly decoded, resulting in too many path parts (rather + than 1 path part containing a /, on some web servers (at least nginx). + (RT#50082) + - Require new HTTP::Request::AsCGI so that it fully decodes $ENV{PATH_INFO} + in non CGI contexts. (RT#50082) + + Refactoring / cleanups: + - NoTabs and Pod tests moved to t/author so that they're not run + (and then skipped) normally. + + Documentation: + - Fix Pod nits in Catalyst::Response (RT#51818) + +5.80014_01 2009-11-22 20:01:23 + + Bug fixes: + - Filehandle now forced to binmode in CGI and FastCGI engines. This appears + to correct some UTF-8 issues, but may break people's code which relies + on the old behaviour. + + Refactoring / cleanups: + - Plugins which inherit from Catalyst::Controller or Catalyst::Component + are deprecated and now issue warnings. + +5.80014 2009-11-21 02:51:14 + Bug fixes: - Require MooseX::MethodAttributes 0.17. This in turn requires new MooseX::Types to stop warnings in Moose 0.91, and correctly supports @@ -9,8 +137,6 @@ - Improved the suggested fix warning when component resolution uses regex fallback for fully qualified component names. - Catalyst::Test::local_request sets ->request on the response. - - Require HTTP::Request 5.814 and HTTP::Response 5.813 from LWP 5.814 - to avoid test fails. - Log flush moved to the end of setup so that roles and plugins which hook setup_finalize can log things and have them appear in application startup, rather than with the first hit. @@ -18,6 +144,10 @@ - Stop warnings when actions are forwarded to during dispatch. - Remove warnings for using Catalyst::Dispatcher->dispatch_types as this is a valid method to publicly call on the dispatcher. + - Args ($c->request->args) and CaptureArgs ($c->request->captrues) + passed to $c->uri_for with an action object ($c->action) will now + correctly round-trip when args or captures contain / as it is now + correctly uri encoded to %2F. Documentation: - Document no-args call to $c->uri_for. @@ -29,6 +159,9 @@ in the correct order. - Update $c->forward and $c->state documentation to address scalar context. + - Pod fix in Catalyst::Request (RT#51490) + - Pod fixes to refer to ::Controller:: rather than ::C:: as the latter + is deprecated (RT#51489) New features: - Added disable_component_resolution_regex_fallback config option to @@ -37,6 +170,11 @@ proper PATH_INFO and SCRIPT_NAME processing for non-root applications - Enable Catalyst::Utils::home() to find home within Dist::Zilla built distributions + - Added the Catalyst::Exception::Interface role defining the interface + exception classes need to implement. + - Added Catalyst::Exception::Basic as a basic implementation of + Catalyst::Exception::Interface and made the existing exception classes + use it. Refactoring / cleanups: - Remove documentation for the case_sensitive setting @@ -298,7 +436,7 @@ B::Hooks::OP::Check::StashChange - Fix the unattached chain debug table for endpoints with no parents at all. - - Turn of test aggregation by default. Only aggregate if the + - Turn off test aggregation by default. Only aggregate if the AGGREGATE_TESTS environment variable is set and a recent Test::Aggregate is available. - Bump to MooseX::MethodAttributes 0.09, to gain the diff --git a/Makefile.PL b/Makefile.PL index be08062..b5eac5f 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -1,6 +1,6 @@ use strict; use warnings; -use inc::Module::Install 0.87; +use inc::Module::Install 0.91; { # Ensure that these get used - yes, M::I loads them for us, but if you're # in author mode and don't have them installed, then the error is tres # cryptic. @@ -17,12 +17,13 @@ all_from 'lib/Catalyst/Runtime.pm'; requires 'List::MoreUtils'; requires 'namespace::autoclean' => '0.09'; -requires 'namespace::clean'; +requires 'namespace::clean' => '0.13'; requires 'B::Hooks::EndOfScope' => '0.08'; requires 'MooseX::Emulate::Class::Accessor::Fast' => '0.00903'; -requires 'Class::MOP' => '0.83'; -requires 'Moose' => '0.90'; -requires 'MooseX::MethodAttributes::Inheritable' => '0.17'; +requires 'Class::MOP' => '0.95'; +requires 'Moose' => '0.93'; +requires 'MooseX::MethodAttributes::Inheritable' => '0.19'; +requires 'MooseX::Role::WithOverloading' => '0.05'; requires 'Carp'; requires 'Class::C3::Adopt::NEXT' => '0.07'; requires 'CGI::Simple::Cookie'; @@ -32,7 +33,7 @@ requires 'HTTP::Body' => '1.04'; # makes uploadtmp work requires 'HTTP::Headers' => '1.64'; requires 'HTTP::Request' => '5.814'; requires 'HTTP::Response' => '5.813'; -requires 'HTTP::Request::AsCGI' => '0.8'; +requires 'HTTP::Request::AsCGI' => '1.0'; requires 'LWP::UserAgent'; requires 'Module::Pluggable' => '3.9'; requires 'Path::Class' => '0.09'; @@ -46,12 +47,14 @@ requires 'URI' => '1.35'; requires 'Task::Weaken'; requires 'Text::Balanced'; # core in 5.8.x but mentioned for completeness requires 'MRO::Compat'; +requires 'MooseX::Getopt' => '0.25'; +requires 'MooseX::Types'; +requires 'MooseX::Types::Common::Numeric'; requires 'String::RewritePrefix' => '0.004'; # Catalyst::Utils::resolve_namespace -recommends 'B::Hooks::OP::Check::StashChange'; - test_requires 'Class::Data::Inheritable'; test_requires 'Test::Exception'; +test_requires 'Test::More' => '0.88'; # aggregate tests if AGGREGATE_TESTS is set and a recent Test::Aggregate and a Test::Simple it works with is available if ($ENV{AGGREGATE_TESTS} && can_use('Test::Simple', '0.88') && can_use('Test::Aggregate', '0.35_05')) { @@ -63,7 +66,8 @@ else { grep { $_ ne 't/aggregate.t' } map { glob } qw[t/*.t t/aggregate/*.t]; } -author_requires 'CatalystX::LeakChecker', '0.03'; # Skipped if this isn't installed +author_requires 'CatalystX::LeakChecker', '0.05'; # Skipped if this isn't installed +author_requires 'File::Copy::Recursive'; # For http server test author_tests 't/author'; author_requires(map {; $_ => 0 } qw( @@ -110,6 +114,7 @@ EOF # NOTE - This is the version number of the _incompatible_ code, # not the version number of the fixed version. my %conflicts = ( + 'Catalyst::Plugin::SubRequest' => '0.14', 'Catalyst::Model::Akismet' => '0.02', 'Catalyst::Component::ACCEPT_CONTEXT' => '0.06', 'Catalyst::Plugin::ENV' => '9999', # This plugin is just stupid, full stop @@ -153,7 +158,7 @@ sub darwin_check_no_resource_forks { my $attr = $osx_ver =~ /^10.(5|6)/ ? 'COPYFILE_DISABLE' : 'COPY_EXTENDED_ATTRIBUTES_DISABLE'; makemaker_args(dist => { PREOP => qq{\@if [ "\$\$$attr" != "true" ]; then}. - qq{ echo "You must set the ENV variable $attr to true,"; }. + qq{ echo "You must set the ENV variable $attr to 'true',"; }. ' echo "to avoid getting resource forks in your dist."; exit 255; fi' }); } } diff --git a/TODO b/TODO index 4a2b319..8fd77ad 100644 --- a/TODO +++ b/TODO @@ -5,12 +5,6 @@ Test app: http://github.com/bobtfish/catalyst-app-bug-go_chain/tree/master - - Bricas' Exception blog post - - http://bricas.vox.com/library/post/catalyst-exceptionclass.html - - Broken by recent exception refactoring - # Compatibility warnings to add: - $self->config should warn as config should only ever be called as a diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index d681c90..f1c63fd 100644 --- a/lib/Catalyst.pm +++ b/lib/Catalyst.pm @@ -4,7 +4,6 @@ use Moose; use Moose::Meta::Class (); extends 'Catalyst::Component'; use Moose::Util qw/find_meta/; -use bytes; use B::Hooks::EndOfScope (); use Catalyst::Exception; use Catalyst::Exception::Detach; @@ -79,13 +78,7 @@ __PACKAGE__->stats_class('Catalyst::Stats'); # Remember to update this in Catalyst::Runtime as well! -our $VERSION = '5.80013'; - -{ - my $dev_version = $VERSION =~ /_\d{2}$/; - *_IS_DEVELOPMENT_VERSION = sub () { $dev_version }; -} - +our $VERSION = '5.80018'; $VERSION = eval $VERSION; sub import { @@ -98,11 +91,6 @@ sub import { my $caller = caller(); return if $caller eq 'main'; - # Kill Adopt::NEXT warnings if we're a non-RC version - unless (_IS_DEVELOPMENT_VERSION()) { - Class::C3::Adopt::NEXT->unimport(qr/^Catalyst::/); - } - my $meta = Moose::Meta::Class->initialize($caller); unless ( $caller->isa('Catalyst') ) { my @superclasses = ($meta->superclasses, $class, 'Catalyst::Controller'); @@ -333,8 +321,8 @@ call to forward. my $foodata = $c->forward('/foo'); $c->forward('index'); - $c->forward(qw/MyApp::Model::DBIC::Foo do_stuff/); - $c->forward('MyApp::View::TT'); + $c->forward(qw/Model::DBIC::Foo do_stuff/); + $c->forward('View::TT'); Note that L<< forward|/"$c->forward( $action [, \@arguments ] )" >> implies an C<< eval { } >> around the call (actually @@ -349,16 +337,16 @@ Or make sure to always return true values from your actions and write your code like this: $c->forward('foo') || return; - + Another note is that C<< $c->forward >> always returns a scalar because it actually returns $c->state which operates in a scalar context. Thus, something like: return @array; - -in an action that is forwarded to is going to return a scalar, + +in an action that is forwarded to is going to return a scalar, i.e. how many items are in that array, which is probably not what you want. -If you need to return an array then return a reference to it, +If you need to return an array then return a reference to it, or stash it like so: $c->stash->{array} = \@array; @@ -418,9 +406,9 @@ sub visit { my $c = shift; $c->dispatcher->visit( $c, @_ ) } =head2 $c->go( $class, $method, [, \@captures, \@arguments ] ) -The relationship between C and +The relationship between C and L<< visit|/"$c->visit( $action [, \@captures, \@arguments ] )" >> is the same as -the relationship between +the relationship between L<< forward|/"$c->forward( $class, $method, [, \@arguments ] )" >> and L<< detach|/"$c->detach( $action [, \@arguments ] )" >>. Like C<< $c->visit >>, C<< $c->go >> will perform a full dispatch on the specified action or method, @@ -505,7 +493,7 @@ sub error { =head2 $c->state -Contains the return value of the last executed action. +Contains the return value of the last executed action. Note that << $c->state >> operates in a scalar context which means that all values it returns are scalar. @@ -803,7 +791,7 @@ component name will be returned. If Catalyst can't find a component by name, it will fallback to regex matching by default. To disable this behaviour set disable_component_resolution_regex_fallback to a true value. - + __PACKAGE__->config( disable_component_resolution_regex_fallback => 1 ); =cut @@ -1216,7 +1204,7 @@ When used as a string, provides a textual URI. If no arguments are provided, the URI for the current action is returned. To return the current action and also provide @args, use -C<< $c->uri_for( $c->action, @args ) >>. +C<< $c->uri_for( $c->action, @args ) >>. If the first argument is a string, it is taken as a public URI path relative to C<< $c->namespace >> (if it doesn't begin with a forward slash) or @@ -1259,9 +1247,10 @@ sub uri_for { } if ( blessed($path) ) { # action object - my $captures = ( scalar @args && ref $args[0] eq 'ARRAY' - ? shift(@args) - : [] ); + my $captures = [ map { s|/|%2F|; $_; } + ( scalar @args && ref $args[0] eq 'ARRAY' + ? @{ shift(@args) } + : ()) ]; my $action = $path; $path = $c->dispatcher->uri_for_action($action, $captures); if (not defined $path) { @@ -1279,6 +1268,7 @@ sub uri_for { carp "uri_for called with undef argument" if grep { ! defined $_ } @args; s/([^$URI::uric])/$URI::Escape::escapes{$1}/go for @args; + s|/|%2F| for @args; unshift(@args, $path); @@ -1339,6 +1329,20 @@ $c->uri_for >>. You can also pass in a Catalyst::Action object, in which case it is passed to C<< $c->uri_for >>. +Note that although the path looks like a URI that dispatches to the wanted action, it is not a URI, but an internal path to that action. + +For example, if the action looks like: + + package MyApp::Controller::Users; + + sub lst : Path('the-list') {} + +You can use: + + $c->uri_for_action('/users/lst') + +and it will create the URI /users/the-list. + =back =cut @@ -1791,7 +1795,7 @@ sub finalize_headers { } else { # everything should be bytes at this point, but just in case - $response->content_length( bytes::length( $response->body ) ); + $response->content_length( length( $response->body ) ); } } @@ -2585,7 +2589,8 @@ the plugin name does not begin with C. my $class = ref $proto || $proto; Class::MOP::load_class( $plugin ); - + $class->log->warn( "$plugin inherits from 'Catalyst::Component' - this is decated and will not work in 5.81" ) + if $plugin->isa( 'Catalyst::Component' ); $proto->_plugins->{$plugin} = 1; unless ($instant) { no strict 'refs'; @@ -2684,12 +2689,11 @@ There are a number of 'base' config variables which can be set: =item * -C - The default model picked if you say C<< $c->model >>. See Lmodel($name)>. +C - The default model picked if you say C<< $c->model >>. See L<< /$c->model($name) >>. =item * -C - The default view to be rendered or returned when C<< $c->view >>. See Lview($name)>. -is called. +C - The default view to be rendered or returned when C<< $c->view >> is called. See L<< /$c->view($name) >>. =item * @@ -2890,6 +2894,8 @@ David Naughton, C David E. Wheeler +dhoss: Devin Austin + dkubb: Dan Kubb Drew Taylor @@ -2952,6 +2958,8 @@ numa: Dan Sully obra: Jesse Vincent +Octavian Rasnita + omega: Andreas Marienborg Oleg Kostyuk diff --git a/lib/Catalyst/Component.pm b/lib/Catalyst/Component.pm index fe0ef6f..5e8a94c 100644 --- a/lib/Catalyst/Component.pm +++ b/lib/Catalyst/Component.pm @@ -84,7 +84,7 @@ sub BUILDARGS { } elsif (Class::MOP::is_class_loaded($_[0]) && $_[0]->isa('Catalyst') && ref($_[1]) eq 'HASH') { $args = $_[1]; - } elsif ($_[0] == $_[1]) { + } elsif ($_[0] eq $_[1]) { $args = $_[1]; } else { $args = +{ @_ }; @@ -157,7 +157,7 @@ __END__ =head1 METHODS -=head2 new($c, $arguments) +=head2 new($app, $arguments) Called by COMPONENT to instantiate the component; should return an object to be stored in the application's component hash. @@ -168,9 +168,10 @@ C<< my $component_instance = $component->COMPONENT($app, $arguments); >> If this method is present (as it is on all Catalyst::Component subclasses, it is called by Catalyst during setup_components with the application class -as $c and any config entry on the application for this component (for example, +as $app and any config entry on the application for this component (for example, in the case of MyApp::Controller::Foo this would be C<< MyApp->config('Controller::Foo' => \%conf >>). + The arguments are expected to be a hashref and are merged with the C<< __PACKAGE__->config >> hashref before calling C<< ->new >> to instantiate the component. diff --git a/lib/Catalyst/Dispatcher.pm b/lib/Catalyst/Dispatcher.pm index d82dfe1..135c578 100644 --- a/lib/Catalyst/Dispatcher.pm +++ b/lib/Catalyst/Dispatcher.pm @@ -153,7 +153,7 @@ sub _command2action { $action = $self->_invoke_as_path( $c, "$command", \@args ); } - # go to a component ( "MyApp::*::Foo" or $c->component("...") + # go to a component ( "View::Foo" or $c->component("...") # - a path or an object) unless ($action) { my $method = @extra_params ? $extra_params[0] : "process"; diff --git a/lib/Catalyst/Engine.pm b/lib/Catalyst/Engine.pm index 443975e..7ba4167 100644 --- a/lib/Catalyst/Engine.pm +++ b/lib/Catalyst/Engine.pm @@ -108,6 +108,24 @@ is in debug mode, or a `please come back later` message otherwise. =cut +sub _dump_error_page_element { + my ($self, $i, $element) = @_; + my ($name, $val) = @{ $element }; + + # This is fugly, but the metaclass is _HUGE_ and demands waaay too much + # scrolling. Suggestions for more pleasant ways to do this welcome. + local $val->{'__MOP__'} = "Stringified: " + . $val->{'__MOP__'} if ref $val eq 'HASH' && exists $val->{'__MOP__'}; + + my $text = encode_entities( dump( $val )); + sprintf <<"EOF", $name, $text; +

%s

+
+
%s
+
+EOF +} + sub finalize_error { my ( $self, $c ) = @_; @@ -138,14 +156,7 @@ sub finalize_error { my @infos; my $i = 0; for my $dump ( $c->dump_these ) { - my $name = $dump->[0]; - my $value = encode_entities( dump( $dump->[1] )); - push @infos, sprintf <<"EOF", $name, $value; -

%s

-
-
%s
-
-EOF + push @infos, $self->_dump_error_page_element($i, $dump); $i++; } $infos = join "\n", @infos; @@ -269,7 +280,8 @@ EOF - # Trick IE + # Trick IE. Old versions of IE would display their own error page instead + # of ours if we'd give it less than 512 bytes. $c->res->{body} .= ( ' ' x 512 ); # Return 500 @@ -327,7 +339,8 @@ sub prepare_body { if exists $appclass->config->{uploadtmp}; } - while ( my $buffer = $self->read($c) ) { + # Check for definedness as you could read '0' + while ( defined ( my $buffer = $self->read($c) ) ) { $c->prepare_body_chunk($buffer); } @@ -566,6 +579,10 @@ sub prepare_write { } =head2 $self->read($c, [$maxlength]) +Reads from the input stream by calling C<< $self->read_chunk >>. + +Maintains the read_length and read_position counters as data is read. + =cut sub read { @@ -583,6 +600,11 @@ sub read { my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining; my $rc = $self->read_chunk( $c, my $buffer, $readlen ); if ( defined $rc ) { + if (0 == $rc) { # Nothing more to read even though Content-Length + # said there should be. FIXME - Warn in the log here? + $self->finalize_read; + return; + } $self->read_position( $self->read_position + $rc ); return $buffer; } @@ -595,7 +617,8 @@ sub read { =head2 $self->read_chunk($c, $buffer, $length) Each engine implements read_chunk as its preferred way of reading a chunk -of data. +of data. Returns the number of bytes read. A return of 0 indicates that +there is no more data to be read. =cut diff --git a/lib/Catalyst/Engine/CGI.pm b/lib/Catalyst/Engine/CGI.pm index 8416e09..1443a2f 100644 --- a/lib/Catalyst/Engine/CGI.pm +++ b/lib/Catalyst/Engine/CGI.pm @@ -85,6 +85,7 @@ sub prepare_connection { if ( $ENV{SERVER_PORT} == 443 ) { $request->secure(1); } + binmode(STDOUT); # Ensure we are sending bytes. } =head2 $self->prepare_headers($c) @@ -107,6 +108,8 @@ sub prepare_headers { =cut +# Please don't touch this method without adding tests in +# t/aggregate/unit_core_engine_cgi-prepare_path.t sub prepare_path { my ( $self, $c ) = @_; local (*ENV) = $self->env || \%ENV; @@ -114,13 +117,16 @@ sub prepare_path { my $scheme = $c->request->secure ? 'https' : 'http'; my $host = $ENV{HTTP_HOST} || $ENV{SERVER_NAME}; my $port = $ENV{SERVER_PORT} || 80; + my $script_name = $ENV{SCRIPT_NAME}; + $script_name =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go if $script_name; + my $base_path; if ( exists $ENV{REDIRECT_URL} ) { $base_path = $ENV{REDIRECT_URL}; $base_path =~ s/$ENV{PATH_INFO}$//; } else { - $base_path = $ENV{SCRIPT_NAME} || '/'; + $base_path = $script_name || '/'; } # If we are running as a backend proxy, get the true hostname @@ -142,8 +148,34 @@ sub prepare_path { } } + # RFC 3875: "Unlike a URI path, the PATH_INFO is not URL-encoded, + # and cannot contain path-segment parameters." This means PATH_INFO + # is always decoded, and the script can't distinguish / vs %2F. + # See https://issues.apache.org/bugzilla/show_bug.cgi?id=35256 + # Here we try to resurrect the original encoded URI from REQUEST_URI. + my $path_info = $ENV{PATH_INFO}; + if (my $req_uri = $ENV{REQUEST_URI}) { + $req_uri =~ s/^\Q$base_path\E//; + $req_uri =~ s/\?.*$//; + if ($req_uri) { + # Note that if REQUEST_URI doesn't start with a /, then the user + # is probably using mod_rewrite or something to rewrite requests + # into a sub-path of their application.. + # This means that REQUEST_URI needs information from PATH_INFO + # prepending to it to be useful, otherwise the sub path which is + # being redirected to becomes the app base address which is + # incorrect. + if (substr($req_uri, 0, 1) ne '/') { + my ($match) = $req_uri =~ m|^([^/]+)|; + my ($path_info_part) = $path_info =~ m|^(.*?\Q$match\E)|; + substr($req_uri, 0, length($match), $path_info_part); + } + $path_info = $req_uri; + } + } + # set the request URI - my $path = $base_path . ( $ENV{PATH_INFO} || '' ); + my $path = $base_path . ( $path_info || '' ); $path =~ s{^/+}{}; # Using URI directly is way too slow, so we construct the URLs manually @@ -163,7 +195,7 @@ sub prepare_path { my $query = $ENV{QUERY_STRING} ? '?' . $ENV{QUERY_STRING} : ''; my $uri = $scheme . '://' . $host . '/' . $path . $query; - $c->request->uri( bless \$uri, $uri_class ); + $c->request->uri( bless(\$uri, $uri_class)->canonical ); # set the base URI # base must end in a slash diff --git a/lib/Catalyst/Engine/FastCGI.pm b/lib/Catalyst/Engine/FastCGI.pm index a6e9688..9f7dfb2 100644 --- a/lib/Catalyst/Engine/FastCGI.pm +++ b/lib/Catalyst/Engine/FastCGI.pm @@ -463,8 +463,8 @@ The server configuration block should look roughly like: fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; + fastcgi_param SCRIPT_NAME /; fastcgi_param PATH_INFO $fastcgi_script_name; - fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; @@ -490,14 +490,14 @@ simply include that file. =head3 Non-root configuration -If you properly specify the PATH_INFO and SCRIPT_NAME parameters your -application will be accessible at any path. The SCRIPT_NAME variable is the +If you properly specify the PATH_INFO and SCRIPT_NAME parameters your +application will be accessible at any path. The SCRIPT_NAME variable is the prefix of your application, and PATH_INFO would be everything in addition. As an example, if your application is rooted at /myapp, you would configure: - fastcgi_param PATH_INFO /myapp/; - fastcgi_param SCRIPT_NAME $fastcgi_script_name; + fastcgi_param SCRIPT_NAME /myapp/; + fastcgi_param PATH_INFO $fastcgi_script_name; C<$fastcgi_script_name> would be "/myapp/path/of/the/action". Catalyst will process this accordingly and setup the application base as expected. diff --git a/lib/Catalyst/Engine/HTTP.pm b/lib/Catalyst/Engine/HTTP.pm index 62c5d0b..7f01795 100644 --- a/lib/Catalyst/Engine/HTTP.pm +++ b/lib/Catalyst/Engine/HTTP.pm @@ -339,7 +339,7 @@ sub run { use Config; $ENV{PERL5LIB} .= join $Config{path_sep}, @INC; - exec $^X, $0, @{ $options->{argv} }; + exec $^X, $0, @{ $options->{argv} || [] }; } exit; diff --git a/lib/Catalyst/Exception.pm b/lib/Catalyst/Exception.pm index c8d5547..7506483 100644 --- a/lib/Catalyst/Exception.pm +++ b/lib/Catalyst/Exception.pm @@ -2,12 +2,6 @@ package Catalyst::Exception; # XXX: See bottom of file for Exception implementation -package Catalyst::Exception::Base; - -use Moose; -use Carp; -use namespace::clean -except => 'meta'; - =head1 NAME Catalyst::Exception - Catalyst Exception Class @@ -32,48 +26,6 @@ This is the Catalyst Exception class. Throws a fatal exception. -=cut - -has message => ( - is => 'ro', - isa => 'Str', - default => sub { $! || '' }, -); - -use overload - q{""} => \&as_string, - fallback => 1; - -sub as_string { - my ($self) = @_; - return $self->message; -} - -around BUILDARGS => sub { - my ($next, $class, @args) = @_; - if (@args == 1 && !ref $args[0]) { - @args = (message => $args[0]); - } - - my $args = $class->$next(@args); - $args->{message} ||= $args->{error} - if exists $args->{error}; - - return $args; -}; - -sub throw { - my $class = shift; - my $error = $class->new(@_); - local $Carp::CarpLevel = 1; - croak $error; -} - -sub rethrow { - my ($self) = @_; - croak $self; -} - =head2 meta Provided by Moose @@ -89,19 +41,30 @@ it under the same terms as Perl itself. =cut -Catalyst::Exception::Base->meta->make_immutable; - -package Catalyst::Exception; +{ + package Catalyst::Exception::Base; -use Moose; -use namespace::clean -except => 'meta'; + use Moose; + use namespace::clean -except => 'meta'; -use vars qw[$CATALYST_EXCEPTION_CLASS]; + with 'Catalyst::Exception::Basic'; -BEGIN { - extends($CATALYST_EXCEPTION_CLASS || 'Catalyst::Exception::Base'); + __PACKAGE__->meta->make_immutable; } -__PACKAGE__->meta->make_immutable; +{ + package Catalyst::Exception; + + use Moose; + use namespace::clean -except => 'meta'; + + use vars qw[$CATALYST_EXCEPTION_CLASS]; + + BEGIN { + extends($CATALYST_EXCEPTION_CLASS || 'Catalyst::Exception::Base'); + } + + __PACKAGE__->meta->make_immutable; +} 1; diff --git a/lib/Catalyst/Exception/Basic.pm b/lib/Catalyst/Exception/Basic.pm new file mode 100644 index 0000000..713bb5f --- /dev/null +++ b/lib/Catalyst/Exception/Basic.pm @@ -0,0 +1,107 @@ +package Catalyst::Exception::Basic; + +use MooseX::Role::WithOverloading; +use Carp; +use namespace::clean -except => 'meta'; + +with 'Catalyst::Exception::Interface'; + +has message => ( + is => 'ro', + isa => 'Str', + default => sub { $! || '' }, +); + +sub as_string { + my ($self) = @_; + return $self->message; +} + +around BUILDARGS => sub { + my ($next, $class, @args) = @_; + if (@args == 1 && !ref $args[0]) { + @args = (message => $args[0]); + } + + my $args = $class->$next(@args); + $args->{message} ||= $args->{error} + if exists $args->{error}; + + return $args; +}; + +sub throw { + my $class = shift; + my $error = $class->new(@_); + local $Carp::CarpLevel = 1; + croak $error; +} + +sub rethrow { + my ($self) = @_; + croak $self; +} + +1; + +=head1 NAME + +Catalyst::Exception::Basic - Basic Catalyst Exception Role + +=head1 SYNOPSIS + + package My::Exception; + use Moose; + use namespace::clean -except => 'meta'; + + with 'Catalyst::Exception::Basic'; + + # Elsewhere.. + My::Exception->throw( qq/Fatal exception/ ); + +See also L and L. + +=head1 DESCRIPTION + +This is the basic Catalyst Exception role which implements all of +L. + +=head1 ATTRIBUTES + +=head2 message + +Holds the exception message. + +=head1 METHODS + +=head2 as_string + +Stringifies the exception's message attribute. +Called when the object is stringified by overloading. + +=head2 throw( $message ) + +=head2 throw( message => $message ) + +=head2 throw( error => $error ) + +Throws a fatal exception. + +=head2 rethrow( $exception ) + +Rethrows a caught exception. + +=head2 meta + +Provided by Moose + +=head1 AUTHORS + +Catalyst Contributors, see Catalyst.pm + +=head1 COPYRIGHT + +This library is free software. You can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Catalyst/Exception/Detach.pm b/lib/Catalyst/Exception/Detach.pm index 5f98119..88f42c1 100644 --- a/lib/Catalyst/Exception/Detach.pm +++ b/lib/Catalyst/Exception/Detach.pm @@ -3,7 +3,7 @@ package Catalyst::Exception::Detach; use Moose; use namespace::clean -except => 'meta'; -extends 'Catalyst::Exception'; +with 'Catalyst::Exception::Basic'; has '+message' => ( default => "catalyst_detach\n", @@ -19,4 +19,34 @@ __END__ Catalyst::Exception::Detach - Exception for redispatching using $ctx->detach() +=head1 DESCRIPTION + +This is the class for the Catalyst Exception which is thrown then you call +C<< $c->detach() >>. + +This class is not intended to be used directly by users. + +=head2 meta + +Provided by Moose + +=head1 SEE ALSO + +=over 4 + +=item L + +=item L + +=back + +=head1 AUTHORS + +Catalyst Contributors, see Catalyst.pm + +=head1 COPYRIGHT + +This library is free software. You can redistribute it and/or modify +it under the same terms as Perl itself. + =cut diff --git a/lib/Catalyst/Exception/Go.pm b/lib/Catalyst/Exception/Go.pm index 6be9efe..f7d7362 100644 --- a/lib/Catalyst/Exception/Go.pm +++ b/lib/Catalyst/Exception/Go.pm @@ -3,7 +3,7 @@ package Catalyst::Exception::Go; use Moose; use namespace::clean -except => 'meta'; -extends 'Catalyst::Exception'; +with 'Catalyst::Exception::Basic'; has '+message' => ( default => "catalyst_go\n", @@ -19,4 +19,34 @@ __END__ Catalyst::Exception::Go - Exception for redispatching using $ctx->go() +=head1 DESCRIPTION + +This is the class for the Catalyst Exception which is thrown then you call +C<< $c->go() >>. + +This class is not intended to be used directly by users. + +=head2 meta + +Provided by Moose + +=head1 SEE ALSO + +=over 4 + +=item L + +=item L + +=back + +=head1 AUTHORS + +Catalyst Contributors, see Catalyst.pm + +=head1 COPYRIGHT + +This library is free software. You can redistribute it and/or modify +it under the same terms as Perl itself. + =cut diff --git a/lib/Catalyst/Exception/Interface.pm b/lib/Catalyst/Exception/Interface.pm new file mode 100644 index 0000000..371bfa3 --- /dev/null +++ b/lib/Catalyst/Exception/Interface.pm @@ -0,0 +1,77 @@ +package Catalyst::Exception::Interface; + +use MooseX::Role::WithOverloading; +use namespace::clean -except => 'meta'; + +use overload + q{""} => sub { $_[0]->as_string }, + fallback => 1; + +requires qw/as_string throw rethrow/; + +1; + +__END__ + +=head1 NAME + +Catalyst::Exception::Interface - Role defining the interface for Catalyst exceptions + +=head1 SYNOPSIS + + package My::Catalyst::Like::Exception; + use Moose; + use namespace::clean -except => 'meta'; + + with 'Catalyst::Exception::Interface'; + + # This comprises the required interface. + sub as_string { 'the exception text for stringification' } + sub die { shift; die @_ } + sub die { shift; die @_ } + +=head1 DESCRIPTION + +This is a role for the required interface for Catalyst exceptions. + +It ensures that all exceptions follow the expected interface, +and adds overloading for stringification when composed onto a +class. + +Note that if you compose this role onto another role, that role +must use L. + +=head1 REQUIRED METHODS + +=head2 as_string + +=head2 throw + +=head2 rethrow + +=head1 METHODS + +=head2 meta + +Provided by Moose + +=head1 SEE ALSO + +=over 4 + +=item L + +=item L + +=back + +=head1 AUTHORS + +Catalyst Contributors, see Catalyst.pm + +=head1 COPYRIGHT + +This library is free software. You can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Catalyst/ROADMAP.pod b/lib/Catalyst/ROADMAP.pod index e872e5e..9c29d1d 100644 --- a/lib/Catalyst/ROADMAP.pod +++ b/lib/Catalyst/ROADMAP.pod @@ -8,11 +8,6 @@ in the the catalyst trunk, currently at Make sure you get it from there to ensure you have the latest version. -=head2 5.80000 1st Quarter 2009 - -Next major planned release, ports Catalyst to Moose, and does some refactoring -to help app/ctx. - =head2 5.81000 =over diff --git a/lib/Catalyst/Request.pm b/lib/Catalyst/Request.pm index 9c9705e..ab5c0da 100644 --- a/lib/Catalyst/Request.pm +++ b/lib/Catalyst/Request.pm @@ -210,7 +210,7 @@ Returns a reference to an array containing the arguments. For example, if your action was - package MyApp::C::Foo; + package MyApp::Controller::Foo; sub moose : Local { ... @@ -673,14 +673,6 @@ sub uri_with { return $uri; } -=head2 $req->user - -Returns the currently logged in user. B, do not call, -this will be removed in version 5.81. To retrieve the currently authenticated -user, see C<< $c->user >> and C<< $c->user_exists >> in -L. For the C provided by the -webserver, see C<< $req->remote_user >> below. - =head2 $req->remote_user Returns the value of the C environment variable. diff --git a/lib/Catalyst/Response.pm b/lib/Catalyst/Response.pm index cb73a34..f268aef 100644 --- a/lib/Catalyst/Response.pm +++ b/lib/Catalyst/Response.pm @@ -65,7 +65,7 @@ will turn the Catalyst::Response into a HTTP Response and return it to the clien =head1 METHODS -=head2 $res->body(<$text|$fh|$iohandle_object) +=head2 $res->body( $text | $fh | $iohandle_object ) $c->response->body('Catalyst rocks!'); @@ -150,7 +150,7 @@ C<302>. This is a convenience method that sets the Location header to the redirect destination, and then sets the response status. You will -want to C< return; > or C< $c->detach() > to interrupt the normal +want to C< return > or C<< $c->detach() >> to interrupt the normal processing flow if you want the redirect to occur straight away. =cut diff --git a/lib/Catalyst/Runtime.pm b/lib/Catalyst/Runtime.pm index 0304b89..c65bfbb 100644 --- a/lib/Catalyst/Runtime.pm +++ b/lib/Catalyst/Runtime.pm @@ -7,7 +7,7 @@ BEGIN { require 5.008004; } # Remember to update this in Catalyst as well! -our $VERSION='5.80013'; +our $VERSION='5.80018'; $VERSION = eval $VERSION; diff --git a/lib/Catalyst/Script/CGI.pm b/lib/Catalyst/Script/CGI.pm new file mode 100644 index 0000000..e80a5f7 --- /dev/null +++ b/lib/Catalyst/Script/CGI.pm @@ -0,0 +1,34 @@ +package Catalyst::Script::CGI; +use Moose; +BEGIN { $ENV{CATALYST_ENGINE} ||= 'CGI' } +use namespace::autoclean; + +with 'Catalyst::ScriptRole'; + +__PACKAGE__->meta->make_immutable; + +=head1 NAME + +Catalyst::Script::CGI - The CGI Catalyst Script + +=head1 SYNOPSIS + + myapp_cgi.pl [options] + + Options: + -h --help display this help and exits + +=head1 DESCRIPTION + +This is a script to run the Catalyst engine specialized for the CGI environment. + +=head1 AUTHORS + +Catalyst Contributors, see Catalyst.pm + +=head1 COPYRIGHT + +This library is free software. You can redistribute it and/or modify it under +the same terms as Perl itself. + +=cut diff --git a/lib/Catalyst/Script/Create.pm b/lib/Catalyst/Script/Create.pm new file mode 100644 index 0000000..05b4a66 --- /dev/null +++ b/lib/Catalyst/Script/Create.pm @@ -0,0 +1,103 @@ +package Catalyst::Script::Create; +use Moose; +use MooseX::Types::Moose qw/Bool Str/; +use namespace::autoclean; + +with 'Catalyst::ScriptRole'; + +has force => ( + traits => [qw(Getopt)], + cmd_aliases => 'nonew', + isa => Bool, + is => 'ro', + documentation => 'Force new scripts', +); + +has debug => ( + traits => [qw(Getopt)], + cmd_aliases => 'd', + isa => Bool, + is => 'ro', + documentation => 'Force debug mode', +); + +has mechanize => ( + traits => [qw(Getopt)], + cmd_aliases => 'mech', + isa => Bool, + is => 'ro', + documentation => 'use WWW::Mechanize', +); + +has helper_class => ( + isa => Str, + is => 'ro', + builder => '_build_helper_class', +); + +sub _build_helper_class { 'Catalyst::Helper' } + +sub run { + my ($self) = @_; + + $self->_getopt_full_usage if !$self->ARGV->[0]; + + my $helper_class = $self->helper_class; + Class::MOP::load_class($helper_class); + my $helper = $helper_class->new( { '.newfiles' => !$self->force, mech => $self->mechanize } ); + + $self->_getopt_full_usage unless $helper->mk_component( $self->application_name, @ARGV ); + +} + +__PACKAGE__->meta->make_immutable; + +=head1 NAME + +Catalyst::Script::Create - Create a new Catalyst Component + +=head1 SYNOPSIS + + myapp_create.pl [options] model|view|controller name [helper] [options] + + Options: + --force don't create a .new file where a file to be created exists + --mechanize use Test::WWW::Mechanize::Catalyst for tests if available + --help display this help and exits + + Examples: + myapp_create.pl controller My::Controller + myapp_create.pl controller My::Controller BindLex + myapp_create.pl -mechanize controller My::Controller + myapp_create.pl view My::View + myapp_create.pl view MyView TT + myapp_create.pl view TT TT + myapp_create.pl model My::Model + myapp_create.pl model SomeDB DBIC::Schema MyApp::Schema create=dynamic\ + dbi:SQLite:/tmp/my.db + myapp_create.pl model AnotherDB DBIC::Schema MyApp::Schema create=static\ + dbi:Pg:dbname=foo root 4321 + + See also: + perldoc Catalyst::Manual + perldoc Catalyst::Manual::Intro + +=head1 DESCRIPTION + +Create a new Catalyst Component. + +Existing component files are not overwritten. If any of the component files +to be created already exist the file will be written with a '.new' suffix. +This behavior can be suppressed with the C<--force> option. + +=head1 AUTHORS + +Catalyst Contributors, see Catalyst.pm + +=head1 COPYRIGHT + +This library is free software, you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + diff --git a/lib/Catalyst/Script/FastCGI.pm b/lib/Catalyst/Script/FastCGI.pm new file mode 100644 index 0000000..60b4133 --- /dev/null +++ b/lib/Catalyst/Script/FastCGI.pm @@ -0,0 +1,113 @@ +package Catalyst::Script::FastCGI; + +BEGIN { $ENV{CATALYST_ENGINE} ||= 'FastCGI' } +use Moose; +use MooseX::Types::Moose qw/Str Bool Int/; +use namespace::autoclean; + +with 'Catalyst::ScriptRole'; + +has listen => ( + traits => [qw(Getopt)], + cmd_aliases => 'l', + isa => Str, + is => 'ro', + documentation => 'Specify a listening port/socket', +); + +has pidfile => ( + traits => [qw(Getopt)], + cmd_aliases => [qw/pid p/], + isa => Str, + is => 'ro', + documentation => 'Specify a pidfile', +); + +has daemon => ( + traits => [qw(Getopt)], + isa => Bool, + is => 'ro', + cmd_aliases => [qw/d detach/], # Eww, detach is here as we fucked it up.. Deliberately not documented + documentation => 'Daemonize (go into the background)', +); + +has manager => ( + traits => [qw(Getopt)], + isa => Str, + is => 'ro', + cmd_aliases => 'M', + documentation => 'Use a different FastCGI process manager class', +); + +has keeperr => ( + traits => [qw(Getopt)], + cmd_aliases => 'e', + isa => Bool, + is => 'ro', + documentation => 'Log STDERR', +); + +has nproc => ( + traits => [qw(Getopt)], + cmd_aliases => 'n', + isa => Int, + is => 'ro', + documentation => 'Specify a number of child processes', +); + +sub _application_args { + my ($self) = shift; + return ( + $self->listen, + { + nproc => $self->nproc, + pidfile => $self->pidfile, + manager => $self->manager, + detach => $self->daemon, + keep_stderr => $self->keeperr, + } + ); +} + +__PACKAGE__->meta->make_immutable; + +=head1 NAME + +Catalyst::Script::FastCGI - The FastCGI Catalyst Script + +=head1 SYNOPSIS + + myapp_fastcgi.pl [options] + + Options: + -? --help display this help and exits + -l --listen Socket path to listen on + (defaults to standard input) + can be HOST:PORT, :PORT or a + filesystem path + -n --nproc specify number of processes to keep + to serve requests (defaults to 1, + requires -listen) + -p --pidfile specify filename for pid file + (requires -listen) + -d --daemon daemonize (requires -listen) + -M --manager specify alternate process manager + (FCGI::ProcManager sub-class) + or empty string to disable + -e --keeperr send error messages to STDOUT, not + to the webserver + +=head1 DESCRIPTION + +Run a Catalyst application as fastcgi. + +=head1 AUTHORS + +Catalyst Contributors, see Catalyst.pm + +=head1 COPYRIGHT + +This library is free software. You can redistribute it and/or modify it under +the same terms as Perl itself. + +=cut diff --git a/lib/Catalyst/Script/Server.pm b/lib/Catalyst/Script/Server.pm new file mode 100644 index 0000000..e1f1049 --- /dev/null +++ b/lib/Catalyst/Script/Server.pm @@ -0,0 +1,258 @@ +package Catalyst::Script::Server; + +BEGIN { + $ENV{CATALYST_ENGINE} ||= 'HTTP'; + require Catalyst::Engine::HTTP; +} + +use Moose; +use MooseX::Types::Common::Numeric qw/PositiveInt/; +use MooseX::Types::Moose qw/ArrayRef Str Bool Int RegexpRef/; +use Catalyst::Utils; +use namespace::autoclean; + +with 'Catalyst::ScriptRole'; + +__PACKAGE__->meta->get_attribute('help')->cmd_aliases('?'); + +has debug => ( + traits => [qw(Getopt)], + cmd_aliases => 'd', + isa => Bool, + is => 'ro', + documentation => q{Force debug mode}, +); + +has host => ( + traits => [qw(Getopt)], + cmd_aliases => 'h', + isa => Str, + is => 'ro', + # N.B. undef (the default) means we bind on all interfaces on the host. + documentation => 'Specify a hostname or IP on this host for the server to bind to', +); + +has fork => ( + traits => [qw(Getopt)], + cmd_aliases => 'f', + isa => Bool, + is => 'ro', + default => 0, + documentation => 'Fork the server to be able to serve multiple requests at once', +); + +has port => ( + traits => [qw(Getopt)], + cmd_aliases => 'p', + isa => PositiveInt, + is => 'ro', + default => sub { + Catalyst::Utils::env_value(shift->application_name, 'port') || 3000 + }, + documentation => 'Specify a different listening port (to the default port 3000)', +); + +has pidfile => ( + traits => [qw(Getopt)], + cmd_aliases => 'pid', + isa => Str, + is => 'ro', + documentation => 'Specify a pidfile', +); + +has keepalive => ( + traits => [qw(Getopt)], + cmd_aliases => 'k', + isa => Bool, + is => 'ro', + default => 0, + documentation => 'Support keepalive', +); + +has background => ( + traits => [qw(Getopt)], + cmd_aliases => 'bg', + isa => Bool, + is => 'ro', + default => 0, + documentation => 'Run in the background', +); + +has restart => ( + traits => [qw(Getopt)], + cmd_aliases => 'r', + isa => Bool, + is => 'ro', + default => sub { + Catalyst::Utils::env_value(shift->application_name, 'reload') || 0; + }, + documentation => 'use Catalyst::Restarter to detect code changes and restart the application', +); + +has restart_directory => ( + traits => [qw(Getopt)], + cmd_aliases => [ 'rdir', 'restartdirectory' ], + isa => ArrayRef[Str], + is => 'ro', + documentation => 'Restarter directory to watch', + predicate => '_has_restart_directory', +); + +has restart_delay => ( + traits => [qw(Getopt)], + cmd_aliases => 'rd', + isa => Int, + is => 'ro', + documentation => 'Set a restart delay', + predicate => '_has_restart_delay', +); + +{ + use Moose::Util::TypeConstraints; + + my $tc = subtype as RegexpRef; + coerce $tc, from Str, via { qr/$_/ }; + + MooseX::Getopt::OptionTypeMap->add_option_type_to_map($tc => '=s'); + + has restart_regex => ( + traits => [qw(Getopt)], + cmd_aliases => 'rr', + isa => $tc, + coerce => 1, + is => 'ro', + documentation => 'Restart regex', + predicate => '_has_restart_regex', + ); +} + +has follow_symlinks => ( + traits => [qw(Getopt)], + cmd_aliases => 'sym', + isa => Bool, + is => 'ro', + default => 0, + documentation => 'Follow symbolic links', + predicate => '_has_follow_symlinks', +); + +sub _restarter_args { + my $self = shift; + + return ( + argv => $self->ARGV, + start_sub => sub { $self->_run_application }, + ($self->_has_follow_symlinks ? (follow_symlinks => $self->follow_symlinks) : ()), + ($self->_has_restart_delay ? (sleep_interval => $self->restart_delay) : ()), + ($self->_has_restart_directory ? (directories => $self->restart_directory) : ()), + ($self->_has_restart_regex ? (filter => $self->restart_regex) : ()), + ); +} + +sub run { + my $self = shift; + + local $ENV{CATALYST_DEBUG} = 1 + if $self->debug; + + if ( $self->restart ) { + die "Cannot run in the background and also watch for changed files.\n" + if $self->background; + + # If we load this here, then in the case of a restarter, it does not + # need to be reloaded for each restart. + require Catalyst; + + # If this isn't done, then the Catalyst::Devel tests for the restarter + # fail. + $| = 1 if $ENV{HARNESS_ACTIVE}; + + require Catalyst::Restarter; + + my $subclass = Catalyst::Restarter->pick_subclass; + + my $restarter = $subclass->new( + $self->_restarter_args() + ); + + $restarter->run_and_watch; + } + else { + $self->_run_application; + } + + +} + +sub _application_args { + my ($self) = shift; + return ( + $self->port, + $self->host, + { + argv => $self->ARGV, + map { $_ => $self->$_ } qw/ + fork + keepalive + background + pidfile + keepalive + follow_symlinks + /, + }, + ); +} + +__PACKAGE__->meta->make_immutable; + +1; + +=head1 NAME + +Catalyst::Script::Server - Catalyst test server + +=head1 SYNOPSIS + + myapp_server.pl [options] + + Options: + -d --debug force debug mode + -f --fork handle each request in a new process + (defaults to false) + --help display this help and exits + -h --host host (defaults to all) + -p --port port (defaults to 3000) + -k --keepalive enable keep-alive connections + -r --restart restart when files get modified + (defaults to false) + --rd --restart_delay delay between file checks + (ignored if you have Linux::Inotify2 installed) + --rr --restart_regex regex match files that trigger + a restart when modified + (defaults to '\.yml$|\.yaml$|\.conf|\.pm$') + --rdir --restart_directory the directory to search for + modified files, can be set mulitple times + (defaults to '[SCRIPT_DIR]/..') + --sym --follow_symlinks follow symlinks in search directories + (defaults to false. this is a no-op on Win32) + --bg --background run the process in the background + --pid --pidfile specify filename for pid file + + See also: + perldoc Catalyst::Manual + perldoc Catalyst::Manual::Intro + +=head1 DESCRIPTION + +Run a Catalyst test server for this application. + +=head1 AUTHORS + +Catalyst Contributors, see Catalyst.pm + +=head1 COPYRIGHT + +This library is free software. You can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Catalyst/Script/Test.pm b/lib/Catalyst/Script/Test.pm new file mode 100644 index 0000000..53473a4 --- /dev/null +++ b/lib/Catalyst/Script/Test.pm @@ -0,0 +1,43 @@ +package Catalyst::Script::Test; +use Moose; +use Catalyst::Test (); +use namespace::autoclean; + +with 'Catalyst::ScriptRole'; + +sub run { + my $self = shift; + + Catalyst::Test->import($self->application_name); + + print request($self->ARGV->[0])->content . "\n"; +} + + +__PACKAGE__->meta->make_immutable; + +=head1 NAME + +Catalyst::Script::Test - Test Catalyst application on the command line + +=head1 SYNOPSIS + + myapp_test.pl [options] /path + + Options: + -h --help display this help and exits + +=head1 DESCRIPTION + +Script to perform a test hit against your application and display the output. + +=head1 AUTHORS + +Catalyst Contributors, see Catalyst.pm + +=head1 COPYRIGHT + +This library is free software. You can redistribute it and/or modify it under +the same terms as Perl itself. + +=cut diff --git a/lib/Catalyst/ScriptRole.pm b/lib/Catalyst/ScriptRole.pm new file mode 100644 index 0000000..535ed3d --- /dev/null +++ b/lib/Catalyst/ScriptRole.pm @@ -0,0 +1,112 @@ +package Catalyst::ScriptRole; +use Moose::Role; +use MooseX::Types::Moose qw/Str Bool/; +use Pod::Usage; +use MooseX::Getopt; +use namespace::autoclean; + +with 'MooseX::Getopt' => { + excludes => [qw/ + _getopt_spec_warnings + _getopt_spec_exception + _getopt_full_usage + /], +}; + +has application_name => ( + traits => ['NoGetopt'], + isa => Str, + is => 'ro', + required => 1, +); + +has help => ( + traits => ['Getopt'], + isa => Bool, + is => 'ro', + documentation => 'Display this help and exit', + cmd_aliases => ['?', 'h'], +); + +sub _getopt_spec_exception {} + +sub _getopt_spec_warnings { + shift; + warn @_; +} + +sub _getopt_full_usage { + my $self = shift; + pod2usage(); + exit 0; +} + +before run => sub { + my $self = shift; + $self->_getopt_full_usage if $self->help; +}; + +sub run { + my $self = shift; + $self->_run_application; +} + +sub _application_args { + () +} + +sub _run_application { + my $self = shift; + my $app = $self->application_name; + Class::MOP::load_class($app); + $app->run($self->_application_args); +} + +1; + +=head1 NAME + +Catalyst::ScriptRole - Common functionality for Catalyst scripts. + +=head1 SYNOPSIS + + package MyApp::Script::Foo; + use Moose; + use namespace::autoclean; + + with 'Catalyst::ScriptRole'; + + sub _application_args { ... } + +=head1 DESCRIPTION + +Role with the common functionality of Catalyst scripts. + +=head1 METHODS + +=head2 run + +The method invoked to run the application. + +=head1 ATTRIBUTES + +=head2 application_name + +The name of the application class, e.g. MyApp + +=head1 SEE ALSO + +L + +L + +=head1 AUTHORS + +Catalyst Contributors, see Catalyst.pm + +=head1 COPYRIGHT + +This library is free software, you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Catalyst/ScriptRunner.pm b/lib/Catalyst/ScriptRunner.pm new file mode 100644 index 0000000..247ce30 --- /dev/null +++ b/lib/Catalyst/ScriptRunner.pm @@ -0,0 +1,56 @@ +package Catalyst::ScriptRunner; +use Moose; +use FindBin; +use lib; +use File::Spec; +use namespace::autoclean; + +sub run { + my ($self, $class, $scriptclass) = @_; + my $classtoload = "${class}::Script::$scriptclass"; + + lib->import(File::Spec->catdir($FindBin::Bin, '..', 'lib')); + + unless ( eval { Class::MOP::load_class($classtoload) } ) { + warn("Could not load $classtoload - falling back to Catalyst::Script::$scriptclass : $@\n") + if $@ !~ /Can't locate/; + $classtoload = "Catalyst::Script::$scriptclass"; + Class::MOP::load_class($classtoload); + } + $classtoload->new_with_options( application_name => $class )->run; +} + +__PACKAGE__->meta->make_immutable; + +=head1 NAME + +Catalyst::ScriptRunner - The Catalyst Framework script runner + +=head1 SYNOPSIS + + # Will run MyApp::Script::Server if it exists, otherwise + # will run Catalyst::Script::Server. + Catalyst::ScriptRunner->run('MyApp', 'Server'); + +=head1 DESCRIPTION + +This class is responsible for running scripts, either in the application specific namespace +(e.g. C), or the Catalyst namespace (e.g. C) + +=head1 METHODS + +=head2 run ($application_class, $scriptclass) + +Called with two parameters, the application classs (e.g. MyApp) +and the script class, (i.e. one of Server/FastCGI/CGI/Create/Test) + +=head1 AUTHORS + +Catalyst Contributors, see Catalyst.pm + +=head1 COPYRIGHT + +This library is free software. You can redistribute it and/or modify it under +the same terms as Perl itself. + +=cut diff --git a/lib/Catalyst/Test.pm b/lib/Catalyst/Test.pm index 8776803..f987172 100644 --- a/lib/Catalyst/Test.pm +++ b/lib/Catalyst/Test.pm @@ -103,6 +103,12 @@ our $default_host; sub import { my ($self, $class, $opts) = @_; + Carp::carp( +qq{Importing Catalyst::Test without an application name is deprecated:\n +Instead of saying: use Catalyst::Test; +say: use Catalyst::Test (); # If you don't want to import a test app right now. +or say: use Catalyst::Test 'MyApp'; # If you do want to import a test app.\n\n}) + unless $class; $import->($self, '-all' => { class => $class }); $opts = {} unless ref $opts eq 'HASH'; $default_host = $opts->{default_host} if exists $opts->{default_host}; diff --git a/lib/Catalyst/Utils.pm b/lib/Catalyst/Utils.pm index cc3f326..53bf795 100644 --- a/lib/Catalyst/Utils.pm +++ b/lib/Catalyst/Utils.pm @@ -124,7 +124,7 @@ sub class2prefix { Returns a tempdir for a class. If create is true it will try to create the path. My::App becomes /tmp/my/app - My::App::C::Foo::Bar becomes /tmp/my/app/c/foo/bar + My::App::Controller::Foo::Bar becomes /tmp/my/app/c/foo/bar =cut diff --git a/script/catalyst.pl b/script/catalyst.pl index e9083cc..ef9771c 100755 --- a/script/catalyst.pl +++ b/script/catalyst.pl @@ -41,9 +41,10 @@ my $helper = Catalyst::Helper->new( '.newfiles' => !$force, 'makefile' => $makefile, 'scripts' => $scripts, - 'short' => 0, # FIXME - to be removed. + name => $ARGV[0], } ); +# Pass $ARGV[0] for compatibility with old ::Devel pod2usage(1) unless $helper->mk_app( $ARGV[0] ); 1; diff --git a/t/02pod.t b/t/02pod.t deleted file mode 100644 index 05aa78a..0000000 --- a/t/02pod.t +++ /dev/null @@ -1,7 +0,0 @@ -use Test::More; - -eval "use Test::Pod 1.14"; -plan skip_all => 'Test::Pod 1.14 required' if $@; -plan skip_all => 'set TEST_POD to enable this test' unless $ENV{TEST_POD} || -e 'inc/.author'; - -all_pod_files_ok(); diff --git a/t/03podcoverage.t b/t/03podcoverage.t deleted file mode 100644 index 61a207b..0000000 --- a/t/03podcoverage.t +++ /dev/null @@ -1,13 +0,0 @@ -use Test::More; - -eval "use Pod::Coverage 0.19"; -plan skip_all => 'Pod::Coverage 0.19 required' if $@; -eval "use Test::Pod::Coverage 1.04"; -plan skip_all => 'Test::Pod::Coverage 1.04 required' if $@; -plan skip_all => 'set TEST_POD to enable this test' unless $ENV{TEST_POD} || -e 'inc/.author'; - -all_pod_coverage_ok( - { - also_private => ['BUILD'] - } -); diff --git a/t/04critic.t b/t/04critic.t deleted file mode 100644 index 5a4a226..0000000 --- a/t/04critic.t +++ /dev/null @@ -1,21 +0,0 @@ -use strict; -use warnings; - -use File::Spec; -use FindBin (); -use Test::More; - -if ( !-e "$FindBin::Bin/../MANIFEST.SKIP" ) { - plan skip_all => 'Critic test only for developers.'; -} -else { - eval { require Test::NoTabs }; - if ( $@ ) { - plan tests => 1; - fail( 'You must install Test::NoTabs to run 04critic.t' ); - exit; - } -} - -Test::NoTabs->import; -all_perl_files_ok(qw/lib/); diff --git a/t/aggregate/catalyst_test_utf8.t b/t/aggregate/catalyst_test_utf8.t new file mode 100644 index 0000000..d8eb56f --- /dev/null +++ b/t/aggregate/catalyst_test_utf8.t @@ -0,0 +1,27 @@ +use strict; +use warnings; +use FindBin qw/$Bin/; +use lib "$Bin/../lib"; + +use Test::More; +# "binmode STDOUT, ':utf8'" is insufficient, see http://code.google.com/p/test-more/issues/detail?id=46#c1 +binmode Test::More->builder->output, ":utf8"; +binmode Test::More->builder->failure_output, ":utf8"; + +use Catalyst::Test 'TestAppEncoding'; + +plan skip_all => 'This test does not run live' + if $ENV{CATALYST_SERVER}; + +{ + # Test for https://rt.cpan.org/Ticket/Display.html?id=53678 + # Catalyst::Test::get currently returns the raw octets, but it + # would be more useful if it decoded the content based on the + # Content-Type charset, as Test::WWW::Mechanize::Catalyst does + use utf8; + my $body = get('/utf8_non_ascii_content'); + utf8::decode($body); + is $body, 'ʇsʎlɐʇɐɔ', 'Catalyst::Test::get returned content correctly UTF-8 encoded'; +} + +done_testing; diff --git a/t/aggregate/deprecated_test_import.t b/t/aggregate/deprecated_test_import.t new file mode 100644 index 0000000..ee90eea --- /dev/null +++ b/t/aggregate/deprecated_test_import.t @@ -0,0 +1,16 @@ +use strict; +use warnings; + +use Test::More; +use Catalyst::Test (); + +my $warn; +{ + local $SIG{__WARN__} = sub { $warn = shift; }; + Catalyst::Test->import(); +} +ok $warn; +like $warn, qr/deprecated/; + +done_testing; + diff --git a/t/aggregate/error_page_dump.t b/t/aggregate/error_page_dump.t new file mode 100644 index 0000000..099f8da --- /dev/null +++ b/t/aggregate/error_page_dump.t @@ -0,0 +1,15 @@ +use strict; +use warnings; +use Test::More; +use Test::Exception; + +use Catalyst::Engine; + +my $m = sub { Catalyst::Engine->_dump_error_page_element(@_) }; + +lives_ok { $m->('Scalar' => ['foo' => 'bar']) }; +lives_ok { $m->('Array' => ['foo' => []]) }; +lives_ok { $m->('Hash' => ['foo' => {}]) }; + +done_testing; + diff --git a/t/aggregate/live_component_controller_action_chained.t b/t/aggregate/live_component_controller_action_chained.t index 3784fe6..fef26ef 100644 --- a/t/aggregate/live_component_controller_action_chained.t +++ b/t/aggregate/live_component_controller_action_chained.t @@ -10,7 +10,7 @@ our $iters; BEGIN { $iters = $ENV{CAT_BENCH_ITERS} || 1; } -use Test::More tests => 148*$iters; +use Test::More; use Catalyst::Test 'TestApp'; if ( $ENV{CAT_BENCHMARK} ) { @@ -908,6 +908,51 @@ sub run_tests { is( $response->content => 'a; anchor.html', 'Content OK' ); } + # CaptureArgs(1) PathPart('...') should win over CaptureArgs(2) PathPart('') + { + my @expected = qw[ + TestApp::Controller::Action::Chained->begin + TestApp::Controller::Action::Chained::CaptureArgs->base + TestApp::Controller::Action::Chained::CaptureArgs->one_arg + TestApp::Controller::Action::Chained::CaptureArgs->edit_one_arg + TestApp::Controller::Action::Chained::CaptureArgs->end + ]; + + my $expected = join( ", ", @expected ); + + # should dispatch to /base/one_args/edit_one_arg + ok( my $response = request('http://localhost/captureargs/one/edit'), + 'Correct arg order ran' ); + TODO: { + local $TODO = 'Known bug'; + is( $response->header('X-Catalyst-Executed'), + $expected, 'Executed actions' ); + is( $response->content, 'base; one_arg; edit_one_arg', 'Content OK' ); + } + } + + # PathPart('...') Args(1) should win over CaptureArgs(2) PathPart('') + { + my @expected = qw[ + TestApp::Controller::Action::Chained->begin + TestApp::Controller::Action::Chained::CaptureArgs->base + TestApp::Controller::Action::Chained::CaptureArgs->test_one_arg + TestApp::Controller::Action::Chained::CaptureArgs->end + ]; + + my $expected = join( ", ", @expected ); + + # should dispatch to /base/test_one_arg + ok( my $response = request('http://localhost/captureargs/test/one'), + 'Correct pathpart/arg ran' ); + TODO: { + local $TODO = 'Known bug'; + is( $response->header('X-Catalyst-Executed'), + $expected, 'Executed actions' ); + is( $response->content, 'base; test_plus_arg; one;', 'Content OK' ); + } + } + # # Args(0) should win over Args() if we actually have no arguments. { @@ -1018,5 +1063,31 @@ sub run_tests { 'request with URI-encoded arg' ); like( $content, qr{foo/bar;\z}, 'args decoded' ); } + + # Test round tripping, specifically the / character %2F in uri_for: + # not being able to feed it back action + captureargs and args into uri for + # and result in the original request uri is a major piece of suck ;) + foreach my $thing ( + ['foo', 'bar'], + ['foo%2Fbar', 'baz'], + ['foo', 'bar%2Fbaz'], + ['foo%2Fbar', 'baz%2Fquux'], + ['foo%2Fbar', 'baz%2Fquux', { foo => 'bar', 'baz' => 'quux%2Ffrood'}], + ['foo%2Fbar', 'baz%2Fquux', { foo => 'bar', 'baz%2Ffnoo' => 'quux%2Ffrood'}], + ) { + my $path = '/chained/roundtrip_urifor/' . + $thing->[0] . '/' . $thing->[1]; + $path .= '?' . join('&', + map { $_ .'='. $thing->[2]->{$_}} + sort keys %{$thing->[2]}) if $thing->[2]; + ok( my $content = + get('http://localhost/' . $path), + 'request ' . $path . ' ok'); + # Just check that the path matches, as who the hell knows or cares + # where the app is based (live tests etc) + ok( index($content, $path) > 1, 'uri can round trip through uri_for' ); + } } +done_testing; + diff --git a/t/aggregate/live_engine_request_escaped_path.t b/t/aggregate/live_engine_request_escaped_path.t index 0512e6a..fca5a05 100644 --- a/t/aggregate/live_engine_request_escaped_path.t +++ b/t/aggregate/live_engine_request_escaped_path.t @@ -13,7 +13,7 @@ use HTTP::Request::AsCGI; This test exposes a problem in the handling of PATH_INFO in C::Engine::CGI (and other engines) where Catalyst does not un-escape the request correctly. -If a request is URL-encoded then Catalyst fails to decode the request +If a request is URL-encoded then Catalyst fails to decode the request and thus will try and match actions using the URL-encoded value. Can NOT use Catalyst::Test as it uses HTTP::Request::AsCGI which does @@ -31,11 +31,11 @@ Index: lib/Catalyst/Engine/CGI.pm @@ -157,6 +157,8 @@ my $query = $ENV{QUERY_STRING} ? '?' . $ENV{QUERY_STRING} : ''; my $uri = $scheme . '://' . $host . '/' . $path . $query; - + + $uri = URI->new( $uri )->canonical; + $c->request->uri( bless \$uri, $uri_class ); - + # set the base URI =cut @@ -54,6 +54,7 @@ Index: lib/Catalyst/Engine/CGI.pm } # test that request with URL-escaped code works. +{ my $request = Catalyst::Utils::request( 'http://localhost/args/param%73/one/two' ); my $cgi = HTTP::Request::AsCGI->new( $request, %ENV )->setup; @@ -65,8 +66,6 @@ Index: lib/Catalyst/Engine/CGI.pm TestApp->handle_request( env => \%ENV ); ok( my $response = $cgi->restore->response ); -TODO: { - local $TODO = 'Actions should match when path parts are url encoded'; ok( $response->is_success, 'Response Successful 2xx' ); is( $response->content, 'onetwo' ); } diff --git a/t/aggregate/unit_core_component.t b/t/aggregate/unit_core_component.t index 69ac6c0..9fb578a 100644 --- a/t/aggregate/unit_core_component.t +++ b/t/aggregate/unit_core_component.t @@ -1,4 +1,4 @@ -use Test::More tests => 22; +use Test::More tests => 23; use strict; use warnings; @@ -91,3 +91,18 @@ is_deeply([ MyApp->comp('Foo') ], \@complist, 'Fallthrough return ok'); is_deeply($args, [qw/foo3 bar3/], 'args passed to ACCEPT_CONTEXT ok'); } +# BUILDARGS logic +{ + { + package MyController; + @MyController::ISA = ('Catalyst::Controller'); + } + my $warning; + local $SIG{__WARN__} = sub { + $warning = shift; + diag($warning); + }; + my $controller = MyController->new('MyApp', undef); + like( $warning, qr/uninitialized value in string eq/, "no warning for == comparison"); + +} diff --git a/t/aggregate/unit_core_engine_cgi-prepare_path.t b/t/aggregate/unit_core_engine_cgi-prepare_path.t new file mode 100644 index 0000000..f8b08ef --- /dev/null +++ b/t/aggregate/unit_core_engine_cgi-prepare_path.t @@ -0,0 +1,88 @@ +use strict; +use warnings; +use Test::More; +use FindBin qw/$Bin/; +use lib "$Bin/../lib"; +use TestApp; +use Catalyst::Engine::CGI; + +# mod_rewrite to app root for non / based app +{ + my $r = get_req ( + REDIRECT_URL => '/comics/', + SCRIPT_NAME => '/comics/dispatch.cgi', + REQUEST_URI => '/comics/', + ); + is ''.$r->uri, 'http://www.foo.com/comics/'; + is ''.$r->base, 'http://www.foo.com/comics/'; +} + +# mod_rewrite to sub path under app root for non / based app +{ + my $r = get_req ( + PATH_INFO => '/foo/bar.gif', + REDIRECT_URL => '/comics/foo/bar.gif', + SCRIPT_NAME => '/comics/dispatch.cgi', + REQUEST_URI => '/comics/foo/bar.gif', + ); + is ''.$r->uri, 'http://www.foo.com/comics/foo/bar.gif'; + is ''.$r->base, 'http://www.foo.com/comics/'; +} + +# Standard CGI hit for non / based app +{ + my $r = get_req ( + PATH_INFO => '/static/css/blueprint/screen.css', + SCRIPT_NAME => '/~bobtfish/Gitalist/script/gitalist.cgi', + REQUEST_URI => '/~bobtfish/Gitalist/script/gitalist.cgi/static/css/blueprint/screen.css', + ); + is ''.$r->uri, 'http://www.foo.com/~bobtfish/Gitalist/script/gitalist.cgi/static/css/blueprint/screen.css'; + is ''.$r->base, 'http://www.foo.com/~bobtfish/Gitalist/script/gitalist.cgi/'; +} +# / %2F %252F escaping case. +{ + my $r = get_req ( + PATH_INFO => '/%2F/%2F', + SCRIPT_NAME => '/~bobtfish/Gitalist/script/gitalist.cgi', + REQUEST_URI => '/~bobtfish/Gitalist/script/gitalist.cgi/%252F/%252F', + ); + is ''.$r->uri, 'http://www.foo.com/~bobtfish/Gitalist/script/gitalist.cgi/%252F/%252F'; + is ''.$r->base, 'http://www.foo.com/~bobtfish/Gitalist/script/gitalist.cgi/'; +} + +# Using rewrite rules to ask for a sub-path in your app. +# E.g. RewriteRule ^(.*)$ /path/to/fastcgi/domainprofi.fcgi/iframeredirect$1 [L,NS] +{ + my $r = get_req ( + PATH_INFO => '/iframeredirect/info', + SCRIPT_NAME => '', + REQUEST_URI => '/info', + ); + is ''.$r->uri, 'http://www.foo.com/iframeredirect/info'; + is ''.$r->base, 'http://www.foo.com/'; +} + + + +# FIXME - Test proxy logic +# - Test query string +# - Test non standard port numbers +# - Test // in PATH_INFO +# - Test scheme (secure request on port 80) + +sub get_req { + my %template = ( + HTTP_HOST => 'www.foo.com', + PATH_INFO => '/', + ); + + local %ENV = (%template, @_); + + my $i = TestApp->new; + $i->engine(Catalyst::Engine::CGI->new); + $i->engine->prepare_path($i); + return $i->req; +} + +done_testing; + diff --git a/t/aggregate/unit_core_script_cgi.t b/t/aggregate/unit_core_script_cgi.t new file mode 100644 index 0000000..ba187e1 --- /dev/null +++ b/t/aggregate/unit_core_script_cgi.t @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use FindBin qw/$Bin/; +use lib "$Bin/../lib"; + +use Test::More; +use Test::Exception; + +use Catalyst::Script::CGI; + +local @ARGV; +lives_ok { + Catalyst::Script::CGI->new_with_options(application_name => 'TestAppToTestScripts')->run; +} "new_with_options"; +shift @TestAppToTestScripts::RUN_ARGS; +is_deeply \@TestAppToTestScripts::RUN_ARGS, [], "no args"; + +done_testing; diff --git a/t/aggregate/unit_core_script_create.t b/t/aggregate/unit_core_script_create.t new file mode 100644 index 0000000..68e2458 --- /dev/null +++ b/t/aggregate/unit_core_script_create.t @@ -0,0 +1,75 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use Test::More; +use Test::Exception; + +use FindBin qw/$Bin/; +use lib "$Bin/../lib"; + +{ + package TestCreateScript; + use Moose; + extends 'Catalyst::Script::Create'; + our $help; + sub _getopt_full_usage { $help++ } +} + +{ + package TestHelperClass; + use Moose; + + has 'newfiles' => ( is => 'ro', init_arg => '.newfiles' ); + has 'mech' => ( is => 'ro' ); + our @ARGS; + our %p; + sub mk_component { + my $self = shift; + @ARGS = @_; + %p = ( '.newfiles' => $self->newfiles, mech => $self->mech); + return $self->_mk_component_return; + } + sub _mk_component_return { 1 } +} +{ + package TestHelperClass::False; + use Moose; + extends 'TestHelperClass'; + sub _mk_component_return { 0 } +} + +{ + local $TestCreateScript::help; + local @ARGV; + lives_ok { + TestCreateScript->new_with_options(application_name => 'TestAppToTestScripts', helper_class => 'TestHelperClass')->run; + } "no argv"; + ok $TestCreateScript::help, 'Exited with usage info'; +} +{ + local $TestCreateScript::help; + local @ARGV = 'foo'; + local @TestHelperClass::ARGS; + local %TestHelperClass::p; + lives_ok { + TestCreateScript->new_with_options(application_name => 'TestAppToTestScripts', helper_class => 'TestHelperClass')->run; + } "with argv"; + ok !$TestCreateScript::help, 'Did not exit with usage into'; + is_deeply \@TestHelperClass::ARGS, ['TestAppToTestScripts', 'foo'], 'Args correct'; + is_deeply \%TestHelperClass::p, { '.newfiles' => 1, mech => undef }, 'Params correct'; +} + +{ + local $TestCreateScript::help; + local @ARGV = 'foo'; + local @TestHelperClass::ARGS; + local %TestHelperClass::p; + lives_ok { + TestCreateScript->new_with_options(application_name => 'TestAppToTestScripts', helper_class => 'TestHelperClass::False')->run; + } "with argv"; + ok $TestCreateScript::help, 'Did exit with usage into as mk_component returned false'; + is_deeply \@TestHelperClass::ARGS, ['TestAppToTestScripts', 'foo'], 'Args correct'; + is_deeply \%TestHelperClass::p, { '.newfiles' => 1, mech => undef }, 'Params correct'; +} + +done_testing; diff --git a/t/aggregate/unit_core_script_fastcgi.t b/t/aggregate/unit_core_script_fastcgi.t new file mode 100644 index 0000000..b5d3ea4 --- /dev/null +++ b/t/aggregate/unit_core_script_fastcgi.t @@ -0,0 +1,67 @@ +use strict; +use warnings; + +use FindBin qw/$Bin/; +use lib "$Bin/../lib"; + +use Test::More; +use Test::Exception; + +use Catalyst::Script::FastCGI; + +my $testopts; + +# Test default (no opts/args behaviour) +testOption( [ qw// ], [undef, opthash()] ); + +# listen socket +testOption( [ qw|-l /tmp/foo| ], ['/tmp/foo', opthash()] ); +testOption( [ qw/-l 127.0.0.1:3000/ ], ['127.0.0.1:3000', opthash()] ); + +#daemonize -d --daemon +testOption( [ qw/-d/ ], [undef, opthash(detach => 1)] ); +testOption( [ qw/--daemon/ ], [undef, opthash(detach => 1)] ); + +# pidfile -pidfile -p --pid --pidfile +testOption( [ qw/--pidfile cat.pid/ ], [undef, opthash(pidfile => 'cat.pid')] ); +testOption( [ qw/--pid cat.pid/ ], [undef, opthash(pidfile => 'cat.pid')] ); +testOption( [ qw/-p cat.pid/ ], [undef, opthash(pidfile => 'cat.pid')] ); + +# manager +testOption( [ qw/--manager foo::bar/ ], [undef, opthash(manager => 'foo::bar')] ); +testOption( [ qw/-M foo::bar/ ], [undef, opthash(manager => 'foo::bar')] ); + +# keeperr +testOption( [ qw/--keeperr/ ], [undef, opthash(keep_stderr => 1)] ); +testOption( [ qw/-e/ ], [undef, opthash(keep_stderr => 1)] ); + +# nproc +testOption( [ qw/--nproc 6/ ], [undef, opthash(nproc => 6)] ); +testOption( [ qw/--n 6/ ], [undef, opthash(nproc => 6)] ); + +done_testing; + +sub testOption { + my ($argstring, $resultarray) = @_; + + local @ARGV = @$argstring; + local @TestAppToTestScripts::RUN_ARGS; + lives_ok { + Catalyst::Script::FastCGI->new_with_options(application_name => 'TestAppToTestScripts')->run; + } "new_with_options"; + # First element of RUN_ARGS will be the script name, which we don't care about + shift @TestAppToTestScripts::RUN_ARGS; + is_deeply \@TestAppToTestScripts::RUN_ARGS, $resultarray, "is_deeply comparison"; +} + +# Returns the hash expected when no flags are passed +sub opthash { + return { + pidfile => undef, + keep_stderr => undef, + detach => undef, + nproc => undef, + manager => undef, + @_, + }; +} diff --git a/t/aggregate/unit_core_script_help.t b/t/aggregate/unit_core_script_help.t new file mode 100644 index 0000000..0287990 --- /dev/null +++ b/t/aggregate/unit_core_script_help.t @@ -0,0 +1,31 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use Test::More; +use Test::Exception; + +use FindBin qw/$Bin/; +use lib "$Bin/../lib"; + +{ + package TestHelpScript; + use Moose; + with 'Catalyst::ScriptRole'; + our $help; + sub _getopt_full_usage { $help++ } +} + +test('-h'); +test('--help'); +test('-?'); + +sub test { + local $TestHelpScript::help; + local @ARGV = (@_); + lives_ok { + TestHelpScript->new_with_options(application_name => 'TestAppToTestScripts')->run; + } 'Lives'; + ok $TestHelpScript::help, 'Got help'; +} + +done_testing; diff --git a/t/aggregate/unit_core_script_server.t b/t/aggregate/unit_core_script_server.t new file mode 100644 index 0000000..b9ad60b --- /dev/null +++ b/t/aggregate/unit_core_script_server.t @@ -0,0 +1,137 @@ +use strict; +use warnings; + +use FindBin qw/$Bin/; +use lib "$Bin/../lib"; + +use Test::More; +use Test::Exception; + +use Catalyst::Script::Server; + +my $testopts; + +# Test default (no opts/args behaviour) +# Note undef for host means we bind to all interfaces. +testOption( [ qw// ], ['3000', undef, opthash()] ); + +# Old version supports long format opts with either one or two dashes. New version only supports two. +# Old New +# help -? -help --help -? --help +# debug -d -debug --debug -d --debug +# host -host --host --host +testOption( [ qw/--host testhost/ ], ['3000', 'testhost', opthash()] ); +testOption( [ qw/-h testhost/ ], ['3000', 'testhost', opthash()] ); + +# port -p -port --port -l --listen +testOption( [ qw/-p 3001/ ], ['3001', undef, opthash()] ); +testOption( [ qw/--port 3001/ ], ['3001', undef, opthash()] ); +{ + local $ENV{TESTAPPTOTESTSCRIPTS_PORT} = 5000; + testOption( [ qw// ], [5000, undef, opthash()] ); +} +{ + local $ENV{CATALYST_PORT} = 5000; + testOption( [ qw// ], [5000, undef, opthash()] ); +} + +# fork -f -fork --fork -f --fork +testOption( [ qw/--fork/ ], ['3000', undef, opthash(fork => 1)] ); +testOption( [ qw/-f/ ], ['3000', undef, opthash(fork => 1)] ); + +# pidfile -pidfile --pid --pidfile +testOption( [ qw/--pidfile cat.pid/ ], ['3000', undef, opthash(pidfile => "cat.pid")] ); +testOption( [ qw/--pid cat.pid/ ], ['3000', undef, opthash(pidfile => "cat.pid")] ); + +# keepalive -k -keepalive --keepalive -k --keepalive +testOption( [ qw/-k/ ], ['3000', undef, opthash(keepalive => 1)] ); +testOption( [ qw/--keepalive/ ], ['3000', undef, opthash(keepalive => 1)] ); + +# symlinks -follow_symlinks --sym --follow_symlinks +testOption( [ qw/--follow_symlinks/ ], ['3000', undef, opthash(follow_symlinks => 1)] ); +testOption( [ qw/--sym/ ], ['3000', undef, opthash(follow_symlinks => 1)] ); + +# background -background --bg --background +testOption( [ qw/--background/ ], ['3000', undef, opthash(background => 1)] ); +testOption( [ qw/--bg/ ], ['3000', undef, opthash(background => 1)] ); + +# restart -r -restart --restart -R --restart +testRestart( ['-r'], restartopthash() ); +{ + local $ENV{TESTAPPTOTESTSCRIPTS_RELOAD} = 1; + testRestart( [], restartopthash() ); +} +{ + local $ENV{CATALYST_RELOAD} = 1; + testRestart( [], restartopthash() ); +} + +# restart dly -rd -restartdelay --rd --restart_delay +testRestart( ['-r', '--rd', 30], restartopthash(sleep_interval => 30) ); +testRestart( ['-r', '--restart_delay', 30], restartopthash(sleep_interval => 30) ); + +# restart dir -restartdirectory --rdir --restart_directory +testRestart( ['-r', '--rdir', 'root'], restartopthash(directories => ['root']) ); +testRestart( ['-r', '--rdir', 'root', '--rdir', 'lib'], restartopthash(directories => ['root', 'lib']) ); +testRestart( ['-r', '--restart_directory', 'root'], restartopthash(directories => ['root']) ); + +# restart regex -rr -restartregex --rr --restart_regex +testRestart( ['-r', '--rr', 'foo'], restartopthash(filter => qr/foo/) ); +testRestart( ['-r', '--restart_regex', 'foo'], restartopthash(filter => qr/foo/) ); + +done_testing; + +sub testOption { + my ($argstring, $resultarray) = @_; + my $app = _build_testapp($argstring); + lives_ok { + $app->run; + }; + # First element of RUN_ARGS will be the script name, which we don't care about + shift @TestAppToTestScripts::RUN_ARGS; + # Mangle argv into the options.. + $resultarray->[-1]->{argv} = $argstring; + is_deeply \@TestAppToTestScripts::RUN_ARGS, $resultarray, "is_deeply comparison " . join(' ', @$argstring); +} + +sub testRestart { + my ($argstring, $resultarray) = @_; + my $app = _build_testapp($argstring); + ok $app->restart, 'App is in restart mode'; + my $args = {$app->_restarter_args}; + is_deeply delete $args->{argv}, $argstring, 'argv is arg string'; + is ref(delete $args->{start_sub}), 'CODE', 'Closure to start app present'; + is_deeply $args, $resultarray, "is_deeply comparison of restarter args " . join(' ', @$argstring); +} + +sub _build_testapp { + my ($argstring, $resultarray) = @_; + + local @ARGV = @$argstring; + local @TestAppToTestScripts::RUN_ARGS; + my $i; + lives_ok { + $i = Catalyst::Script::Server->new_with_options(application_name => 'TestAppToTestScripts'); + } "new_with_options " . join(' ', @$argstring);; + ok $i; + return $i; +} + +# Returns the hash expected when no flags are passed +sub opthash { + return { + 'pidfile' => undef, + 'fork' => 0, + 'follow_symlinks' => 0, + 'background' => 0, + 'keepalive' => 0, + @_, + }; +} + +sub restartopthash { + return { + follow_symlinks => 0, + @_, + }; +} diff --git a/t/aggregate/unit_core_script_test.t b/t/aggregate/unit_core_script_test.t new file mode 100644 index 0000000..5f56681 --- /dev/null +++ b/t/aggregate/unit_core_script_test.t @@ -0,0 +1,54 @@ +use strict; +use warnings; + +use FindBin qw/$Bin/; +use lib "$Bin/../lib"; + +use Test::More; +use Test::Exception; + +use Catalyst::Script::Test; +use File::Temp qw/tempfile/; +use IO::Handle; + +is run_test('/'), "root index\n", 'correct content printed'; +is run_test('/moose/get_attribute'), "42\n", 'Correct content printed for non root action'; + +done_testing; + +sub run_test { + my $url = shift; + + my ($fh, $fn) = tempfile(); + + binmode( $fh ); + binmode( STDOUT ); + + { + local @ARGV = ($url); + my $i; + lives_ok { + $i = Catalyst::Script::Test->new_with_options(application_name => 'TestApp'); + } "new_with_options"; + ok $i; + my $saved; + open( $saved, '<&'. STDIN->fileno ) + or croak("Can't dup stdin: $!"); + open( STDOUT, '>&='. $fh->fileno ) + or croak("Can't open stdout: $!"); + eval { $i->run }; + ok !$@, 'Ran ok'; + + STDOUT->flush + or croak("Can't flush stdout: $!"); + + open( STDOUT, '>&'. fileno($saved) ) + or croak("Can't restore stdout: $!"); + } + + my $data = do { my $fh; open($fh, '<', $fn) or die $!; local $/; <$fh>; }; + $fh = undef; + unlink $fn if -r $fn; + + return $data; +} diff --git a/t/aggregate/unit_core_scriptrunner.t b/t/aggregate/unit_core_scriptrunner.t new file mode 100644 index 0000000..d9af3bc --- /dev/null +++ b/t/aggregate/unit_core_scriptrunner.t @@ -0,0 +1,24 @@ +use strict; +use warnings; +use Test::More; +use FindBin qw/$Bin/; +use lib "$Bin/../lib"; + +use_ok('Catalyst::ScriptRunner'); + +is Catalyst::ScriptRunner->run('ScriptTestApp', 'Foo'), 'ScriptTestApp::Script::Foo', + 'Script existing only in app'; +is Catalyst::ScriptRunner->run('ScriptTestApp', 'Bar'), 'ScriptTestApp::Script::Bar', + 'Script existing in both app and Catalyst - prefers app'; +is Catalyst::ScriptRunner->run('ScriptTestApp', 'Baz'), 'Catalyst::Script::Baz', + 'Script existing only in Catalyst'; +# +1 test for the params passed to new_with_options in t/lib/Catalyst/Script/Baz.pm +{ + my $warnings = ''; + local $SIG{__WARN__} = sub { $warnings .= shift }; + is 'Catalyst::Script::CompileTest', Catalyst::ScriptRunner->run('ScriptTestApp', 'CompileTest'); + like $warnings, qr/Does not compile/; + like $warnings, qr/Could not load ScriptTestApp::Script::CompileTest - falling back to Catalyst::Script::CompileTest/; +} + +done_testing; diff --git a/t/aggregate/unit_core_setup_log.t b/t/aggregate/unit_core_setup_log.t index 1406944..e2dba17 100644 --- a/t/aggregate/unit_core_setup_log.t +++ b/t/aggregate/unit_core_setup_log.t @@ -8,7 +8,6 @@ use Catalyst (); sub mock_app { my $name = shift; - print "Setting up mock application: $name\n"; my $meta = Moose->init_meta( for_class => $name ); $meta->superclasses('Catalyst'); return $meta->name; diff --git a/t/aggregate/unit_load_catalyst_test.t b/t/aggregate/unit_load_catalyst_test.t index fa8144c..036c3b8 100644 --- a/t/aggregate/unit_load_catalyst_test.t +++ b/t/aggregate/unit_load_catalyst_test.t @@ -3,9 +3,7 @@ use strict; use warnings; -use FindBin; -use lib "$FindBin::Bin/../lib"; -use Test::More tests => 61; +use Test::More; use FindBin qw/$Bin/; use lib "$Bin/../lib"; use Catalyst::Utils; @@ -26,7 +24,7 @@ my %Meth = ( ### make sure we're not trying to connect to a remote host -- these are local tests local $ENV{CATALYST_SERVER}; -use_ok( $Class ); +use Catalyst::Test (); ### check available methods { ### turn of redefine warnings, we'll get new subs exported @@ -155,3 +153,4 @@ lives_ok { request(GET('/dummy'), []); } 'array additional param to request method ignored'; +done_testing; diff --git a/t/aggregate/utf8_content_length.t b/t/aggregate/utf8_content_length.t new file mode 100644 index 0000000..86297e8 --- /dev/null +++ b/t/aggregate/utf8_content_length.t @@ -0,0 +1,30 @@ +use strict; +use warnings; +use FindBin qw/$Bin/; +use lib "$Bin/../lib"; +use File::Spec; +use Test::More; + +use Catalyst::Test qw/TestAppEncoding/; + +if ( $ENV{CATALYST_SERVER} ) { + plan skip_all => 'This test does not run live'; + exit 0; +} + +my $fn = "$Bin/../catalyst_130pix.gif"; +ok -r $fn, 'Can read catalyst_130pix.gif'; +my $size = -s $fn; +{ + my $r = request('/binary'); + is $r->code, 200, '/binary OK'; + is $r->header('Content-Length'), $size, '/binary correct content length'; +} +{ + my $r = request('/binary_utf8'); + is $r->code, 200, '/binary_utf8 OK'; + is $r->header('Content-Length'), $size, '/binary_utf8 correct content length'; +} + +done_testing; + diff --git a/t/author/optional_http-server.t b/t/author/http-server.t similarity index 99% rename from t/author/optional_http-server.t rename to t/author/http-server.t index d4a2183..8f60174 100644 --- a/t/author/optional_http-server.t +++ b/t/author/http-server.t @@ -32,7 +32,7 @@ rmtree '../t/tmp/TestApp/t' or die; # spawn the standalone HTTP server my $port = 30000 + int rand(1 + 10000); my @cmd = ($^X, "-I$FindBin::Bin/../../lib", - "$FindBin::Bin/../../t/tmp/TestApp/script/testapp_server.pl", '-port', $port ); + "$FindBin::Bin/../../t/tmp/TestApp/script/testapp_server.pl", '--port', $port ); my $pid = open3( undef, my $server, undef, @cmd) or die "Unable to spawn standalone HTTP server: $!"; diff --git a/t/author/notabs.t b/t/author/notabs.t new file mode 100644 index 0000000..5cd3ae0 --- /dev/null +++ b/t/author/notabs.t @@ -0,0 +1,10 @@ +use strict; +use warnings; + +use File::Spec; +use FindBin (); +use Test::More; +use Test::NoTabs; + +all_perl_files_ok(qw/lib/); + diff --git a/t/author/pod.t b/t/author/pod.t new file mode 100644 index 0000000..f908f73 --- /dev/null +++ b/t/author/pod.t @@ -0,0 +1,8 @@ +use strict; +use warnings; +use Test::More; + +use Test::Pod 1.14; + +all_pod_files_ok(); + diff --git a/t/author/podcoverage.t b/t/author/podcoverage.t new file mode 100644 index 0000000..e8730de --- /dev/null +++ b/t/author/podcoverage.t @@ -0,0 +1,13 @@ +use strict; +use warnings; +use Test::More; + +use Pod::Coverage 0.19; +use Test::Pod::Coverage 1.04; + +all_pod_coverage_ok( + { + also_private => ['BUILD'] + } +); + diff --git a/t/custom_exception_class_simple.t b/t/custom_exception_class_simple.t index e87ed80..8c8c0c2 100644 --- a/t/custom_exception_class_simple.t +++ b/t/custom_exception_class_simple.t @@ -7,12 +7,6 @@ use lib "$Bin/lib"; use Test::More tests => 1; use Test::Exception; -TODO: { - local $TODO = 'Does not work yet'; - lives_ok { require TestAppClassExceptionSimpleTest; } 'Can load application'; - -} - diff --git a/t/deprecated.t b/t/deprecated.t index b30df89..307181b 100644 --- a/t/deprecated.t +++ b/t/deprecated.t @@ -9,7 +9,10 @@ use Test::More tests => 4; my $warnings; BEGIN { # Do this at compile time in case we generate a warning when use # DeprecatedTestApp - $SIG{__WARN__} = sub { $warnings++ if $_[0] =~ /trying to use NEXT/ }; + $SIG{__WARN__} = sub { + $warnings++ if $_[0] =~ /uses NEXT, which is deprecated/; + $warnings++ if $_[0] =~ /trying to use NEXT, which is deprecated/; + }; } use Catalyst; # Cause catalyst to be used so I can fiddle with the logging. my $mvc_warnings; @@ -36,7 +39,4 @@ is( $mvc_warnings, 1, 'Get the ::MVC:: warning' ); ok( my $response = request('http://localhost/'), 'Request' ); is( $response->header('X-Catalyst-Plugin-Deprecated'), '1', 'NEXT plugin ran correctly' ); -SKIP: { - skip 'non-dev release', 1 unless Catalyst::_IS_DEVELOPMENT_VERSION(); - is( $warnings, 1, 'Got one and only one Adopt::NEXT warning'); -} +is( $warnings, 1, 'Got one and only one Adopt::NEXT warning'); diff --git a/t/lib/Catalyst/Plugin/Test/Deprecated.pm b/t/lib/Catalyst/Plugin/Test/Deprecated.pm index 688ad21..7453248 100644 --- a/t/lib/Catalyst/Plugin/Test/Deprecated.pm +++ b/t/lib/Catalyst/Plugin/Test/Deprecated.pm @@ -2,7 +2,6 @@ package Catalyst::Plugin::Test::Deprecated; use strict; use warnings; -use NEXT; sub prepare { my $class = shift; diff --git a/t/lib/Catalyst/Plugin/Test/Plugin.pm b/t/lib/Catalyst/Plugin/Test/Plugin.pm index 16f3f63..f4f835b 100644 --- a/t/lib/Catalyst/Plugin/Test/Plugin.pm +++ b/t/lib/Catalyst/Plugin/Test/Plugin.pm @@ -4,7 +4,7 @@ use strict; use warnings; use MRO::Compat; -use base qw/Catalyst::Controller Class::Data::Inheritable/; +use base qw/Class::Data::Inheritable/; __PACKAGE__->mk_classdata('ran_setup'); diff --git a/t/lib/Catalyst/Script/Bar.pm b/t/lib/Catalyst/Script/Bar.pm new file mode 100644 index 0000000..18e699c --- /dev/null +++ b/t/lib/Catalyst/Script/Bar.pm @@ -0,0 +1,9 @@ +package Catalyst::Script::Bar; +use Moose; +use namespace::autoclean; + +with 'Catalyst::ScriptRole'; + +sub run { __PACKAGE__ } + +1; diff --git a/t/lib/Catalyst/Script/Baz.pm b/t/lib/Catalyst/Script/Baz.pm new file mode 100644 index 0000000..d699fe6 --- /dev/null +++ b/t/lib/Catalyst/Script/Baz.pm @@ -0,0 +1,16 @@ +package Catalyst::Script::Baz; +use Moose; +use namespace::autoclean; + +use Test::More; + +with 'Catalyst::ScriptRole'; + +sub run { __PACKAGE__ } + +after new_with_options => sub { + my ($self, %args) = @_; + is_deeply \%args, { application_name => 'ScriptTestApp' }, 'App name correct'; +}; + +1; diff --git a/t/lib/Catalyst/Script/CompileTest.pm b/t/lib/Catalyst/Script/CompileTest.pm new file mode 100644 index 0000000..df81247 --- /dev/null +++ b/t/lib/Catalyst/Script/CompileTest.pm @@ -0,0 +1,16 @@ +package Catalyst::Script::CompileTest; +use Moose; +use namespace::autoclean; + +use Test::More; + +with 'Catalyst::ScriptRole'; + +sub run { __PACKAGE__ } + +after new_with_options => sub { + my ($self, %args) = @_; + is_deeply \%args, { application_name => 'ScriptTestApp' }, 'App name correct'; +}; + +1; diff --git a/t/lib/ScriptTestApp/Script/Bar.pm b/t/lib/ScriptTestApp/Script/Bar.pm new file mode 100644 index 0000000..9617441 --- /dev/null +++ b/t/lib/ScriptTestApp/Script/Bar.pm @@ -0,0 +1,9 @@ +package ScriptTestApp::Script::Bar; +use Moose; +use namespace::autoclean; + +with 'Catalyst::ScriptRole'; + +sub run { __PACKAGE__ } + +1; diff --git a/t/lib/ScriptTestApp/Script/CompileTest.pm b/t/lib/ScriptTestApp/Script/CompileTest.pm new file mode 100644 index 0000000..5d4b89c --- /dev/null +++ b/t/lib/ScriptTestApp/Script/CompileTest.pm @@ -0,0 +1,7 @@ +package ScriptTestApp::Script::CompileTest; +use Moose; +use namespace::autoclean; + +die("Does not compile"); + +1; diff --git a/t/lib/ScriptTestApp/Script/Foo.pm b/t/lib/ScriptTestApp/Script/Foo.pm new file mode 100644 index 0000000..8d61c63 --- /dev/null +++ b/t/lib/ScriptTestApp/Script/Foo.pm @@ -0,0 +1,9 @@ +package ScriptTestApp::Script::Foo; +use Moose; +use namespace::autoclean; + +with 'Catalyst::ScriptRole'; + +sub run { __PACKAGE__ } + +1; diff --git a/t/lib/TestApp.pm b/t/lib/TestApp.pm index a2fc0b2..1e4d5c4 100644 --- a/t/lib/TestApp.pm +++ b/t/lib/TestApp.pm @@ -20,7 +20,7 @@ our $VERSION = '0.01'; TestApp->config( name => 'TestApp', root => '/some/dir' ); -if (eval { Class::MOP::load_class('CatalystX::LeakChecker'); 1 }) { +if ($::setup_leakchecker && eval { Class::MOP::load_class('CatalystX::LeakChecker'); 1 }) { with 'CatalystX::LeakChecker'; has leaks => ( diff --git a/t/lib/TestApp/Controller/Action/Chained.pm b/t/lib/TestApp/Controller/Action/Chained.pm index 64de556..cbba762 100644 --- a/t/lib/TestApp/Controller/Action/Chained.pm +++ b/t/lib/TestApp/Controller/Action/Chained.pm @@ -204,6 +204,13 @@ sub return_arg_decoded : Chained('/') PathPart('chained/return_arg_decoded') Arg $c->req->args([ map { decode_entities($_) } @{ $c->req->args }]); } +sub roundtrip_urifor : Chained('/') PathPart('chained/roundtrip_urifor') CaptureArgs(1) {} +sub roundtrip_urifor_end : Chained('roundtrip_urifor') PathPart('') Args(1) { + my ($self, $c) = @_; + # This should round-trip, always - i.e. the uri you put in should come back out. + $c->res->body($c->uri_for($c->action, $c->req->captures, @{$c->req->args}, $c->req->parameters)); + $c->stash->{no_end} = 1; +} sub end :Private { my ($self, $c) = @_; diff --git a/t/lib/TestApp/Controller/Action/Chained/CaptureArgs.pm b/t/lib/TestApp/Controller/Action/Chained/CaptureArgs.pm new file mode 100644 index 0000000..d42ab67 --- /dev/null +++ b/t/lib/TestApp/Controller/Action/Chained/CaptureArgs.pm @@ -0,0 +1,66 @@ +package TestApp::Controller::Action::Chained::CaptureArgs; +use warnings; +use strict; + +use base qw( Catalyst::Controller ); + +# +# This controller build the following patterns of URI: +# /captureargs/*/* +# /captureargs/*/*/edit +# /captureargs/* +# /captureargs/*/edit +# /captureargs/test/* +# It will output the arguments they got passed to @_ after the +# context object. +# /captureargs/one/edit should not dispatch to /captureargs/*/* +# /captureargs/test/one should not dispatch to /captureargs/*/* + +sub base :Chained('/') PathPart('captureargs') CaptureArgs(0) { + my ( $self, $c, $arg ) = @_; + push @{ $c->stash->{ passed_args } }, 'base'; +} + +sub two_args :Chained('base') PathPart('') CaptureArgs(2) { + my ( $self, $c, $arg1, $arg2 ) = @_; + push @{ $c->stash->{ passed_args } }, 'two_args', $arg1, $arg2; +} + +sub one_arg :Chained('base') ParthPart('') CaptureArgs(1) { + my ( $self, $c, $arg ) = @_; + push @{ $c->stash->{ passed_args } }, 'one_arg', $arg; +} + +sub edit_two_args :Chained('two_args') PathPart('edit') Args(0) { + my ( $self, $c ) = @_; + push @{ $c->stash->{ passed_args } }, 'edit_two_args'; +} + +sub edit_one_arg :Chained('one_arg') PathPart('edit') Args(0) { + my ( $self, $c ) = @_; + push @{ $c->stash->{ passed_args } }, 'edit_one_arg'; +} + +sub view_two_args :Chained('two_args') PathPart('') Args(0) { + my ( $self, $c ) = @_; + push @{ $c->stash->{ passed_args } }, 'view_two_args'; +} + +sub view_one_arg :Chained('one_arg') PathPart('') Args(0) { + my ( $self, $c ) = @_; + push @{ $c->stash->{ passed_args } }, 'view_one_arg'; +} + +sub test_plus_arg :Chained('base') PathPart('test') Args(1) { + my ( $self, $c, $arg ) = @_; + push @{ $c->stash->{ passed_args } }, 'test_plus_arg', $arg; +} + + +sub end : Private { + my ( $self, $c ) = @_; + no warnings 'uninitialized'; + $c->response->body( join '; ', @{ $c->stash->{ passed_args } } ); +} + +1; diff --git a/t/lib/TestApp/Controller/Root.pm b/t/lib/TestApp/Controller/Root.pm index 5aa03dc..5b29201 100644 --- a/t/lib/TestApp/Controller/Root.pm +++ b/t/lib/TestApp/Controller/Root.pm @@ -1,5 +1,6 @@ package TestApp::Controller::Root; - +use strict; +use warnings; use base 'Catalyst::Controller'; __PACKAGE__->config->{namespace} = ''; diff --git a/t/lib/TestAppEncoding.pm b/t/lib/TestAppEncoding.pm new file mode 100644 index 0000000..53f50ff --- /dev/null +++ b/t/lib/TestAppEncoding.pm @@ -0,0 +1,11 @@ +package TestAppEncoding; +use strict; +use warnings; +use base qw/Catalyst/; +use Catalyst; + +__PACKAGE__->config(name => __PACKAGE__); +__PACKAGE__->setup; + +1; + diff --git a/t/lib/TestAppEncoding/Controller/Root.pm b/t/lib/TestAppEncoding/Controller/Root.pm new file mode 100644 index 0000000..a8987fb --- /dev/null +++ b/t/lib/TestAppEncoding/Controller/Root.pm @@ -0,0 +1,48 @@ +package TestAppEncoding::Controller::Root; +use strict; +use warnings; +use base 'Catalyst::Controller'; +use Test::More; + +__PACKAGE__->config->{namespace} = ''; + +sub binary : Local { + my ($self, $c) = @_; + $c->res->body(do { + open(my $fh, '<', $c->path_to('..', '..', 'catalyst_130pix.gif')) or die $!; + binmode($fh); + local $/ = undef; <$fh>; + }); +} + +sub binary_utf8 : Local { + my ($self, $c) = @_; + $c->forward('binary'); + my $str = $c->res->body; + utf8::upgrade($str); + ok utf8::is_utf8($str), 'Body is variable width encoded string'; + $c->res->body($str); +} + +# called by t/aggregate/catalyst_test_utf8.t +sub utf8_non_ascii_content : Local { + use utf8; + my ($self, $c) = @_; + + my $str = 'ʇsʎlɐʇɐɔ'; # 'catalyst' flipped at http://www.revfad.com/flip.html + ok utf8::is_utf8($str), '$str is in UTF8 internally'; + + # encode $str into a sequence of octets and turn off the UTF-8 flag, so that + # we don't get the 'Wide character in syswrite' error in Catalyst::Engine + utf8::encode($str); + ok !utf8::is_utf8($str), '$str is a sequence of octets (byte string)'; + + $c->res->body($str); +} + + +sub end : Private { + my ($self,$c) = @_; +} + +1; diff --git a/t/lib/TestAppPluginWithConstructor.pm b/t/lib/TestAppPluginWithConstructor.pm index 5b4b8c1..3d0d552 100644 --- a/t/lib/TestAppPluginWithConstructor.pm +++ b/t/lib/TestAppPluginWithConstructor.pm @@ -4,7 +4,7 @@ use Test::More; use Test::Exception; use Catalyst qw/+TestPluginWithConstructor/; use Moose; -BEGIN { extends qw/Catalyst Catalyst::Controller/ } # Ewww, FIXME. +extends qw/Catalyst/; __PACKAGE__->setup; our $MODIFIER_FIRED = 0; diff --git a/t/lib/TestAppToTestScripts.pm b/t/lib/TestAppToTestScripts.pm new file mode 100644 index 0000000..f32154a --- /dev/null +++ b/t/lib/TestAppToTestScripts.pm @@ -0,0 +1,14 @@ +package TestAppToTestScripts; +use strict; +use warnings; +use Carp; + +our @RUN_ARGS; + +sub run { + @RUN_ARGS = @_; + 1; # Does this work? +} + +1; + diff --git a/t/live_component_controller_context_closure.t b/t/live_component_controller_context_closure.t index 172f91e..767822d 100644 --- a/t/live_component_controller_context_closure.t +++ b/t/live_component_controller_context_closure.t @@ -3,8 +3,8 @@ use warnings; use Test::More; BEGIN { - unless (eval 'use CatalystX::LeakChecker 0.03; 1') { - plan skip_all => 'CatalystX::LeakChecker 0.03 required for this test'; + unless (eval 'use CatalystX::LeakChecker 0.05; 1') { + plan skip_all => 'CatalystX::LeakChecker 0.05 required for this test'; } plan tests => 4; @@ -13,6 +13,8 @@ BEGIN { use FindBin; use lib "$FindBin::Bin/lib"; +BEGIN { $::setup_leakchecker = 1 } + use Catalyst::Test 'TestApp'; { diff --git a/t/live_fork.t b/t/live_fork.t index d10e9d5..1fefc2a 100644 --- a/t/live_fork.t +++ b/t/live_fork.t @@ -26,7 +26,6 @@ plan skip_all => 'Skipping fork tests: no /bin/ls' plan tests => 13; # otherwise { - system: ok(my $result = get('/fork/system/%2Fbin%2Fls'), 'system'); my @result = split /$/m, $result; $result = join q{}, @result[-4..-1]; @@ -37,7 +36,6 @@ plan tests => 13; # otherwise } { - backticks: ok(my $result = get('/fork/backticks/%2Fbin%2Fls'), '`backticks`'); my @result = split /$/m, $result; $result = join q{}, @result[-4..-1]; @@ -49,7 +47,6 @@ plan tests => 13; # otherwise like($result_ref->{result}, qr{\n.*\n}m, 'contains two newlines'); } { - fork: ok(my $result = get('/fork/fork'), 'fork'); my @result = split /$/m, $result; $result = join q{}, @result[-4..-1];