Skip to content

Commit

Permalink
Item13897: First working test in unit test!
Browse files Browse the repository at this point in the history
Yes, QueryTests does it! Don't ask why is this particular one – it was just
small enough start with... Yet, not sure if successfull pass isn't caused
by a hidden bug.

- Added Unit::TestApp class to avoid polluting Foswiki::App with test-only
related code.

- Added Foswiki::Object::Parent and *::Child classes. They were spawned as
a side-effect of a nonviable idea and are not used; but as they were
planned for some future development I decided have them included now.

- Foswiki::Aux::Localize can behaviour can now be tuned by additional
flags. The flags are set using setLocalizeFlags() method provided by a
class with Localize role. Currently the only flag supported is
clearAttributes which indicates whether attributes of the object being
localized must be cleared or left alone. Used by FoswikiTestCase pushApp
method.

- New Foswiki::Object feature – object cloning. As any cloning it has it's
problems and not every clone is the exact copy of the original – yet it
tries to do its best. Especially when it's going about rather simple
classes like Foswiki::Config. An object may provide additional aid to the
process by declaring _clone_attrName() methods where attrName is the
attribute name to be cloned. These methods must return a cloned copy of
attrName attribute.

- Foswiki::AppObject now declares the app attribute as protected.

- Added Foswiki::AppObject _clone_app() method to support object cloning.

- Added new concept to FoswikiTestCase – push/pop of application object.
It's developed as a replacement for the old style code when a request
object is preserved -> new temporary $session is created -> some code
executed -> new semi-permanent $session is created and the original request
object is restored. For now we just pushApp -> do some code -> popApp.

Note that createNewFoswikiApp() and popApp() call _fixupAppObject() to set
all attributes of the current test case object which are descendants of
Foswiki::App to have their app attribute point to the currently active
application. Generally it shall be transparent to the end user. But if in
some case a new application object is created and set active by a test case
it must call _fixupAppObject() too.
  • Loading branch information
vrurg committed May 17, 2016
1 parent 352bc98 commit 353f23d
Show file tree
Hide file tree
Showing 16 changed files with 605 additions and 69 deletions.
79 changes: 79 additions & 0 deletions UnitTestContrib/lib/Unit/TestApp.pm
@@ -0,0 +1,79 @@
# See bottom of file for license and copyright

package Unit::TestApp;
use v5.14;

use Scalar::Util qw(blessed);

use Moo;
use namespace::clean;
extends qw(Foswiki::App);

#with qw(Foswiki::Aux::Localize);

#sub setLocalizableAttributes {
# return
# qw(
# access attach cache cfg env forms
# logger engine heap i18n plugins prefs
# renderer request _requestParams response
# search store templates macros context
# ui remoteUser user users zones _dispatcherAttrs
# )
# ;
#}

sub BUILD {
my $this = shift;

# Fixup Foswiki::AppObject descendants which have been cloned from objects
# on another Foswiki::App instance.
foreach my $attr ( keys %$this ) {
if (
blessed( $this->{$attr} )
&& $this->$attr->isa('Foswiki::Object')
&& $this->$attr->does('Foswiki::AppObject')
&& ( !defined( $this->$attr->app )
|| ( $this->$attr->app != $this ) )
)
{
$this->$attr->_set_app($this);
}
}
}

=begin TML
---++ ObjectMethod cloneEnv => \%envHash
Clones current application =env= hash.
=cut

sub cloneEnv {
my $this = shift;

# SMELL Use Foswiki::Object internals.
return $this->_cloneData( $this->env, 'env' );
}

1;

__DATA__
Author: Crawford Currie, http://c-dot.co.uk
Copyright (C) 2007-2016 Foswiki Contributors
All Rights Reserved.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version. For
more details read LICENSE in the root of this distribution.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
As per the GPL, removal of this notice is prohibited.
2 changes: 1 addition & 1 deletion UnitTestContrib/lib/Unit/TestCase.pm
Expand Up @@ -749,7 +749,7 @@ __DATA__
Author: Crawford Currie, http://c-dot.co.uk
Copyright (C) 2008-2010 Foswiki Contributors
Copyright (C) 2008-2016 Foswiki Contributors
Additional copyrights apply to some or all of the code in this file
as follows:
Expand Down
2 changes: 1 addition & 1 deletion UnitTestContrib/lib/Unit/TestRunner.pm
Expand Up @@ -701,7 +701,7 @@ __DATA__
Author: Crawford Currie, http://c-dot.co.uk
Copyright (C) 2007-2013 Foswiki Contributors
Copyright (C) 2007-2016 Foswiki Contributors
All Rights Reserved.
This program is free software; you can redistribute it and/or
Expand Down
4 changes: 2 additions & 2 deletions UnitTestContrib/test/bin/TestRunner.pl
Expand Up @@ -8,7 +8,7 @@
use FindBin;
use Cwd ();
use File::Path ();
require Foswiki::App;
require Unit::TestApp;
my $starting_root;

