Skip to content

Commit

Permalink
Item14152: Implemented long forgotten feature compatibility check.
Browse files Browse the repository at this point in the history
Done via @FS_REQUIRED extension's module array. Supports -namespace.
  • Loading branch information
vrurg committed May 2, 2017
1 parent 28f8fd0 commit a6cf48d
Show file tree
Hide file tree
Showing 2 changed files with 212 additions and 17 deletions.
112 changes: 112 additions & 0 deletions UnitTestContrib/test/unit/ExtensionsTests.pm
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ package ExtensionsTests;
use Assert;
use Foswiki::Exception ();

use Foswiki::FeatureSet;

use version 0.77;

use Foswiki::Class;
Expand Down Expand Up @@ -249,8 +251,12 @@ sub test_pluggable_methods {
my $this = shift;

$this->_disableAllCurrentExtensions;

# Generate 3 extensions.
my @ext = $this->_genExtModules(
3,

# First extension body
<<'EXT1',
plugBefore 'Foswiki::ExtensionsTests::SampleClass::testPluggableMethod' => sub {
my $this = shift;
Expand All @@ -260,6 +266,8 @@ plugBefore 'Foswiki::ExtensionsTests::SampleClass::testPluggableMethod' => sub {
$params->{args}[1] = "ext1ArgFromBefore";
};
EXT1

# Second extension body
<<'EXT2',
plugAround 'Foswiki::ExtensionsTests::SampleClass::testPluggableMethod' => sub {
my $this = shift;
Expand All @@ -285,6 +293,8 @@ plugAround 'Foswiki::ExtensionsTests::SampleClass::testPluggableMethod' => sub {
};
EXT2

# Third extension body
<<'EXT3',
plugBefore 'Foswiki::ExtensionsTests::SampleClass::testPluggableMethod' => sub {
my $this = shift;
Expand Down Expand Up @@ -396,6 +406,108 @@ sub test_API_VERSION {
);
}

sub test_FS_REQUIRED {
my $this = shift;

$this->_disableAllCurrentExtensions;

features_provided
GLOBAL_TEST => [ undef, undef, undef ],
-namespace => "ExtensionsTests",
TEST1 => [ undef, undef, undef ],
TEST2 => [ 2.0, 2.99, 4.0 ],
TEST3 => [ 3.1, undef, undef ],
;

my @ext = $this->_genExtModules(
8,

# First extension body
<<'EXT1',
# This will pass
our @FS_REQUIRED = qw(MOO UNICODE);
EXT1

# Second extension
<<'EXT2',
# This must fail
our @FS_REQUIRED = qw(MOO MISSING_FEATURE);
EXT2

# Third extension
<<'EXT3',
# Must pass
our @FS_REQUIRED = qw(MOO -namespace ExtensionsTests TEST1 TEST2);
EXT3

# Fourth extension
<<'EXT4',
# Must fail
our @FS_REQUIRED = qw(-namespace ExtensionsTests TEST1 TEST3);
EXT4

# Fifth extension
<<'EXT5',
# Must fail due to missing namespace name
our @FS_REQUIRED = qw(MOO UNICODE -namespace);
EXT5

# Sixth extension
<<'EXT6',
# Must fail due to no features in namespace
our @FS_REQUIRED = qw(MOO UNICODE -namespace ExtensionsTests);
EXT6

# Seventh extension
<<'EXT7',
# No @FS_REQUIRED, must pass
EXT7

# Eighth extension
<<'EXT8',
# Empty @FS_REQUIRED, must pass
our @FS_REQUIRED = ();
EXT8
);

my @expect = (
undef,
"Inactive or missing features: MISSING_FEATURE",
undef,
"Inactive or missing features: TEST3 from namespace ExtensionsTests",
'Incomplete @FS_REQUIRED: no name defined for the last -namespace',
'Incomplete @FS_REQUIRED: empty list of features for -namespace ExtensionsTests',
undef,
undef,
);

$this->reCreateFoswikiApp;

my $exts = $this->app->extensions;

foreach my $i ( 0 .. 7 ) {
my $expect = defined( $expect[$i] ) ? "disabled" : "enabled";
my $got = $exts->extEnabled( $ext[$i] ) ? "enabled" : "disabled";
$this->assert_equals( $expect, $got,
"Module #"
. ( $i + 1 ) . " is "
. $got
. " but expected to be "
. $expect );
if ( $expect[$i] ) {
my $reason = $exts->disabledExtensions->{ $ext[$i] };
$this->assert_equals( $expect[$i], $reason,
"Module #"
. ( $i + 1 )
. " has failed as expected but with wrong reason:"
. "\n * Expected: "
. $expect[$i]
. "\n * Got : "
. $reason );
}
}
}

sub test_subClassing {
my $this = shift;

Expand Down
117 changes: 100 additions & 17 deletions core/lib/Foswiki/Extensions.pm
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use Assert;
use Try::Tiny;
use Data::Dumper;
use Foswiki::Exception;
use Foswiki::FeatureSet qw(featuresComply);

# Constants for topological sorting.
use constant NODE_TEMP_MARK => 0;
Expand Down Expand Up @@ -191,28 +192,110 @@ sub isBadVersion {
unless $extName->isa('Foswiki::Extension');

my @apiScalar = grep { /::API_VERSION$/ } Devel::Symdump->scalars($extName);
my @featArray = grep { /::FS_REQUIRED$/ } Devel::Symdump->arrays($extName);
my $checkAPI = 1;

return "No \$API_VERSION scalar defined in $extName"
unless @apiScalar;
unless ( @apiScalar || @featArray ) {
return
"Neither \$API_VERSION nor \@FS_REQUIRED are defined in $extName";
}

if (@featArray) {

# When @FS_REQUIRED is present we don't check API_VERSION as demand of
# features is more specific and more precise.
my @fs_required = Foswiki::fetchGlobal( '@' . $featArray[0] );

my $api_ver = Foswiki::fetchGlobal( '$' . $apiScalar[0] );
if (@fs_required) {

# As we have a list of required features API check can be skipped.
$checkAPI = 0;

my (
@nameSpaceParam, @fList, @nonComplyList,
$errMsg, $failOnEmptyList
);
my $comply = 1;

# Closure with all its access to internals is better solution than a
# separate sub.
my $checkFList = sub {
if (@fList) {
$comply = featuresComply(
-features => \@fList,
-inactive => \@nonComplyList,
@nameSpaceParam
);
unless ($comply) {
$errMsg =
"Inactive or missing features: "
. join( ", ", @nonComplyList )
. (
@nameSpaceParam
? " from namespace $nameSpaceParam[1]"
: ""
);
}
@fList = ();
}
elsif ($failOnEmptyList) {
$comply = 0;
$errMsg =
"Incomplete \@FS_REQUIRED: empty list of features";
if (@nameSpaceParam) {
$errMsg .= " for -namespace $nameSpaceParam[1]";
}
}

return "Failed to fetch \$API_VERSION"
unless defined $api_ver;
return $comply;
};

return
"Declared API version "
. $api_ver
. " is lower than supported "
. $MIN_VERSION
if $api_ver < $MIN_VERSION;
while ( $comply && @fs_required ) {
my $item = shift @fs_required;
if ( $item =~ /^-namespace$/ ) {
( $comply = $checkFList->() ) or next;
unless (@fs_required) {
$errMsg =
"Incomplete \@FS_REQUIRED: no name defined for the last -namespace";
$comply = 0;
next;
}
@nameSpaceParam = ( -namespace => shift @fs_required );
$failOnEmptyList = 1;
}
else {
push @fList, $item;
}
}

# Check features of the last -namespace unless already failed.
$comply &&= $checkFList->();

return $errMsg unless $comply;
}
}

if ( $checkAPI && @apiScalar ) {
my $api_ver = Foswiki::fetchGlobal( '$' . $apiScalar[0] );

return "Failed to fetch \$API_VERSION"
unless defined $api_ver;

return
"Declared API version "
. $api_ver
. " is lower than supported "
. $MIN_VERSION
if $api_ver < $MIN_VERSION;

return
"Declared API version "
. $api_ver
. " is higher than supported "
. $VERSION
if $api_ver > $VERSION;
}

return
"Declared API version "
. $api_ver
. " is higher than supported "
. $VERSION
if $api_ver > $VERSION;
return '';
}

Expand Down

0 comments on commit a6cf48d

Please sign in to comment.