libimporter-perl/t/units.t

1084 lines
32 KiB
Perl
Raw Permalink Normal View History

2022-09-02 13:37:03 +08:00
use strict;
use warnings;
use Importer 'Test::More';
my $CLASS = 'Importer';
sub dies(&) {
my $code = shift;
my $err;
{
local $@;
eval { $code->(); 1 } and return undef;
$err = $@;
}
$@ = $err;
return $err || 1;
}
sub warns(&) {
my $code = shift;
my $warn;
my $warned = 0;
local $SIG{__WARN__} = sub { ($warn) = @_; $warned++ };
$code->();
return undef unless $warned;
return $warn || $warned;
}
###
# These cannot be inside a subtest because of the lexical scopeing
{
my %CARP;
use Importer Carp => qw/croak confess/, sub {
my ($name, $ref) = @_;
$CARP{$name} = $ref;
};
is_deeply(
\%CARP,
{
croak => \&Carp::croak,
confess => \&Carp::confess,
},
"Exports went into the hash."
);
}
subtest _version_check => sub {
my $version_check = $CLASS->can('_version_check');
ok($version_check->('Importer', [__PACKAGE__, __FILE__, __LINE__], 0.001), "Version check pass" );
my $error = dies { $version_check->('Importer', [__PACKAGE__, __FILE__, __LINE__], 100) };
my $line = __LINE__ - 1;
my $file = __FILE__;
like(
$error,
qr/version 100 required.*at \Q$file\E line $line/,
"Got expected error"
);
};
subtest import => sub {
ok(!dies { $CLASS->import('0.001') }, "No errors, valid version");
like(dies { $CLASS->import('100') }, qr/version 100 required/, "bad version check");
package Consumer1;
use Importer 'Data::Dumper' => 'Dumper';
::can_ok(__PACKAGE__, 'Dumper');
};
subtest import_into => sub {
$CLASS->import_into('Data::Dumper', 'Consumer2', 'Dumper');
can_ok('Consumer2', 'Dumper');
my $do_it = sub { $CLASS->import_into('Data::Dumper', 0, 'Dumper') };
package Consumer3;
$do_it->();
::can_ok('Consumer3', 'Dumper');
};
subtest unimport => sub {
package Consumer1;
$CLASS->unimport;
::ok(!__PACKAGE__->can('Dumper'), "removed 'Dumper' from Consumer1");
::like(
::dies { $CLASS->unimport('foo') },
qr/Sub 'foo' was not imported using Importer/,
"we did not import it, we cannot remove it"
);
package Consumer100;
::like(
::dies { $CLASS->unimport },
qr/'Consumer100' does not have any imports to remove/,
"nothing to unimport"
);
};
subtest unimport_from => sub {
$CLASS->unimport_from('Consumer2', 'Dumper');
ok(!Consumer2->can('Dumper'), "removed 'Dumper' from Consumer2");
like(
dies { $CLASS->unimport_from('Consumer100') },
qr/'Consumer100' does not have any imports to remove/,
"Nothing to unimport"
);
my $do_it = sub { $CLASS->unimport_from(0, 'Dumper') };
my $do_it2 = sub { $CLASS->unimport_from(0, 'foo') };
package Consumer3;
$do_it->();
::ok(!Consumer3->can('Dumper'), "removed 'Dumper' from Consumer3");
::like(
::dies { $do_it2->() },
qr/Sub 'foo' was not imported using Importer/,
"we did not import it, we cannot remove it"
);
};
subtest new_and_from => sub {
my $one = $CLASS->new(from => 'Data::Dumper');
isa_ok($one, $CLASS);
is($one->from, 'Data::Dumper', "Saved 'from' from constructor");
like(
dies { $CLASS->new() },
qr/You must specify a package to import from/,
"'from' is a required attribute"
);
$one = $CLASS->new(from => 'Data::Dumper', caller => ['Foo::Bar', 'Foo/Bar.pm', 42]);
is_deeply($one->get_caller, ['Foo::Bar', 'Foo/Bar.pm', 42], "Saved caller from construction");
};
subtest from_file => sub {
my $one = $CLASS->new(from => 'Foo::Bar::Baz');
is($one->from_file, 'Foo/Bar/Baz.pm', "got filename");
};
subtest load_from => sub {
my $one = $CLASS->new(from => 'Some::Fake::Module::AFSGEWGWE::FASDF', caller => ['main', 'fake.pl', 42]);
like(
dies { $one->load_from },
qr{Can't locate.*at fake\.pl line 42},
"Failed to load 'from' module",
);
$INC{$one->from_file} = 1;
ok(!dies { $one->load_from }, "file already loaded");
ok(!$INC{'Test/Simple.pm'}, "check that our test file is not already loaded");
$one = $CLASS->new(from => 'Test::Simple');
ok(!dies { $one->load_from }, "file found");
};
subtest get_caller => sub {
my $one = $CLASS->new(from => 'Fake', caller => ['A', 'A.pm', 42]);
is_deeply($one->get_caller, ['A', 'A.pm', 42], "got stored caller");
$one = $CLASS->new(from => 'Fake');
is_deeply([@{sub { $one->get_caller }->()}[0,1,2]], [__PACKAGE__, __FILE__, __LINE__], "got real caller");
my $get = sub {
package Importer;
sub {
package Importer::Subclass;
use base 'Importer';
sub {
package main; # get_caller loosk at level 1+, so this gets skipped by design
$one->get_caller;
}->()
}->()
};
is_deeply([@{$get->()}[0,1,2]], [__PACKAGE__, __FILE__, __LINE__], "got true caller");
};
subtest carp_and_croak => sub {
my $one = $CLASS->new(from => 'fake', caller => ['A', 'A.pm', 42]);
is(
dies { $one->croak("apple pie") },
"apple pie at A.pm line 42.\n",
"Died at correct place"
);
is(
warns { $one->carp("apple pie") },
"apple pie at A.pm line 42.\n",
"Warned at correct place"
);
};
subtest menu => sub {
my $menu;
no warnings 'redefine';
local *Importer::reload_menu = sub {
my $self = shift;
my ($into) = @_;
$self->{menu} = $menu;
$self->{menu_for} = $into;
return $menu;
};
$menu = { a => 1 };
my $one = $CLASS->new(from => 'fake');
is_deeply($one->menu('fake2'), $menu, "returned menu");
my $old = $menu;
$menu = { b => 2 };
is_deeply($one->menu('fake2'), $old, "cached");
is_deeply($one->menu('fake3'), $menu, "refreshed with different destination");
my $line;
like(
dies { $line = __LINE__; $one->menu() },
qr/menu\(\) requires the name of the destination package at \Q${\__FILE__}\E line $line/,
"Need 'into' package"
);
};
subtest reload_menu => sub {
my $one = $CLASS->new(from => 'fake');
my $line;
like(
dies { $line = __LINE__; $one->reload_menu() },
qr/menu\(\) requires the name of the destination package at \Q${\__FILE__}\E line $line/,
"Need 'into' package"
);
subtest empty => sub {
{
no warnings 'once';
require Exporter;
@Fake::Exporter1::ISA = ('Exporter');
*Fake::Exporter2::import = Exporter->can('import');
*Fake::Exporter3::IMPORTER_MENU = sub { () };
*Fake::Exporter4::IMPORTER_MENU = sub { (generate => sub { 1 }, export_gen => { a => 1 }) };
}
like(
dies { $line = __LINE__; $CLASS->new(from => 'Fake::Exporter4')->reload_menu('fake') },
qr/'Fake::Exporter4' provides both 'generate' and 'export_gen' in its IMPORTER_MENU \(They are exclusive, module must pick 1\) at \Q${\__FILE__}\E line $line/,
"Bad IMPORT_MENU"
);
like(
dies { $line = __LINE__; $CLASS->new(from => 'Fake::Exporter5')->reload_menu('fake') },
qr/'Fake::Exporter5' does not provide any exports at \Q${\__FILE__}\E line $line/,
"No exports, not an exporter"
);
my ($menu1, $menu2, $menu3);
ok(!dies { $menu1 = $CLASS->new(from => 'Fake::Exporter1')->reload_menu('fake') }, "Package isa Exporter with no exports") || diag $@;
ok(!dies { $menu2 = $CLASS->new(from => 'Fake::Exporter2')->reload_menu('fake') }, "Package uses Exporter qw/import/") || diag $@;
ok(!dies { $menu3 = $CLASS->new(from => 'Fake::Exporter3')->reload_menu('fake') }, "Package provides IMPORTER_MENU") || diag $@;
is_deeply(
[$menu1, $menu1, $menu2],
[$menu2, $menu3, $menu3],
"All empty menus are the same"
);
is_deeply(
$menu1,
{
lookup => {},
exports => {},
tags => { DEFAULT => [], ALL => [] },
fail => undef,
generate => undef,
magic => {},
},
"Got valid, but empty menu"
);
};
subtest IMPORTER_MENU => sub {
{
package Fake::ExporterI;
sub IMPORTER_MENU {
::is_deeply(
\@_,
['Fake::ExporterI', 'fake', ['fake', 'fake.pl', 42]],
"Got input args"
);
return (
export => [qw/foo &bar $ZAP %ZAP @ZAP/],
export_ok => [qw/baz ick missing/],
export_tags => {b => [qw/bar baz/]},
export_fail => [qw/ick/],
export_anon => { x => \&__x, z => \&__z },
export_gen => {
'gena' => sub {
sub { 'a' }
},
'&genb' => sub {
sub { 'b' }
},
},
);
}
sub foo { 'foo' }
sub bar { 'bar' }
sub baz { 'baz' }
sub ick { 'ick' }
sub __x { 'x' }
sub __z { 'z' }
# These are here to insure 'exports' does not pull them in, they are listed as generate
sub gena { die 'oops, should not see this' }
sub genb { die 'oops, should not see this' }
# These are here to insure 'exports' does not pull them in, refs were provided by anon
sub x { die 'oops, should not see this' }
sub y { die 'oops, should not see this' }
package Fake::ExporterI2;
sub IMPORTER_MENU {
return (
generate => \&generate,
);
}
sub generate { sub { 'a pie' } }
}
my $one = $CLASS->new(from => 'Fake::ExporterI', caller => ['fake', 'fake.pl', 42]);
my $menu = $one->reload_menu('fake');
is($one->{menu_for}, 'fake', "remember who it was generated for");
ok(my $gen = delete $menu->{generate}, "got a generate function");
is_deeply(
$menu,
{
lookup => {qw/
foo 1 &foo 1
bar 1 &bar 1
baz 1 &baz 1
ick 1 &ick 1
missing 1 &missing 1
x 1 &x 1
z 1 &z 1
gena 1 &gena 1
genb 1 &genb 1
$ZAP 1 %ZAP 1 @ZAP 1
/},
exports => {
'&foo' => \&Fake::ExporterI::foo,
'&bar' => \&Fake::ExporterI::bar,
'&baz' => \&Fake::ExporterI::baz,
'&ick' => \&Fake::ExporterI::ick,
'&x' => \&Fake::ExporterI::__x,
'&z' => \&Fake::ExporterI::__z,
'&missing' => \&Fake::ExporterI::missing,
'$ZAP' => \$Fake::ExporterI::ZAP,
'@ZAP' => \@Fake::ExporterI::ZAP,
'%ZAP' => \%Fake::ExporterI::ZAP,
},
tags => {
'b' => [qw/bar baz/],
'DEFAULT' => [qw/foo &bar $ZAP %ZAP @ZAP/],
'ALL' => [sort qw/&foo &bar &baz &ick &missing &x &z &gena &genb %ZAP $ZAP @ZAP/],
},
fail => { '&ick' => 1, ick => 1 },
magic => {},
},
"Got menu"
);
is($gen->('gena')->(), 'a', "generated a");
is($gen->('genb')->(), 'b', "generated b");
$one = $CLASS->new(from => 'Fake::ExporterI2', caller => ['fake', 'fake.pl', 42]);
$menu = $one->reload_menu('fake');
is($menu->{generate}, \&Fake::ExporterI2::generate, "can provide custom generate")
};
subtest OLD_STYLE => sub {
{
package Fake::ExporterE;
our @EXPORT = qw/foo &bar $ZAP %ZAP @ZAP/;
our @EXPORT_OK = qw/baz ick missing/;
our %EXPORT_TAGS = (b => [qw/bar baz/]);
our @EXPORT_FAIL = qw/ick/;
our %EXPORT_ANON = (x => \&__x, z => \&__z);
our %EXPORT_GEN = (
'gena' => sub {
sub { 'a' }
},
'&genb' => sub {
sub { 'b' }
},
);
sub foo { 'foo' }
sub bar { 'bar' }
sub baz { 'baz' }
sub ick { 'ick' }
sub __x { 'x' }
sub __z { 'z' }
# These are here to insure 'exports' does not pull them in, they are listed as generate
sub gena { die 'oops, should not see this' }
sub genb { die 'oops, should not see this' }
# These are here to insure 'exports' does not pull them in, refs were provided by anon
sub x { die 'oops, should not see this' }
sub y { die 'oops, should not see this' }
}
my $one = $CLASS->new(from => 'Fake::ExporterE', caller => ['fake', 'fake.pl', 42]);
my $menu = $one->reload_menu('fake');
is($one->{menu_for}, 'fake', "remember who it was generated for");
ok(my $gen = delete $menu->{generate}, "got a generate function");
is_deeply(
$menu,
{
lookup => {qw/
foo 1 &foo 1
bar 1 &bar 1
baz 1 &baz 1
ick 1 &ick 1
missing 1 &missing 1
x 1 &x 1
z 1 &z 1
gena 1 &gena 1
genb 1 &genb 1
$ZAP 1 %ZAP 1 @ZAP 1
/},
exports => {
'&foo' => \&Fake::ExporterE::foo,
'&bar' => \&Fake::ExporterE::bar,
'&baz' => \&Fake::ExporterE::baz,
'&ick' => \&Fake::ExporterE::ick,
'&x' => \&Fake::ExporterE::__x,
'&z' => \&Fake::ExporterE::__z,
'&missing' => \&Fake::ExporterE::missing,
'$ZAP' => \$Fake::ExporterE::ZAP,
'@ZAP' => \@Fake::ExporterE::ZAP,
'%ZAP' => \%Fake::ExporterE::ZAP,
},
tags => {
'b' => [qw/bar baz/],
'DEFAULT' => [qw/foo &bar $ZAP %ZAP @ZAP/],
'ALL' => [sort qw/&foo &bar &baz &ick &missing &x &z &gena &genb %ZAP $ZAP @ZAP/],
},
fail => { '&ick' => 1, ick => 1 },
magic => {},
},
"Got menu"
);
is($gen->('gena')->(), 'a', "generated a");
is($gen->('genb')->(), 'b', "generated b");
};
};
subtest parse_args => sub {
{
package Fake::Exporter::ForArgs;
sub IMPORTER_MENU {
return (
export => [qw/foo &bar $ZAP %ZAP @ZAP/],
export_ok => [qw/baz ick missing/],
export_tags => {
b => [qw/bar baz/],
c => [
boo => {'-as' => 'buz', '-prefix' => 'a_', '-postfix' => '_a'},
':b'
],
},
export_fail => [qw/ick/],
export_anon => { x => \&__x, z => \&__z },
export_gen => {
'gena' => sub {
sub { 'a' }
},
'&genb' => sub {
sub { 'b' }
},
},
);
}
sub boo { 'boo' }
sub foo { 'foo' }
sub bar { 'bar' }
sub baz { 'baz' }
sub ick { 'ick' }
sub __x { 'x' }
sub __z { 'z' }
# These are here to insure 'exports' does not pull them in, they are listed as generate
sub gena { die 'oops, should not see this' }
sub genb { die 'oops, should not see this' }
# These are here to insure 'exports' does not pull them in, refs were provided by anon
sub x { die 'oops, should not see this' }
sub y { die 'oops, should not see this' }
}
my $one = $CLASS->new(from => 'Fake::Exporter::ForArgs', caller => ['Foo', 'foo.pl', 42]);
is_deeply(
[$one->parse_args('Dest')],
[
'Dest',
[],
{},
[
['&foo', {}],
['&bar', {}],
['$ZAP', {}],
['%ZAP', {}],
['@ZAP', {}],
],
undef,
],
"Got defaults with empty list"
);
my $set = sub { 1 };
is_deeply(
[$one->parse_args('Dest', $set)],
[
'Dest',
[],
{},
[
['&foo', {}],
['&bar', {}],
['$ZAP', {}],
['%ZAP', {}],
['@ZAP', {}],
],
$set,
],
"Got defaults with empty list + custom setter"
);
is_deeply(
[$one->parse_args('Dest', 'foo', $set)],
[
'Dest',
[],
{},
[
['&foo', {}],
],
$set,
],
"Got defaults with 1 item + custom setter"
);
is_deeply(
[$one->parse_args('Dest', '!bar')],
[
'Dest',
[],
{ '&bar' => 1 },
[
['&foo', {}],
['&bar', {}],
['$ZAP', {}],
['%ZAP', {}],
['@ZAP', {}],
],
undef,
],
"Got defaults, exclude bar"
);
is_deeply(
[$one->parse_args('Dest', '!' => 'bar')],
[
'Dest',
[],
{ '&bar' => 1 },
[
['&foo', {}],
['&bar', {}],
['$ZAP', {}],
['%ZAP', {}],
['@ZAP', {}],
],
undef,
],
"Got defaults, exclude bar"
);
is_deeply(
[$one->parse_args('Dest', ':DEFAULT', '!:b')],
[
'Dest',
[],
{ '&bar' => 1, '&baz' => 1 },
[
['&foo', {}],
['&bar', {}],
['$ZAP', {}],
['%ZAP', {}],
['@ZAP', {}],
],
undef,
],
"Got defaults, exclude :b"
);
is_deeply(
[$one->parse_args('Dest', ':b' => {-prefix => 'foo_'}, qw/x &y/)],
[
'Dest',
[],
{},
[
['&bar', {-prefix => 'foo_'}],
['&baz', {-prefix => 'foo_'}],
['&x', {}],
['&y', {}],
],
undef,
],
"Spec for tag"
);
is_deeply(
[$one->parse_args('Dest', '/A/' => { -postfix => '_foo' }, '!$ZAP')],
[
'Dest',
[],
{ '$ZAP' => 1 },
[
['$ZAP', {-postfix => '_foo'}],
['%ZAP', {-postfix => '_foo'}],
['@ZAP', {-postfix => '_foo'}],
],
undef,
],
"Spec for pattern"
);
is_deeply(
[$one->parse_args('Dest', ':c' => {-prefix => 'b_', -postfix => '_b'})],
[
'Dest',
[],
{},
[
['&boo', {-as => 'buz', -prefix => 'b_a_', -postfix => '_a_b'}],
['&bar', {-prefix => 'b_', -postfix => '_b'}],
['&baz', {-prefix => 'b_', -postfix => '_b'}],
],
undef,
],
"Spec for tag with embedded and added specs"
);
is_deeply(
[$one->parse_args('Dest', 22, qr/A/, { -postfix => '_foo' }, '!$ZAP', 45)],
[
'Dest',
[ 22, 45 ],
{ '$ZAP' => 1 },
[
['$ZAP', {-postfix => '_foo'}],
['%ZAP', {-postfix => '_foo'}],
['@ZAP', {-postfix => '_foo'}],
],
undef,
],
"Spec for qr// (also test version)"
);
like(
dies { $one->parse_args('Dest', '/A/' => { -as => 'foo' }) },
qr{Cannot use '-as' to rename multiple symbols included by: /A/},
"-as does not work with multiple imports"
);
like(
dies { $one->parse_args('Dest', ':b' => { -as => 'foo' }) },
qr{Cannot use '-as' to rename multiple symbols included by: :b},
"-as does not work with multiple imports"
);
like(
dies { $one->parse_args('Dest', ':bad') },
qr{Fake::Exporter::ForArgs does not export the :bad tag},
"-as does not work with multiple imports"
);
};
subtest _handle_fail => sub {
{
package Fake::Exporter::ForFail;
sub IMPORTER_MENU {
return (
export => [qw/foo &bar $ZAP %ZAP @ZAP/],
export_ok => [qw/baz ick missing/],
export_tags => {b => [qw/bar baz/]},
export_fail => [qw/ick foo/],
export_anon => { x => \&__x, z => \&__z },
export_gen => {
'gena' => sub {
sub { 'a' }
},
'&genb' => sub {
sub { 'b' }
},
},
);
}
sub foo { 'foo' }
sub bar { 'bar' }
sub baz { 'baz' }
sub ick { 'ick' }
sub __x { 'x' }
sub __z { 'z' }
# These are here to insure 'exports' does not pull them in, they are listed as generate
sub gena { die 'oops, should not see this' }
sub genb { die 'oops, should not see this' }
# These are here to insure 'exports' does not pull them in, refs were provided by anon
sub x { die 'oops, should not see this' }
sub y { die 'oops, should not see this' }
sub export_fail {
my $from = shift;
return grep !/foo/, @_;
}
}
my $one = $CLASS->new(from => 'Fake::Exporter::ForFail', caller => ['Foo', 'foo.pl', 42]);
ok(!dies { $one->_handle_fail('dest', [['bar'], ['baz']]) }, "no failures") || diag $@;
ok(!dies { $one->_handle_fail('dest', [['bar'], ['foo']]) }, "no failures, but 'foo' was on list") || diag $@;
like(
warns {
like(
dies { $one->_handle_fail('dest', [['bar'], ['ick']]) },
qr/Can't continue after import errors/,
"True failure"
)
},
qr/"ick" is not implemented by the Fake::Exporter::ForFail module on this architecture/,
"Got expected warning"
);
};
subtest _set_symbols => sub {
{
package Fake::ForSetSymbols;
our @EXPORT = qw/foo &bar $ZAP %ZAP @ZAP $REF/;
our @EXPORT_OK = qw/baz ick missing/;
our %EXPORT_TAGS = (b => [qw/bar baz/]);
our @EXPORT_FAIL = qw/ick/;
our %EXPORT_ANON = (x => \&__x, z => \&__z);
our %EXPORT_GEN = (
'gena' => sub {
sub { 'a' }
},
'&genb' => sub {
my $bad = 'bad';
return \$bad; # To test sigil mismatch
},
);
our @ZAP = (qw/Z A P/);
our $ZAP = 'ZAP';
our %ZAP = (ZAP => 1);
our $REF = \$ZAP;
sub foo { 'foo' }
sub bar { 'bar' }
sub baz { 'baz' }
sub ick { 'ick' }
sub __x { 'x' }
sub __z { 'z' }
# These are here to insure 'exports' does not pull them in, they are listed as generate
sub gena { die 'oops, should not see this' }
sub genb { die 'oops, should not see this' }
# These are here to insure 'exports' does not pull them in, refs were provided by anon
sub x { die 'oops, should not see this' }
sub y { die 'oops, should not see this' }
}
my $one = $CLASS->new(from => 'Fake::ForSetSymbols', caller => ['Foo', 'foo.pl', 42]);
$one->_set_symbols(
'Fake::Dest::A',
{'&bar' => 1, '@ZAP' => 1},
[
# These first 2 should both be excluded
['&bar' => {}],
['&bar' => {-prefix => 'pre_', -postfix => '_post'}],
# Replicate use of ':b', this one is not excluded though
['&baz' => {-prefix => 'pre_', -postfix => '_post'}],
# Exclude
['@ZAP' => {}],
# Should import, specific name requested, ignore exclude
['&bar' => {-as => 'boo'}],
# Should work fine
['$REF' => {}],
['&foo' => {}],
['&gena' => {}],
['&x' => {}],
['$ZAP' => {-prefix => 'pre_', -postfix => '_post'}],
],
);
is(\&Fake::Dest::A::pre_baz_post, \&Fake::ForSetSymbols::baz, 'Exported &baz as pre_baz_post');
is(\&Fake::Dest::A::boo, \&Fake::ForSetSymbols::bar, 'Exported &bar as &boo');
is(\&Fake::Dest::A::foo, \&Fake::ForSetSymbols::foo, 'Exported &foo');
is(\&Fake::Dest::A::x, \&Fake::ForSetSymbols::__x, 'Exported anon &x');
is(\$Fake::Dest::A::pre_ZAP_post, \$Fake::ForSetSymbols::ZAP, 'Exported $ZAP as $pre_ZAP_post');
is(Fake::Dest::A::gena(), 'a', 'Generated &gena');
{
no warnings 'once';
ok(\$Fake::Dest::A::REF == \$Fake::ForSetSymbols::REF, 'Exported $REF');
ok(\@Fake::Dest::A::ZAP != \@Fake::ForSetSymbols::ZAP, 'Excluded @ZAP');
ok(\&Fake::Dest::A::bar != \&Fake::ForSetSymbols::bar, 'Excluded &bar');
ok(\&Fake::Dest::A::pre_bar_post != \&Fake::ForSetSymbols::bar, 'Excluded &bar with prefix/postfix');
}
ok(!dies { $one->_set_symbols('Fake::Dest::A', {}, [['&missing' => {}]]) }, "Can fake-import missing symbol if it is listed");
like(
dies { $one->_set_symbols('Fake::Dest::A', {}, [['&nope' => {}]]) },
qr/Fake::ForSetSymbols does not export \&nope/,
"unlisted symbol cannot be imported"
);
like(
dies { $one->_set_symbols('Fake::Dest::A', {}, [['&genb' => {}]]) },
qr/Symbol '\&genb' requested, but reference \(SCALAR\) does not match sigil \(\&\)/,
"sigil mismatch"
);
# Make sure it finds the correct caller, not our fake one
delete $one->{caller};
{
no warnings 'redefine';
*Fake::Dest::A::foo = sub { 1 };
}
ok(
!warns {
no warnings 'redefine';
$one->_set_symbols('Fake::Dest::A', {}, [['&foo' => {}]])
},
"no redefine warnings"
);
{
no warnings 'redefine';
*Fake::Dest::A::foo = sub { 1 };
}
like(
warns {
use warnings 'redefine';
$one->_set_symbols('Fake::Dest::A', {}, [['&foo' => {}]])
},
qr/Subroutine Fake::Dest::A::foo redefined/,
"redefine warnings"
);
$one = $CLASS->new(from => 'Fake::Dest::A');
can_ok('Fake::Dest::A', 'foo');
$one->do_unimport(qw/foo/);
ok(!'Fake::Dest::A'->can('foo'), "removed &foo");
is(\&Fake::Dest::A::pre_baz_post, \&Fake::ForSetSymbols::baz, 'Kept &baz as pre_baz_post');
is(\&Fake::Dest::A::boo, \&Fake::ForSetSymbols::bar, 'Kept &bar as &boo');
is(\&Fake::Dest::A::x, \&Fake::ForSetSymbols::__x, 'Kept anon &x');
is(\$Fake::Dest::A::pre_ZAP_post, \$Fake::ForSetSymbols::ZAP, 'Kept $ZAP as $pre_ZAP_post');
is(Fake::Dest::A::gena(), 'a', 'Kept &gena');
$one->do_unimport();
is(\$Fake::Dest::A::pre_ZAP_post, \$Fake::ForSetSymbols::ZAP, 'Kept $ZAP as $pre_ZAP_post');
ok(!'Fake::Dest::A'->can($_), "removed \&$_") for qw/pre_baz_post boo x gena/;
};
subtest version_check => sub {
local *version_check = $CLASS->can('_version_check') or die "where did _version_check go?";
ok(version_check($CLASS, ['foo', 'foo.pl', 42], '0.001'), "version check pass");
like(
dies { version_check($CLASS, ['foo', 'foo.pl', 42], '9999') },
qr/version 9999 required.*foo\.pl line 42/,
"Version Check fails"
);
};
subtest mod_to_file => sub {
local *mod_to_file = $CLASS->can('_mod_to_file') or die "where did _mod_to_file go?";
is(mod_to_file('Foo::Bar::Baz'), 'Foo/Bar/Baz.pm', "Converted module to filename");
};
subtest load_file => sub {
local *load_file = $CLASS->can('_load_file') or die "where did _load_file go?";
ok(load_file(['foo', 'foo.pl', 42], 'Data/Dumper.pm'), "Load file pass");
eval <<" EOT" && die "Ooops, wtf?";
#line 42 "foo.pl"
require Fake::File::That::Better::Not::Exist::SAGSDGDS;
1;
EOT
my $error = $@;
like($error, qr/locate.*\@INC/ms, "predicted error message is somewhat sane");
is(
dies { load_file(['foo', 'foo.pl', 42], 'Fake/File/That/Better/Not/Exist/SAGSDGDS.pm') },
$error,
"Load file fails"
);
};
subtest optimal_import => sub {
{
package Fake::ForOptimal::A;
our @EXPORT = qw/foo &bar $ZAP %ZAP @ZAP/;
sub foo { 'foo' }
sub bar { 'bar' }
}
my $optimal = $CLASS->can('optimal_import');
ok($optimal->('Fake::ForOptimal::A', 'FDestA', ['F', 'F.pm', 4], qw/foo/), "Success");
can_ok('FDestA', 'foo');
ok(!$optimal->('Fake::ForOptimal::A', 'FDestA', ['F', 'F.pm', 4], qw/bar @ZAP/), "Failure");
ok(!'FDestA'->can('bar'), 'Did not export anything');
ok(!$optimal->('Fake::ForOptimal::A', 'FDestA', ['F', 'F.pm', 4], qw/bloop/), "Failure, not a valid export");
{
package Fake::ForOptimal::B;
our @EXPORT = qw/foo &bar/;
sub foo { 'foo' }
sub bar { 'bar' }
}
ok($optimal->('Fake::ForOptimal::B', 'FDestB', ['F', 'F.pm', 4]), "Success with defaults");
can_ok('FDestB', 'foo', 'bar');
no warnings 'once';
*FDestD::foo = sub { 'xyz' };
like(
warns { $optimal->('Fake::ForOptimal::A', 'FDestD', ['F', 'F.pm', 4], 'foo') },
qr/Subroutine FDestD::foo redefined(?:, called by F\.pm:4)? at F\.pm line 4/,
"Got redefine warning"
);
{
package FDestD;
Importer->unimport;
}
ok(!FDestD->can('foo'), "Removed 'foo'");
};
subtest get => sub {
is_deeply(
Importer->get(Carp => qw/croak confess/),
{
croak => \&Carp::croak,
confess => \&Carp::confess,
},
"Exports went into the hash (class)."
);
is_deeply(
Importer->new(from => 'Carp')->get(qw/croak confess/),
{
croak => \&Carp::croak,
confess => \&Carp::confess,
},
"Exports went into the hash (instance)."
);
};
subtest get_list => sub {
is_deeply(
[Importer->get_list(Carp => qw/croak confess/)],
[ \&Carp::croak, \&Carp::confess ],
"list of refs (class)."
);
is_deeply(
[Importer->new(from => 'Carp')->get_list(qw/croak confess/)],
[ \&Carp::croak, \&Carp::confess ],
"list of refs (instance)."
);
};
subtest get_one => sub {
is_deeply(
Importer->get_one(Carp => qw/confess/),
\&Carp::confess,
"one ref (class)."
);
is_deeply(
Importer->new(from => 'Carp')->get_one(qw/croak/),
\&Carp::croak,
"one ref (instance)."
);
is_deeply(
Importer->get_one(Carp => qw/confess croak/),
\&Carp::croak,
"one ref (last)."
);
};
subtest magic => sub {
BEGIN {
$INC{'Magic/Exporter.pm'} = 1;
package Magic::Exporter;
our @EXPORT = qw/foo/;
our %EXPORT_MAGIC = ( foo => sub { $main::MAGIC = [@_] } );
sub foo { 1 }
}
use Importer 'Magic::Exporter' => (foo => { -as => 'foo2' });
can_ok(__PACKAGE__, 'foo2');
is_deeply(
$main::MAGIC,
[
'Magic::Exporter',
into => __PACKAGE__,
orig_name => 'foo',
new_name => 'foo2',
ref => \&Magic::Exporter::foo,
],
"Magic callback was called, args as expected"
);
};
done_testing;