sub _findRelativeTo {
Expand Down Expand Up @@ -73,7 +73,7 @@ BEGIN
$env->{FOSWIKI_ACTION} =
'view'; # SMELL Shan't we add a 'test' action to the SwitchBoard?
$env->{FOSWIKI_ENGINE} = 'Foswiki::Engine::Test';
$app = Foswiki::App->new( env => $env );
$app = Unit::TestApp->new( env => $env );
$cfg = $app->cfg;
}
catch {
Expand Down
8 changes: 5 additions & 3 deletions UnitTestContrib/test/unit/FoswikiFnTestCase.pm
Expand Up @@ -207,7 +207,8 @@ Can be used by subclasses to register test users.

sub registerUser {
my ( $this, $loginname, $forename, $surname, $email ) = @_;
my $q = $this->app->request;

$this->pushApp;

my $reqParams = {
'TopicName' => ['UserRegistration'],
Expand Down Expand Up @@ -274,8 +275,9 @@ sub registerUser {
};

# Reload caches
$this->createNewFoswikiApp( undef, $q );
$this->app->net->setMailHandler( \&FoswikiFnTestCase::sentMail );
#$this->createNewFoswikiApp( requestParams => $q );
#$this->app->net->setMailHandler( \&FoswikiFnTestCase::sentMail );
$this->popApp;
}

1;
93 changes: 80 additions & 13 deletions UnitTestContrib/test/unit/FoswikiTestCase.pm
@@ -1,4 +1,4 @@
# See botto . "/working"m of file for license and copyright
# See bottom of file for license and copyright

package FoswikiTestCase;

Expand All @@ -19,6 +19,7 @@ you can always create a new web based on that web.
use Assert;

use Data::Dumper;
use Scalar::Util qw(blessed);

use Foswiki();
use Foswiki::Meta();
Expand All @@ -42,21 +43,23 @@ our $didOnlyOnceChecks = 0;
use Moo;
use namespace::clean;
extends qw(Unit::TestCase);
with qw(Foswiki::Aux::Localize);

has app => (
is => 'rw',
lazy => 1,
predicate => 1,
clearer => 1,
isa => Foswiki::Object::isaCLASS( 'app', 'Foswiki::App', noUndef => 1, ),
isa => Foswiki::Object::isaCLASS( 'app', 'Unit::TestApp', noUndef => 1, ),
default => sub {
if ( defined $Foswiki::app ) {
return $Foswiki::app;
}
return Foswiki::App->new( env => \%ENV );
return Unit::TestApp->new( env => \%ENV );
},
);
has twiki => ( is => 'rw', lazy => 1, default => sub { $_[0]->app }, );
has twiki =>
( is => 'rw', clearer => 1, lazy => 1, default => sub { $_[0]->app }, );
has test_topicObject => (
is => 'rw',
clearer => 1,
Expand All @@ -79,6 +82,8 @@ has request => ( is => 'rw', weak_ref => 1, );
has test_web => ( is => 'rw', );
has test_topic => ( is => 'rw', );

has _holderStack => ( is => 'rw', lazy => 1, default => sub { [] }, );

has __EnvSafe => (
is => 'rw',
lazy => 1,
Expand Down Expand Up @@ -597,7 +602,7 @@ Get an unloaded topic object.
Equivalent to Foswiki::Meta->new, we take the app from $this->app.
That assumes all the tests are playing nice, and aren't doing Foswiki::App->new()
That assumes all the tests are playing nice, and aren't doing Unit::TestApp->new()
themselves (using createNewFoswikiApp instead).
=cut
Expand Down Expand Up @@ -758,13 +763,16 @@ s/((\$Foswiki::cfg\{.*?\})\s*=.*?;)(?:\n|$)/push(@moreConfig, $1) unless (eval "
# Force completion of %Foswiki::cfg
# This must be done before moving the logging.
$Foswiki::cfg{Store}{Implementation} = 'Foswiki::Store::PlainFile';
my $tmp = Foswiki::App->new(
$this->pushApp;
my $tmp = Unit::TestApp->new(
user => undef,
env => $this->app->cloneEnv,
cfg => $this->app->cfg->clone,
);
ASSERT( defined $Foswiki::app ) if SINGLE_SINGLETONS;
undef $tmp; # finish() will be called automatically.
ASSERT( !defined $Foswiki::app ) if SINGLE_SINGLETONS;
$this->popApp;

# Note this does not do much, except for some tests that use it directly.
# The first call to File::Temp caches the temp directory name, so
Expand Down Expand Up @@ -804,7 +812,7 @@ around tear_down => sub {
my $this = shift;

if ( $this->has_app ) {
ASSERT( $this->app->isa('Foswiki::App') ) if SINGLE_SINGLETONS;
ASSERT( $this->app->isa('Unit::TestApp') ) if SINGLE_SINGLETONS;
$this->finishFoswikiSession;
}
$this->_clear_tempDir;
Expand Down Expand Up @@ -983,16 +991,16 @@ sub captureWithKey {
# otherwise take $Foswiki::app
# and we fallback to the one from the test object
my $fatwilly;
if ( UNIVERSAL::isa( $_[1], 'Foswiki::App' ) ) {
if ( UNIVERSAL::isa( $_[1], 'Unit::TestApp' ) ) {
$fatwilly = $_[1];
}
elsif ( UNIVERSAL::isa( $Foswiki::app, 'Foswiki::App' ) ) {
elsif ( UNIVERSAL::isa( $Foswiki::app, 'Unit::TestApp' ) ) {
$fatwilly = $Foswiki::app;
}
else {
$fatwilly = $this->twiki;
}
$this->assert( $fatwilly->isa('Foswiki::App'),
$this->assert( $fatwilly->isa('Unit::TestApp'),
"Could not find the Foswiki object" );

# Now we have to manually craft the validation checkings
Expand Down Expand Up @@ -1057,7 +1065,7 @@ sub getUIFn {

=begin TML
---++ ObjectMethod createNewFoswikiApp(user => $user, request => $query, @params) -> ref to new Foswiki::App obj
---++ ObjectMethod createNewFoswikiApp(user => $user, request => $query, @params) -> ref to new Unit::TestApp obj
cleans up the existing Foswiki object, and creates a new one
Expand All @@ -1077,10 +1085,9 @@ sub createNewFoswikiApp {
$Foswiki::cfg{Store}{Implementation} ||= 'Foswiki::Store::PlainFile';

$params{env} //= $this->app->cloneEnv;
my $app = Foswiki::App->new(%params);
my $app = Unit::TestApp->new( cfg => $this->app->cfg->clone, %params );
$this->app($app);
$this->request( $this->app->request );
$Foswiki::cfg{Register}{EnableNewUserRegistration} = 12;
ASSERT( defined $Foswiki::app ) if SINGLE_SINGLETONS;

if ( $this->test_web && $this->test_topic ) {
Expand All @@ -1089,6 +1096,8 @@ sub createNewFoswikiApp {
[0] );
}

$this->_fixupAppObjects;

return $this->app;
}

Expand Down Expand Up @@ -1153,6 +1162,64 @@ sub toSiteCharSet {
Encode::FB_CROAK # should never happen
);
}

sub setLocalizableAttributes {
return qw(app twiki test_topicObject);
}

around setLocalizeFlags => sub {
my $orig = shift;
my $flags = $orig->(@_);

# Don't clean app on localizing as we might need it until the new one is
# created.
$flags->{clearAttributes} = 0;

return $flags;
};

# Correct all Foswiki::AppObject to use currently active Foswiki::App object.
# SMELL Hacky but shall be transparent for any derived test case class.
sub _fixupAppObjects {
my $this = shift;

my $app = $this->app;

foreach my $attr ( keys %$this ) {
if (
blessed( $this->{$attr} )
&& $this->$attr->isa('Foswiki::Object')
&& $this->$attr->does('Foswiki::AppObject')
&& ( !defined( $this->$attr->app )
|| ( $this->$attr->app != $app ) )
)
{
$this->$attr->_set_app($app);
}
}
}

sub pushApp {
my $this = shift;

my $holderObj = $this->localize(@_);

push @{ $this->_holderStack }, $holderObj;
}

sub popApp {
my $this = shift;

ASSERT( @{ $this->_holderStack } > 0, "Empty stack of holder objects" )
if DEBUG;

pop @{ $this->_holderStack };

$Foswiki::app = $this->app;
$this->app->cfg->_setupGLOBs;
$this->_fixupAppObjects;
}

1;
__DATA__
Expand Down
3 changes: 3 additions & 0 deletions core/lib/Foswiki.pm
Expand Up @@ -43,6 +43,7 @@ with CGI accelerators such as mod_perl.
=cut

use Cwd qw( abs_path );
use Module::Load;
use File::Spec ();
use Monitor ();
use CGI (); # Always required to get html generation tags;
Expand Down Expand Up @@ -968,6 +969,8 @@ sub _package_defined {
return $pkgLoaded;
}

# SMELL Wouldn't it be more reliable to use Module::Load? Or Class::Load? Though
# the latter requires additional CPAN module installed.
sub load_package {
my $fullname = shift;
my %params = @_;
Expand Down
17 changes: 1 addition & 16 deletions core/lib/Foswiki/App.pm
Expand Up @@ -178,7 +178,7 @@ has request => (

# _requestParams hash is used to initialize a new request object.
has _requestParams => (
is => 'ro',
is => 'rwp',
init_arg => 'requestParams',
lazy => 1,
default => sub { {} },
Expand Down Expand Up @@ -591,21 +591,6 @@ sub create {

=begin TML
---++ ObjectMethod cloneEnv => \%envHash
Clones current application =env= hash.
=cut

sub cloneEnv {
my $this = shift;

# SMELL Some smarter method must be used here.
return \%{ $this->env };
}

=begin TML
---++ ObjectMethod deepWebList($filter, $web) -> @list
Deep list subwebs of the named web. $filter is a Foswiki::WebFilter
Expand Down

0 comments on commit 353f23d

Please sign in to comment.