Merge branch captures_and_args_combined
Tomas Doran [Fri, 21 Oct 2011 15:27:37 +0000 (09:27 -0600)]
1  2 
lib/Catalyst.pm
t/aggregate/unit_core_uri_for_action.t

diff --combined lib/Catalyst.pm
@@@ -14,8 -14,8 +14,8 @@@ use Catalyst::Request::Upload
  use Catalyst::Response;
  use Catalyst::Utils;
  use Catalyst::Controller;
 +use Data::OptList;
  use Devel::InnerPackage ();
 -use File::stat;
  use Module::Pluggable::Object ();
  use Text::SimpleTable ();
  use Path::Class::Dir ();
@@@ -28,15 -28,8 +28,15 @@@ use Tree::Simple::Visitor::FindByUID
  use Class::C3::Adopt::NEXT;
  use List::MoreUtils qw/uniq/;
  use attributes;
 +use String::RewritePrefix;
 +use Catalyst::EngineLoader;
  use utf8;
  use Carp qw/croak carp shortmess/;
 +use Try::Tiny;
 +use Plack::Middleware::Conditional;
 +use Plack::Middleware::ReverseProxy;
 +use Plack::Middleware::IIS6ScriptNameFix;
 +use Plack::Middleware::LighttpdScriptNameFix;
  
  BEGIN { require 5.008004; }
  
@@@ -74,17 -67,18 +74,17 @@@ our $GO        = Catalyst::Exception::G
  #maybe we should just make them attributes with a default?
  __PACKAGE__->mk_classdata($_)
    for qw/components arguments dispatcher engine log dispatcher_class
 -  engine_class context_class request_class response_class stats_class
 -  setup_finished/;
 +  engine_loader context_class request_class response_class stats_class
 +  setup_finished _psgi_app loading_psgi_file/;
  
  __PACKAGE__->dispatcher_class('Catalyst::Dispatcher');
 -__PACKAGE__->engine_class('Catalyst::Engine::CGI');
  __PACKAGE__->request_class('Catalyst::Request');
  __PACKAGE__->response_class('Catalyst::Response');
  __PACKAGE__->stats_class('Catalyst::Stats');
  
  # Remember to update this in Catalyst::Runtime as well!
  
 -our $VERSION = '5.80022';
 +our $VERSION = '5.90004';
  
  sub import {
      my ( $class, @arguments ) = @_;
      $meta->superclasses(grep { $_ ne 'Moose::Object' } $meta->superclasses);
  
      unless( $meta->has_method('meta') ){
 -        $meta->add_method(meta => sub { Moose::Meta::Class->initialize("${caller}") } );
 +        if ($Moose::VERSION >= 1.15) {
 +            $meta->_add_meta_method('meta');
 +        }
 +        else {
 +            $meta->add_method(meta => sub { Moose::Meta::Class->initialize("${caller}") } );
 +        }
      }
  
      $caller->arguments( [@arguments] );
@@@ -148,7 -137,7 +148,7 @@@ documentation and tutorials
      use Catalyst qw/-Debug/; # include plugins here as well
  
      ### In lib/MyApp/Controller/Root.pm (autocreated)
 -    sub foo : Global { # called for /foo, /foo/1, /foo/1/2, etc.
 +    sub foo : Chained('/') Args() { # called for /foo, /foo/1, /foo/1/2, etc.
          my ( $self, $c, @args ) = @_; # args are qw/1 2/ for /foo/1/2
          $c->stash->{template} = 'foo.tt'; # set the template
          # lookup something from db -- stash vars are passed to TT
      [% END %]
  
      # called for /bar/of/soap, /bar/of/soap/10, etc.
 -    sub bar : Path('/bar/of/soap') { ... }
 -
 -    # called for all actions, from the top-most controller downwards
 -    sub auto : Private {
 -        my ( $self, $c ) = @_;
 -        if ( !$c->user_exists ) { # Catalyst::Plugin::Authentication
 -            $c->res->redirect( '/login' ); # require login
 -            return 0; # abort request and go immediately to end()
 -        }
 -        return 1; # success; carry on to next action
 -    }
 +    sub bar : Chained('/') PathPart('/bar/of/soap') Args() { ... }
  
      # called after all actions are finished
 -    sub end : Private {
 +    sub end : Action {
          my ( $self, $c ) = @_;
          if ( scalar @{ $c->error } ) { ... } # handle errors
          return if $c->res->body; # already have a response
          $c->forward( 'MyApp::View::TT' ); # render template
      }
  
 -    ### in MyApp/Controller/Foo.pm
 -    # called for /foo/bar
 -    sub bar : Local { ... }
 -
 -    # called for /blargle
 -    sub blargle : Global { ... }
 -
 -    # an index action matches /foo, but not /foo/1, etc.
 -    sub index : Private { ... }
 -
 -    ### in MyApp/Controller/Foo/Bar.pm
 -    # called for /foo/bar/baz
 -    sub baz : Local { ... }
 -
 -    # first Root auto is called, then Foo auto, then this
 -    sub auto : Private { ... }
 -
 -    # powerful regular expression paths are also possible
 -    sub details : Regex('^product/(\w+)/details$') {
 -        my ( $self, $c ) = @_;
 -        # extract the (\w+) from the URI
 -        my $product = $c->req->captures->[0];
 -    }
 -
  See L<Catalyst::Manual::Intro> for additional information.
  
  =head1 DESCRIPTION
@@@ -202,7 -225,7 +202,7 @@@ fully qualify the name by using a unar
          +Fully::Qualified::Plugin::Name
      /;
  
 -Special flags like C<-Debug> and C<-Engine> can also be specified as
 +Special flags like C<-Debug> can also be specified as
  arguments when Catalyst is loaded:
  
      use Catalyst qw/-Debug My::Module/;
@@@ -222,6 -245,13 +222,6 @@@ priority
  This sets the log level to 'debug' and enables full debug output on the
  error screen. If you only want the latter, see L<< $c->debug >>.
  
 -=head2 -Engine
 -
 -Forces Catalyst to use a specific engine. Omit the
 -C<Catalyst::Engine::> prefix of the engine name, i.e.:
 -
 -    use Catalyst qw/-Engine=CGI/;
 -
  =head2 -Home
  
  Forces Catalyst to use a specific home directory, e.g.:
@@@ -235,11 -265,11 +235,11 @@@ the name will be replaced with undersco
  MYAPP_WEB_HOME. If both variables are set, the MYAPP_HOME one will be used.
  
  If none of these are set, Catalyst will attempt to automatically detect the
 -home directory. If you are working in a development envirnoment, Catalyst
 +home directory. If you are working in a development environment, Catalyst
  will try and find the directory containing either Makefile.PL, Build.PL or
  dist.ini. If the application has been installed into the system (i.e.
  you have done C<make install>), then Catalyst will use the path to your
 -application module, without the .pm extension (ie, /foo/MyApp if your
 +application module, without the .pm extension (e.g., /foo/MyApp if your
  application was installed at /foo/MyApp.pm)
  
  =head2 -Log
@@@ -250,15 -280,14 +250,15 @@@ Specifies a comma-delimited list of lo
  
  =head2 -Stats
  
 -Enables statistics collection and reporting. You can also force this setting
 -from the system environment with CATALYST_STATS or <MYAPP>_STATS. The
 -environment settings override the application, with <MYAPP>_STATS having the
 -highest priority.
 +Enables statistics collection and reporting.
  
 -e.g.
 +   use Catalyst qw/-Stats=1/;
  
 -   use Catalyst qw/-Stats=1/
 +You can also force this setting from the system environment with CATALYST_STATS
 +or <MYAPP>_STATS. The environment settings override the application, with
 +<MYAPP>_STATS having the highest priority.
 +
 +Stats are also enabled if L<< debugging |/"-Debug" >> is enabled.
  
  =head1 METHODS
  
@@@ -307,10 -336,9 +307,10 @@@ call to forward
  
  Note that L<< forward|/"$c->forward( $action [, \@arguments ] )" >> implies
  an C<< eval { } >> around the call (actually
 -L<< execute|/"$c->execute( $class, $coderef )" >> does), thus de-fatalizing
 -all 'dies' within the called action. If you want C<die> to propagate you
 -need to do something like:
 +L<< execute|/"$c->execute( $class, $coderef )" >> does), thus rendering all
 +exceptions thrown by the called action non-fatal and pushing them onto
 +$c->error instead. If you want C<die> to propagate you need to do something
 +like:
  
      $c->forward('foo');
      die join "\n", @{ $c->error } if @{ $c->error };
@@@ -335,8 -363,6 +335,8 @@@ or stash it like so
  
  and access it from the stash.
  
 +Keep in mind that the C<end> method used is that of the caller action. So a C<$c-E<gt>detach> inside a forwarded action would run the C<end> method from the original action requested.
 +
  =cut
  
  sub forward { my $c = shift; no warnings 'recursion'; $c->dispatcher->forward( $c, @_ ) }
@@@ -372,7 -398,7 +372,7 @@@ L<reverse|Catalyst::Action/reverse> ret
  when they are invoked within the visited action.  This is different from the
  behavior of L<< forward|/"$c->forward( $action [, \@arguments ] )" >>, which
  continues to use the $c->action object from the caller action even when
 -invoked from the callee.
 +invoked from the called action.
  
  C<< $c->stash >> is kept unchanged.
  
@@@ -400,10 -426,6 +400,10 @@@ with localized C<< $c->action >> and C<
  C<go> escapes the processing of the current request chain on completion, and
  does not return to its caller.
  
 +@arguments are arguments to the final destination of $action. @captures are
 +arguments to the intermediate steps, if any, on the way to the final sub of
 +$action.
 +
  =cut
  
  sub go { my $c = shift; $c->dispatcher->go( $c, @_ ) }
@@@ -717,12 -739,7 +717,12 @@@ sub view 
          unless ( ref($name) ) { # Direct component hash lookup to avoid costly regexps
              my $comps = $c->components;
              my $check = $appclass."::View::".$name;
 -            return $c->_filter_component( $comps->{$check}, @args ) if exists $comps->{$check};
 +            if( exists $comps->{$check} ) {
 +                return $c->_filter_component( $comps->{$check}, @args );
 +            }
 +            else {
 +                $c->log->warn( "Attempted to use view '$check', but does not exist" );
 +            }
          }
          my @result = $c->_comp_search_prefixes( $name, qw/View V/ );
          return map { $c->_filter_component( $_, @args ) } @result if ref $name;
@@@ -826,9 -843,6 +826,9 @@@ sub component 
              return $c->_filter_component( $comp, @args ) if $comp;
          }
  
 +        return
 +            if $c->config->{disable_component_resolution_regex_fallback};
 +
          # This is here so $c->comp( '::M::' ) works
          my $query = ref $name ? $name : qr{$name}i;
  
@@@ -876,7 -890,7 +876,7 @@@ component is constructed
  For example:
  
      MyApp->config({ 'Model::Foo' => { bar => 'baz', overrides => 'me' } });
 -    MyApp::Model::Foo->config({ quux => 'frob', 'overrides => 'this' });
 +    MyApp::Model::Foo->config({ quux => 'frob', overrides => 'this' });
  
  will mean that C<MyApp::Model::Foo> receives the following data when
  constructed:
          overrides => 'me',
      });
  
 +It's common practice to use a Moose attribute
 +on the receiving component to access the config value.
 +
 +    package MyApp::Model::Foo;
 +
 +    use Moose;
 +
 +    # this attr will receive 'baz' at construction time
 +    has 'bar' => (
 +        is  => 'rw',
 +        isa => 'Str',
 +    );
 +
 +You can then get the value 'baz' by calling $c->model('Foo')->bar
 +(or $self->bar inside code in the model).
 +
 +B<NOTE:> you MUST NOT call C<< $self->config >> or C<< __PACKAGE__->config >>
 +as a way of reading config within your code, as this B<will not> give you the
 +correctly merged config back. You B<MUST> take the config values supplied to
 +the constructor and use those instead.
 +
  =cut
  
  around config => sub {
@@@ -995,11 -988,26 +995,11 @@@ sub path_to 
      else { return Path::Class::File->new( $c->config->{home}, @path ) }
  }
  
 -=head2 $c->plugin( $name, $class, @args )
 -
 -Helper method for plugins. It creates a class data accessor/mutator and
 -loads and instantiates the given class.
 -
 -    MyApp->plugin( 'prototype', 'HTML::Prototype' );
 -
 -    $c->prototype->define_javascript_functions;
 -
 -B<Note:> This method of adding plugins is deprecated. The ability
 -to add plugins like this B<will be removed> in a Catalyst 5.81.
 -Please do not use this functionality in new code.
 -
 -=cut
 -
  sub plugin {
      my ( $class, $name, $plugin, @args ) = @_;
  
      # See block comment in t/unit_core_plugin.t
 -    $class->log->warn(qq/Adding plugin using the ->plugin method is deprecated, and will be removed in Catalyst 5.81/);
 +    $class->log->warn(qq/Adding plugin using the ->plugin method is deprecated, and will be removed in a future release/);
  
      $class->_register_plugin( $plugin, 1 );
  
@@@ -1028,9 -1036,6 +1028,9 @@@ Catalyst> line
      MyApp->setup;
      MyApp->setup( qw/-Debug/ );
  
 +B<Note:> You B<should not> wrap this method with method modifiers
 +or bad things will happen - wrap the C<setup_finalize> method instead.
 +
  =cut
  
  sub setup {
      $class->setup_log( delete $flags->{log} );
      $class->setup_plugins( delete $flags->{plugins} );
      $class->setup_dispatcher( delete $flags->{dispatcher} );
 -    $class->setup_engine( delete $flags->{engine} );
 +    if (my $engine = delete $flags->{engine}) {
 +        $class->log->warn("Specifying the engine in ->setup is no longer supported, see Catalyst::Upgrading");
 +    }
 +    $class->setup_engine();
      $class->setup_stats( delete $flags->{stats} );
  
      for my $flag ( sort keys %{$flags} ) {
@@@ -1200,7 -1202,7 +1200,7 @@@ EO
  A hook to attach modifiers to. This method does not do anything except set the
  C<setup_finished> accessor.
  
 -Applying method modifiers to the C<setup> method doesn't work, because of quirky thingsdone for plugin setup.
 +Applying method modifiers to the C<setup> method doesn't work, because of quirky things done for plugin setup.
  
  Example:
  
@@@ -1223,9 -1225,7 +1223,9 @@@ sub setup_finalize 
  
  Constructs an absolute L<URI> object based on the application root, the
  provided path, and the additional arguments and query parameters provided.
 -When used as a string, provides a textual URI.
 +When used as a string, provides a textual URI.  If you need more flexibility
 +than this (i.e. the option to provide relative URIs etc.) see
 +L<Catalyst::Plugin::SmartURI>.
  
  If no arguments are provided, the URI for the current action is returned.
  To return the current action and also provide @args, use
@@@ -1279,11 -1279,13 +1279,11 @@@ sub uri_for 
      carp "uri_for called with undef argument" if grep { ! defined $_ } @args;
      foreach my $arg (@args) {
          utf8::encode($arg) if utf8::is_utf8($arg);
 -    }
 -    s/([^$URI::uric])/$URI::Escape::escapes{$1}/go for @args;
 -    if (blessed $path) { # Action object only.
 -        s|/|%2F|g for @args;
 +        $arg =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
      }
  
      if ( blessed($path) ) { # action object
 +        s|/|%2F|g for @args;
          my $captures = [ map { s|/|%2F|g; $_; }
                          ( scalar @args && ref $args[0] eq 'ARRAY'
                           ? @{ shift(@args) }
          }
  
          my $action = $path;
-         $path = $c->dispatcher->uri_for_action($action, $captures);
+         # ->uri_for( $action, \@captures_and_args, \%query_values? )
+         if( !@args && $action->number_of_args ) {
+             my $expanded_action = $c->dispatcher->expand_action( $action );
+             my $num_captures = $expanded_action->number_of_captures;
+             unshift @args, splice @$captures, $num_captures;
+         }
+        $path = $c->dispatcher->uri_for_action($action, $captures);
          if (not defined $path) {
              $c->log->debug(qq/Can't find uri_for action '$action' @$captures/)
                  if $c->debug;
          $path = '/' if $path eq '';
      }
  
 -    undef($path) if (defined $path && $path eq '');
 -
      unshift(@args, $path);
  
      unless (defined $path && $path =~ s!^/!!) { # in-place strip
@@@ -1619,9 -1631,7 +1627,9 @@@ sub execute 
      push( @{ $c->stack }, $code );
  
      no warnings 'recursion';
 -    eval { $c->state( $code->execute( $class, $c, @{ $c->req->args } ) || 0 ) };
 +    # N.B. This used to be combined, but I have seen $c get clobbered if so, and
 +    #      I have no idea how, ergo $ret (which appears to fix the issue)
 +    eval { my $ret = $code->execute( $class, $c, @{ $c->req->args } ) || 0; $c->state( $ret ) };
  
      $c->_stats_finish_execute( $stats_info ) if $c->use_stats and $stats_info;
  
                  $error = qq/Caught exception in $class->$name "$error"/;
              }
              $c->error($error);
 -            $c->state(0);
          }
 +        $c->state(0);
      }
      return $c->state;
  }
@@@ -1684,7 -1694,7 +1692,7 @@@ sub _stats_start_execute 
          my $parent = $c->stack->[-1];
  
          # forward, locate the caller
 -        if ( exists $c->counter->{"$parent"} ) {
 +        if ( defined $parent && exists $c->counter->{"$parent"} ) {
              $c->stats->profile(
                  begin  => $action,
                  parent => "$parent" . $c->counter->{"$parent"},
@@@ -1811,30 -1821,21 +1819,30 @@@ sub finalize_headers 
  
          if ( !$response->has_body ) {
              # Add a default body if none is already present
 -            $response->body(
 -                qq{<html><body><p>This item has moved <a href="$location">here</a>.</p></body></html>}
 -            );
 +            $response->body(<<"EOF");
 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 +<html xmlns="http://www.w3.org/1999/xhtml"> 
 +  <head>
 +    <title>Moved</title>
 +  </head>
 +  <body>
 +     <p>This item has moved <a href="$location">here</a>.</p>
 +  </body>
 +</html>
 +EOF
 +            $response->content_type('text/html; charset=utf-8');
          }
      }
  
      # Content-Length
 -    if ( $response->body && !$response->content_length ) {
 +    if ( defined $response->body && length $response->body && !$response->content_length ) {
  
          # get the length from a filehandle
 -        if ( blessed( $response->body ) && $response->body->can('read') )
 +        if ( blessed( $response->body ) && $response->body->can('read') || ref( $response->body ) eq 'GLOB' )
          {
 -            my $stat = stat $response->body;
 -            if ( $stat && $stat->size > 0 ) {
 -                $response->content_length( $stat->size );
 +            my $size = -s $response->body;
 +            if ( $size ) {
 +                $response->content_length( $size );
              }
              else {
                  $c->log->warn('Serving filehandle without a content-length');
@@@ -1897,7 -1898,7 +1905,7 @@@ namespaces
  
  sub get_actions { my $c = shift; $c->dispatcher->get_actions( $c, @_ ) }
  
 -=head2 $c->handle_request( $class, @arguments )
 +=head2 $app->handle_request( @arguments )
  
  Called to handle each HTTP request.
  
@@@ -1908,7 -1909,7 +1916,7 @@@ sub handle_request 
  
      # Always expect worst case!
      my $status = -1;
 -    eval {
 +    try {
          if ($class->debug) {
              my $secs = time - $START || 1;
              my $av = sprintf '%.3f', $COUNT / $secs;
          my $c = $class->prepare(@arguments);
          $c->dispatch;
          $status = $c->finalize;
 -    };
 -
 -    if ( my $error = $@ ) {
 -        chomp $error;
 -        $class->log->error(qq/Caught exception in engine "$error"/);
      }
 +    catch {
 +        chomp(my $error = $_);
 +        $class->log->error(qq/Caught exception in engine "$error"/);
 +    };
  
      $COUNT++;
  
      return $status;
  }
  
 -=head2 $c->prepare( @arguments )
 +=head2 $class->prepare( @arguments )
  
  Creates a Catalyst context from an engine-specific request (Apache, CGI,
  etc.).
@@@ -1960,38 -1962,28 +1968,38 @@@ sub prepare 
          $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
      }
  
 -    #XXX reuse coderef from can
 -    # Allow engine to direct the prepare flow (for POE)
 -    if ( $c->engine->can('prepare') ) {
 -        $c->engine->prepare( $c, @arguments );
 -    }
 -    else {
 -        $c->prepare_request(@arguments);
 -        $c->prepare_connection;
 -        $c->prepare_query_parameters;
 -        $c->prepare_headers;
 -        $c->prepare_cookies;
 -        $c->prepare_path;
 -
 -        # Prepare the body for reading, either by prepare_body
 -        # or the user, if they are using $c->read
 -        $c->prepare_read;
 -
 -        # Parse the body unless the user wants it on-demand
 -        unless ( ref($c)->config->{parse_on_demand} ) {
 -            $c->prepare_body;
 +    try {
 +        # Allow engine to direct the prepare flow (for POE)
 +        if ( my $prepare = $c->engine->can('prepare') ) {
 +            $c->engine->$prepare( $c, @arguments );
 +        }
 +        else {
 +            $c->prepare_request(@arguments);
 +            $c->prepare_connection;
 +            $c->prepare_query_parameters;
 +            $c->prepare_headers;
 +            $c->prepare_cookies;
 +            $c->prepare_path;
 +
 +            # Prepare the body for reading, either by prepare_body
 +            # or the user, if they are using $c->read
 +            $c->prepare_read;
 +
 +            # Parse the body unless the user wants it on-demand
 +            unless ( ref($c)->config->{parse_on_demand} ) {
 +                $c->prepare_body;
 +            }
          }
      }
 +    # VERY ugly and probably shouldn't rely on ->finalize actually working
 +    catch {
 +        # failed prepare is always due to an invalid request, right?
 +        $c->response->status(400);
 +        $c->response->content_type('text/plain');
 +        $c->response->body('Bad Request');
 +        $c->finalize;
 +        die $_;
 +    };
  
      my $method  = $c->req->method  || '';
      my $path    = $c->req->path;
@@@ -2151,7 -2143,7 +2159,7 @@@ sub log_request 
          $c->log->debug("Query keywords are: $keywords");
      }
  
 -    $c->log_request_parameters( query => $request->query_parameters, body => $request->body_parameters );
 +    $c->log_request_parameters( query => $request->query_parameters, $request->_has_body ? (body => $request->body_parameters) : () );
  
      $c->log_request_uploads($request);
  }
@@@ -2206,7 -2198,7 +2214,7 @@@ sub log_response_status_line 
  
  =head2 $c->log_response_headers($headers);
  
 -Hook method which can be wrapped by plugins to log the responseheaders.
 +Hook method which can be wrapped by plugins to log the response headers.
  No-op in the default implementation.
  
  =cut
@@@ -2341,11 -2333,11 +2349,11 @@@ sub prepare_write { my $c = shift; $c->
  
  =head2 $c->request_class
  
 -Returns or sets the request class.
 +Returns or sets the request class. Defaults to L<Catalyst::Request>.
  
  =head2 $c->response_class
  
 -Returns or sets the response class.
 +Returns or sets the response class. Defaults to L<Catalyst::Response>.
  
  =head2 $c->read( [$maxlength] )
  
@@@ -2370,12 -2362,7 +2378,12 @@@ Starts the engine
  
  =cut
  
 -sub run { my $c = shift; return $c->engine->run( $c, @_ ) }
 +sub run {
 +  my $app = shift;
 +  $app->engine_loader->needs_psgi_engine_compat_hack ?
 +    $app->engine->run($app, @_) :
 +      $app->engine->run( $app, $app->_finalized_psgi_app, @_ );
 +}
  
  =head2 $c->set_action( $action, $code, $namespace, $attrs )
  
@@@ -2413,7 -2400,8 +2421,7 @@@ sub setup_components 
  
      my $config  = $class->config->{ setup_components };
  
 -    my @comps = sort { length $a <=> length $b }
 -                $class->locate_components($config);
 +    my @comps = $class->locate_components($config);
      my %comps = map { $_ => 1 } @comps;
  
      my $deprecatedcatalyst_component_names = grep { /::[CMV]::/ } @comps;
          # we know M::P::O found a file on disk so this is safe
  
          Catalyst::Utils::ensure_class_loaded( $component, { ignore_loaded => 1 } );
 -
 -        # Needs to be done as soon as the component is loaded, as loading a sub-component
 -        # (next time round the loop) can cause us to get the wrong metaclass..
 -        $class->_controller_init_base_classes($component);
      }
  
      for my $component (@comps) {
              : $class->expand_component_module( $component, $config );
          for my $component (@expanded_components) {
              next if $comps{$component};
 -            $class->_controller_init_base_classes($component); # Also cover inner packages
              $class->components->{ $component } = $class->setup_component($component);
          }
      }
@@@ -2468,8 -2461,7 +2476,8 @@@ sub locate_components 
          %$config
      );
  
 -    my @comps = $locator->plugins;
 +    # XXX think about ditching this sort entirely
 +    my @comps = sort { length $a <=> length $b } $locator->plugins;
  
      return @comps;
  }
@@@ -2490,6 -2482,19 +2498,6 @@@ sub expand_component_module 
  
  =cut
  
 -# FIXME - Ugly, ugly hack to ensure the we force initialize non-moose base classes
 -#         nearest to Catalyst::Controller first, no matter what order stuff happens
 -#         to be loaded. There are TODO tests in Moose for this, see
 -#         f2391d17574eff81d911b97be15ea51080500003
 -sub _controller_init_base_classes {
 -    my ($app_class, $component) = @_;
 -    return unless $component->isa('Catalyst::Controller');
 -    foreach my $class ( reverse @{ mro::get_linear_isa($component) } ) {
 -        Moose::Meta::Class->initialize( $class )
 -            unless find_meta($class);
 -    }
 -}
 -
  sub setup_component {
      my( $class, $component ) = @_;
  
@@@ -2559,171 -2564,114 +2567,171 @@@ Sets up engine
  
  =cut
  
 -sub setup_engine {
 -    my ( $class, $engine ) = @_;
 +sub engine_class {
 +    my ($class, $requested_engine) = @_;
  
 -    if ($engine) {
 -        $engine = 'Catalyst::Engine::' . $engine;
 +    if (!$class->engine_loader || $requested_engine) {
 +        $class->engine_loader(
 +            Catalyst::EngineLoader->new({
 +                application_name => $class,
 +                (defined $requested_engine
 +                     ? (catalyst_engine_class => $requested_engine) : ()),
 +            }),
 +        );
      }
  
 -    if ( my $env = Catalyst::Utils::env_value( $class, 'ENGINE' ) ) {
 -        $engine = 'Catalyst::Engine::' . $env;
 -    }
 +    $class->engine_loader->catalyst_engine_class;
 +}
  
 -    if ( $ENV{MOD_PERL} ) {
 -        my $meta = Class::MOP::get_metaclass_by_name($class);
 +sub setup_engine {
 +    my ($class, $requested_engine) = @_;
  
 -        # create the apache method
 -        $meta->add_method('apache' => sub { shift->engine->apache });
 +    my $engine = do {
 +        my $loader = $class->engine_loader;
  
 -        my ( $software, $version ) =
 -          $ENV{MOD_PERL} =~ /^(\S+)\/(\d+(?:[\.\_]\d+)+)/;
 +        if (!$loader || $requested_engine) {
 +            $loader = Catalyst::EngineLoader->new({
 +                application_name => $class,
 +                (defined $requested_engine
 +                     ? (requested_engine => $requested_engine) : ()),
 +            }),
  
 -        $version =~ s/_//g;
 -        $version =~ s/(\.[^.]+)\./$1/g;
 +            $class->engine_loader($loader);
 +        }
  
 -        if ( $software eq 'mod_perl' ) {
 +        $loader->catalyst_engine_class;
 +    };
  
 -            if ( !$engine ) {
 +    # Don't really setup_engine -- see _setup_psgi_app for explanation.
 +    return if $class->loading_psgi_file;
  
 -                if ( $version >= 1.99922 ) {
 -                    $engine = 'Catalyst::Engine::Apache2::MP20';
 -                }
 +    Class::MOP::load_class($engine);
  
 -                elsif ( $version >= 1.9901 ) {
 -                    $engine = 'Catalyst::Engine::Apache2::MP19';
 -                }
 +    if ($ENV{MOD_PERL}) {
 +        my $apache = $class->engine_loader->auto;
  
 -                elsif ( $version >= 1.24 ) {
 -                    $engine = 'Catalyst::Engine::Apache::MP13';
 -                }
 +        my $meta = find_meta($class);
 +        my $was_immutable = $meta->is_immutable;
 +        my %immutable_options = $meta->immutable_options;
 +        $meta->make_mutable if $was_immutable;
  
 -                else {
 -                    Catalyst::Exception->throw( message =>
 -                          qq/Unsupported mod_perl version: $ENV{MOD_PERL}/ );
 -                }
 +        $meta->add_method(handler => sub {
 +            my $r = shift;
 +            my $psgi_app = $class->psgi_app;
 +            $apache->call_app($r, $psgi_app);
 +        });
  
 -            }
 +        $meta->make_immutable(%immutable_options) if $was_immutable;
 +    }
  
 -            # install the correct mod_perl handler
 -            if ( $version >= 1.9901 ) {
 -                *handler = sub  : method {
 -                    shift->handle_request(@_);
 -                };
 -            }
 -            else {
 -                *handler = sub ($$) { shift->handle_request(@_) };
 -            }
 +    $class->engine( $engine->new );
  
 -        }
 +    return;
 +}
  
 -        elsif ( $software eq 'Zeus-Perl' ) {
 -            $engine = 'Catalyst::Engine::Zeus';
 -        }
 +sub _finalized_psgi_app {
 +    my ($app) = @_;
  
 -        else {
 -            Catalyst::Exception->throw(
 -                message => qq/Unsupported mod_perl: $ENV{MOD_PERL}/ );
 -        }
 +    unless ($app->_psgi_app) {
 +        my $psgi_app = $app->_setup_psgi_app;
 +        $app->_psgi_app($psgi_app);
      }
  
 -    unless ($engine) {
 -        $engine = $class->engine_class;
 -    }
 +    return $app->_psgi_app;
 +}
  
 -    Class::MOP::load_class($engine);
 +sub _setup_psgi_app {
 +    my ($app) = @_;
  
 -    # check for old engines that are no longer compatible
 -    my $old_engine;
 -    if ( $engine->isa('Catalyst::Engine::Apache')
 -        && !Catalyst::Engine::Apache->VERSION )
 -    {
 -        $old_engine = 1;
 -    }
 +    for my $home (Path::Class::Dir->new($app->config->{home})) {
 +        my $psgi_file = $home->file(
 +            Catalyst::Utils::appprefix($app) . '.psgi',
 +        );
  
 -    elsif ( $engine->isa('Catalyst::Engine::Server::Base')
 -        && Catalyst::Engine::Server->VERSION le '0.02' )
 -    {
 -        $old_engine = 1;
 -    }
 +        next unless -e $psgi_file;
  
 -    elsif ($engine->isa('Catalyst::Engine::HTTP::POE')
 -        && $engine->VERSION eq '0.01' )
 -    {
 -        $old_engine = 1;
 -    }
 +        # If $psgi_file calls ->setup_engine, it's doing so to load
 +        # Catalyst::Engine::PSGI. But if it does that, we're only going to
 +        # throw away the loaded PSGI-app and load the 5.9 Catalyst::Engine
 +        # anyway. So set a flag (ick) that tells setup_engine not to populate
 +        # $c->engine or do any other things we might regret.
  
 -    elsif ($engine->isa('Catalyst::Engine::Zeus')
 -        && $engine->VERSION eq '0.01' )
 -    {
 -        $old_engine = 1;
 -    }
 +        $app->loading_psgi_file(1);
 +        my $psgi_app = Plack::Util::load_psgi($psgi_file);
 +        $app->loading_psgi_file(0);
  
 -    if ($old_engine) {
 -        Catalyst::Exception->throw( message =>
 -              qq/Engine "$engine" is not supported by this version of Catalyst/
 -        );
 +        return $psgi_app
 +            unless $app->engine_loader->needs_psgi_engine_compat_hack;
 +
 +        warn <<"EOW";
 +Found a legacy Catalyst::Engine::PSGI .psgi file at ${psgi_file}.
 +
 +Its content has been ignored. Please consult the Catalyst::Upgrading
 +documentation on how to upgrade from Catalyst::Engine::PSGI.
 +EOW
      }
  
 -    # engine instance
 -    $class->engine( $engine->new );
 +    return $app->apply_default_middlewares($app->psgi_app);
 +}
 +
 +=head2 $c->apply_default_middlewares
 +
 +Adds the following L<Plack> middlewares to your application, since they are
 +useful and commonly needed:
 +
 +L<Plack::Middleware::ReverseProxy>, (conditionally added based on the status
 +of your $ENV{REMOTE_ADDR}, and can be forced on with C<using_frontend_proxy>
 +or forced off with C<ignore_frontend_proxy>), L<Plack::Middleware::LighttpdScriptNameFix>
 +(if you are using Lighttpd), L<Plack::Middleware::IIS6ScriptNameFix> (always
 +applied since this middleware is smart enough to conditionally apply itself).
 +
 +Additionally if we detect we are using Nginx, we add a bit of custom middleware
 +to solve some problems with the way that server handles $ENV{PATH_INFO} and
 +$ENV{SCRIPT_NAME}
 +
 +=cut
 +
 +
 +sub apply_default_middlewares {
 +    my ($app, $psgi_app) = @_;
 +
 +    $psgi_app = Plack::Middleware::Conditional->wrap(
 +        $psgi_app,
 +        builder   => sub { Plack::Middleware::ReverseProxy->wrap($_[0]) },
 +        condition => sub {
 +            my ($env) = @_;
 +            return if $app->config->{ignore_frontend_proxy};
 +            return $env->{REMOTE_ADDR} eq '127.0.0.1'
 +                || $app->config->{using_frontend_proxy};
 +        },
 +    );
 +
 +    # If we're running under Lighttpd, swap PATH_INFO and SCRIPT_NAME
 +    # http://lists.scsys.co.uk/pipermail/catalyst/2006-June/008361.html
 +    $psgi_app = Plack::Middleware::LighttpdScriptNameFix->wrap($psgi_app);
 +
 +    # we're applying this unconditionally as the middleware itself already makes
 +    # sure it doesn't fuck things up if it's not running under one of the right
 +    # IIS versions
 +    $psgi_app = Plack::Middleware::IIS6ScriptNameFix->wrap($psgi_app);
 +
 +    return $psgi_app;
 +}
 +
 +=head2 $c->psgi_app
 +
 +Returns a PSGI application code reference for the catalyst application
 +C<$c>. This is the bare application without any middlewares
 +applied. C<${myapp}.psgi> is not taken into account.
 +
 +This is what you want to be using to retrieve the PSGI application code
 +reference of your Catalyst application for use in F<.psgi> files.
 +
 +=cut
 +
 +sub psgi_app {
 +    my ($app) = @_;
 +    return $app->engine->build_psgi_app($app);
  }
  
  =head2 $c->setup_home
@@@ -2816,7 -2764,7 +2824,7 @@@ sub setup_stats 
  =head2 $c->registered_plugins
  
  Returns a sorted list of the plugins which have either been stated in the
 -import list or which have been added via C<< MyApp->plugin(@args); >>.
 +import list.
  
  If passed a given plugin name, it will report a boolean value indicating
  whether or not that plugin is loaded.  A fully qualified name is required if
@@@ -2843,7 -2791,7 +2851,7 @@@ the plugin name does not begin with C<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" )
 +        $class->log->warn( "$plugin inherits from 'Catalyst::Component' - this is deprecated and will not work in 5.81" )
              if $plugin->isa( 'Catalyst::Component' );
          $proto->_plugins->{$plugin} = 1;
          unless ($instant) {
          my ( $class, $plugins ) = @_;
  
          $class->_plugins( {} ) unless $class->_plugins;
 -        $plugins ||= [];
 +        $plugins = Data::OptList::mkopt($plugins || []);
  
 -        my @plugins = Catalyst::Utils::resolve_namespace($class . '::Plugin', 'Catalyst::Plugin', @$plugins);
 +        my @plugins = map {
 +            [ Catalyst::Utils::resolve_namespace(
 +                  $class . '::Plugin',
 +                  'Catalyst::Plugin', $_->[0]
 +              ),
 +              $_->[1],
 +            ]
 +         } @{ $plugins };
  
          for my $plugin ( reverse @plugins ) {
 -            Class::MOP::load_class($plugin);
 -            my $meta = find_meta($plugin);
 +            Class::MOP::load_class($plugin->[0], $plugin->[1]);
 +            my $meta = find_meta($plugin->[0]);
              next if $meta && $meta->isa('Moose::Meta::Role');
  
 -            $class->_register_plugin($plugin);
 +            $class->_register_plugin($plugin->[0]);
          }
  
          my @roles =
 -            map { $_->name }
 -            grep { $_ && blessed($_) && $_->isa('Moose::Meta::Role') }
 -            map { find_meta($_) }
 +            map  { $_->[0]->name, $_->[1] }
 +            grep { blessed($_->[0]) && $_->[0]->isa('Moose::Meta::Role') }
 +            map  { [find_meta($_->[0]), $_->[1]] }
              @plugins;
  
          Moose::Util::apply_all_roles(
  Returns an arrayref of the internal execution stack (actions that are
  currently executing).
  
 +=head2 $c->stats
 +
 +Returns the current timing statistics object. By default Catalyst uses
 +L<Catalyst::Stats|Catalyst::Stats>, but can be set otherwise with
 +L<< stats_class|/"$c->stats_class" >>.
 +
 +Even if L<< -Stats|/"-Stats" >> is not enabled, the stats object is still
 +available. By enabling it with C< $c->stats->enabled(1) >, it can be used to
 +profile explicitly, although MyApp.pm still won't profile nor output anything
 +by itself.
 +
  =head2 $c->stats_class
  
 -Returns or sets the stats (timing statistics) class.
 +Returns or sets the stats (timing statistics) class. L<Catalyst::Stats|Catalyst::Stats> is used by default.
  
  =head2 $c->use_stats
  
 -Returns 1 when stats collection is enabled.  Stats collection is enabled
 -when the -Stats options is set, debug is on or when the <MYAPP>_STATS
 -environment variable is set.
 +Returns 1 when L<< stats collection|/"-Stats" >> is enabled.
  
  Note that this is a static method, not an accessor and should be overridden
  by declaring C<sub use_stats { 1 }> in your MyApp.pm, not by calling C<< $c->use_stats(1) >>.
@@@ -2987,7 -2919,7 +2995,7 @@@ welcome screen
  
  C<parse_on_demand> - The request body (for example file uploads) will not be parsed
  until it is accessed. This allows you to (for example) check authentication (and reject
 -the upload) before actually recieving all the data. See L</ON-DEMAND PARSER>
 +the upload) before actually receiving all the data. See L</ON-DEMAND PARSER>
  
  =item *
  
@@@ -3008,50 -2940,6 +3016,50 @@@ to be shown in hit debug tables in the 
  
  =item *
  
 +C<use_request_uri_for_path> - Controls if the C<REQUEST_URI> or C<PATH_INFO> environment
 +variable should be used for determining the request path. 
 +
 +Most web server environments pass the requested path to the application using environment variables,
 +from which Catalyst has to reconstruct the request base (i.e. the top level path to / in the application,
 +exposed as C<< $c->request->base >>) and the request path below that base.
 +
 +There are two methods of doing this, both of which have advantages and disadvantages. Which method is used
 +is determined by the C<< $c->config(use_request_uri_for_path) >> setting (which can either be true or false).
 +
 +=over
 +
 +=item use_request_uri_for_path => 0
 +
 +This is the default (and the) traditional method that Catalyst has used for determining the path information.
 +The path is generated from a combination of the C<PATH_INFO> and C<SCRIPT_NAME> environment variables.
 +The allows the application to behave correctly when C<mod_rewrite> is being used to redirect requests
 +into the application, as these variables are adjusted by mod_rewrite to take account for the redirect.
 +
 +However this method has the major disadvantage that it is impossible to correctly decode some elements
 +of the path, as RFC 3875 says: "C<< Unlike a URI path, the PATH_INFO is not URL-encoded, and cannot
 +contain path-segment parameters. >>" This means PATH_INFO is B<always> decoded, and therefore Catalyst
 +can't distinguish / vs %2F in paths (in addition to other encoded values).
 +
 +=item use_request_uri_for_path => 1
 +
 +This method uses the C<REQUEST_URI> and C<SCRIPT_NAME> environment variables. As C<REQUEST_URI> is never
 +decoded, this means that applications using this mode can correctly handle URIs including the %2F character
 +(i.e. with C<AllowEncodedSlashes> set to C<On> in Apache).
 +
 +Given that this method of path resolution is provably more correct, it is recommended that you use
 +this unless you have a specific need to deploy your application in a non-standard environment, and you are
 +aware of the implications of not being able to handle encoded URI paths correctly.
 +
 +However it also means that in a number of cases when the app isn't installed directly at a path, but instead
 +is having paths rewritten into it (e.g. as a .cgi/fcgi in a public_html directory, with mod_rewrite in a
 +.htaccess file, or when SSI is used to rewrite pages into the app, or when sub-paths of the app are exposed
 +at other URIs than that which the app is 'normally' based at with C<mod_rewrite>), the resolution of
 +C<< $c->request->base >> will be incorrect.
 +
 +=back
 +
 +=item *
 +
  C<using_frontend_proxy> - See L</PROXY SUPPORT>.
  
  =back
@@@ -3163,8 -3051,6 +3171,8 @@@ Wiki
  
  =head2 L<Catalyst::Test> - The test suite.
  
 +=begin stopwords
 +
  =head1 PROJECT FOUNDER
  
  sri: Sebastian Riedel <sri@cpan.org>
@@@ -3283,8 -3169,6 +3291,8 @@@ random: Roland Lammel <lammel@cpan.org
  
  Robert Sedlacek C<< <rs@474.at> >>
  
 +SpiceMan: Marcel Montes
 +
  sky: Arthur Bergman
  
  szbalint: Balint Szilakszi <szbalint@cpan.org>
@@@ -3299,20 -3183,8 +3307,20 @@@ Will Hawes C<info@whawes.co.uk
  
  willert: Sebastian Willert <willert@cpan.org>
  
 +wreis: Wallace Reis <wallace@reis.org.br>
 +
  Yuval Kogman, C<nothingmuch@woobling.org>
  
 +rainboxx: Matthias Dietrich, C<perl@rainboxx.de>
 +
 +dd070: Dhaval Dhanani <dhaval070@gmail.com>
 +
 +=end stopwords
 +
 +=head1 COPYRIGHT
 +
 +Copyright (c) 2005, the above named PROJECT FOUNDER and CONTRIBUTORS.
 +
  =head1 LICENSE
  
  This library is free software. You can redistribute it and/or modify it under
@@@ -6,9 -6,9 +6,7 @@@ use warnings
  use FindBin;
  use lib "$FindBin::Bin/../lib";
  
--use Test::More;
--
- plan tests => 33;
 -plan tests => 39;
++use Test::More 0.88;
  
  use_ok('TestApp');
  
@@@ -21,6 -21,8 +19,6 @@@ my $private_action = $dispatcher->get_a
                         '/class_forward_test_method'
                       );
  
 -warn $dispatcher->uri_for_action($private_action);
 -
  ok(!defined($dispatcher->uri_for_action($private_action)),
     "Private action returns undef for URI");
  
@@@ -54,21 -56,6 +52,21 @@@ is($dispatcher->uri_for_action($regex_a
     "/action/regexp/foo/123",
     "Regex action interpolates captures correctly");
  
 +my $regex_action_bs = $dispatcher->get_action_by_path(
 +                     '/action/regexp/one_backslashes'
 +                   );
 +
 +ok(!defined($dispatcher->uri_for_action($regex_action_bs)),
 +   "Regex action without captures returns undef");
 +
 +ok(!defined($dispatcher->uri_for_action($regex_action_bs, [ 1, 2, 3 ])),
 +   "Regex action with too many captures returns undef");
 +
 +is($dispatcher->uri_for_action($regex_action_bs, [ 'foo', 123 ]),
 +   "/action/regexp/foo/123.html",
 +   "Regex action interpolates captures correctly");
 +
 +
  #
  #   Index Action
  #
@@@ -143,10 -130,18 +141,18 @@@ is($context->uri_for($chained_action, 
          'http://127.0.0.1/foo/chained/foo2/1/2/end2/3/4?x=5',
          'uri_for_action correct for chained with multiple captures and args' );
  
+     is( $context->uri_for_action( '/action/chained/endpoint2', [1,2,3,4], { x => 5 } ),
+         'http://127.0.0.1/foo/chained/foo2/1/2/end2/3/4?x=5',
+         'uri_for_action correct for chained with multiple captures and args combined' );
      is( $context->uri_for_action( '/action/chained/three_end', [1,2,3], (4,5,6) ),
          'http://127.0.0.1/foo/chained/one/1/two/2/3/three/4/5/6',
          'uri_for_action correct for chained with multiple capturing actions' );
  
+     is( $context->uri_for_action( '/action/chained/three_end', [1,2,3,4,5,6] ),
+         'http://127.0.0.1/foo/chained/one/1/two/2/3/three/4/5/6',
+         'uri_for_action correct for chained with multiple capturing actions and args combined' );
      my $action_needs_two = '/action/chained/endpoint2';
      
      ok( ! defined( $context->uri_for_action($action_needs_two, [1],     (2,3)) ),
      is( $context->uri_for_action($action_needs_two,            [1,2],   (2,3)),
          'http://127.0.0.1/foo/chained/foo2/1/2/end2/2/3',
          'uri_for_action returns correct uri for correct captures' );
-         
+     is( $context->uri_for_action($action_needs_two,            [1,2,2,3]),
+         'http://127.0.0.1/foo/chained/foo2/1/2/end2/2/3',
+         'uri_for_action returns correct uri for correct captures and args combined' );
      ok( ! defined( $context->uri_for_action($action_needs_two, [1,2,3], (2,3)) ),
          'uri_for_action returns undef for too many captures' );
      
          'http://127.0.0.1/foo/chained/foo2/1/2/end2/3',
          'uri_for_action returns uri with lesser args than specified on action' );
  
+     is( $context->uri_for_action($action_needs_two, [1,2,3]),
+         'http://127.0.0.1/foo/chained/foo2/1/2/end2/3',
+         'uri_for_action returns uri with lesser args than specified on action with captures combined' );
      is( $context->uri_for_action($action_needs_two, [1,2],   (3,4,5)),
          'http://127.0.0.1/foo/chained/foo2/1/2/end2/3/4/5',
          'uri_for_action returns uri with more args than specified on action' );
  
+     is( $context->uri_for_action($action_needs_two, [1,2,3,4,5]),
+         'http://127.0.0.1/foo/chained/foo2/1/2/end2/3/4/5',
+         'uri_for_action returns uri with more args than specified on action with captures combined' );
      is( $context->uri_for_action($action_needs_two, [1,''], (3,4)),
          'http://127.0.0.1/foo/chained/foo2/1//end2/3/4',
          'uri_for_action returns uri with empty capture on undef capture' );
  
+     is( $context->uri_for_action($action_needs_two, [1,'',3,4]),
+         'http://127.0.0.1/foo/chained/foo2/1//end2/3/4',
+         'uri_for_action returns uri with empty capture on undef capture and args combined' );
      is( $context->uri_for_action($action_needs_two, [1,2], ('',3)),
          'http://127.0.0.1/foo/chained/foo2/1/2/end2//3',
          'uri_for_action returns uri with empty arg on undef argument' );
  
+     is( $context->uri_for_action($action_needs_two, [1,2,'',3]),
+         'http://127.0.0.1/foo/chained/foo2/1/2/end2//3',
+         'uri_for_action returns uri with empty arg on undef argument and args combined' );
      is( $context->uri_for_action($action_needs_two, [1,2], (3,'')),
          'http://127.0.0.1/foo/chained/foo2/1/2/end2/3/',
          'uri_for_action returns uri with empty arg on undef last argument' );
  
+     is( $context->uri_for_action($action_needs_two, [1,2,3,'']),
+         'http://127.0.0.1/foo/chained/foo2/1/2/end2/3/',
+         'uri_for_action returns uri with empty arg on undef last argument with captures combined' );
      my $complex_chained = '/action/chained/empty_chain_f';
      is( $context->uri_for_action( $complex_chained, [23], (13), {q => 3} ),
          'http://127.0.0.1/foo/chained/empty/23/13?q=3',
          'uri_for_action returns correct uri for chain with many empty path parts' );
+     is( $context->uri_for_action( $complex_chained, [23,13], {q => 3} ),
+         'http://127.0.0.1/foo/chained/empty/23/13?q=3',
+         'uri_for_action returns correct uri for chain with many empty path parts with captures and args combined' );
  
      eval { $context->uri_for_action( '/does/not/exist' ) };
      like $@, qr{^Can't find action for path '/does/not/exist'},
  
  }
  
++done_testing;
++