#!./perl
BEGIN {
chdir
't'
if
-d
't'
;
require
'./test.pl'
;
set_up_inc(
'../lib'
);
}
plan 28;
no
warnings
'experimental::defer'
;
{
my
$x
=
""
;
{
defer {
$x
=
"a"
}
}
is(
$x
,
"a"
,
'defer block is invoked'
);
{
defer {
$x
=
""
;
$x
.=
"abc"
;
$x
.=
"123"
;
}
}
is(
$x
,
"abc123"
,
'defer block can contain multiple statements'
);
{
defer {}
}
ok(1,
'Empty defer block parses OK'
);
}
{
my
$x
=
""
;
{
defer {
$x
.=
"a"
}
defer {
$x
.=
"b"
}
defer {
$x
.=
"c"
}
}
is(
$x
,
"cba"
,
'defer blocks happen in LIFO order'
);
}
{
my
$x
=
""
;
{
defer {
$x
.=
"a"
}
$x
.=
"A"
;
}
is(
$x
,
"Aa"
,
'defer blocks happen after the main body'
);
}
{
my
$x
=
""
;
foreach
my
$i
(
qw( a b c )
) {
defer {
$x
.=
$i
}
}
is(
$x
,
"abc"
,
'defer block happens for every iteration of foreach'
);
}
{
my
$x
=
""
;
my
$cond
= 0;
if
(
$cond
) {
defer {
$x
.=
"XXX"
}
}
is(
$x
,
""
,
'defer block does not happen inside non-taken conditional branch'
);
}
{
my
$x
=
""
;
while
(1) {
last
;
defer {
$x
.=
"a"
}
}
is(
$x
,
""
,
'defer block does not happen if entered but unencountered'
);
}
{
my
$x
=
""
;
my
$counter
= 1;
{
defer {
$x
.=
"A"
}
redo
if
$counter
++ < 5;
}
is(
$x
,
"AAAAA"
,
'defer block can happen multiple times'
);
}
{
my
$x
=
""
;
{
defer {
$x
.=
"a"
;
defer {
$x
.=
"b"
;
}
}
}
is(
$x
,
"ab"
,
'defer block can contain another defer'
);
}
{
my
$x
=
""
;
my
$value
=
do
{
defer {
$x
.=
"before"
}
"value"
;
};
is(
$x
,
"before"
,
'defer blocks run inside do { }'
);
is(
$value
,
"value"
,
'defer block does not disturb do { } value'
);
}
{
my
$x
=
""
;
my
$sub
=
sub
{
defer {
$x
.=
"a"
}
};
$sub
->();
$sub
->();
$sub
->();
is(
$x
,
"aaa"
,
'defer block inside sub'
);
}
{
my
$x
=
""
;
my
$sub
=
sub
{
return
;
defer {
$x
.=
"a"
}
};
$sub
->();
is(
$x
,
""
,
'defer block inside sub does not happen if entered but returned early'
);
}
{
my
$x
=
""
;
my
sub
after
{
$x
.=
"c"
;
}
my
sub
before
{
$x
.=
"a"
;
defer {
$x
.=
"b"
}
goto
\
&after
;
}
before
();
is(
$x
,
"abc"
,
'defer block invoked before tail-call'
);
}
{
my
$var
=
"outer"
;
my
$x
;
{
my
$var
=
"inner"
;
defer {
$x
=
$var
}
}
is(
$x
,
"inner"
,
'defer block captures live value of same-scope lexicals'
);
}
{
my
$var
=
"outer"
;
my
$x
;
{
defer {
$x
=
$var
}
my
$var
=
"inner"
;
}
is (
$x
,
"outer"
,
'defer block correctly captures outer lexical when only shadowed afterwards'
);
}
{
our
$var
=
"outer"
;
{
local
$var
=
"inner"
;
defer {
$var
=
"finally"
}
}
is(
$var
,
"outer"
,
'defer after localization still unlocalizes'
);
}
{
our
$var
=
"outer"
;
{
defer {
$var
=
"finally"
}
local
$var
=
"inner"
;
}
is(
$var
,
"finally"
,
'defer before localization overwrites'
);
}
{
my
$x
=
""
;
my
$sub
=
sub
{
defer {
$x
.=
"a"
}
die
"Oopsie\n"
;
};
my
$e
=
defined
eval
{
$sub
->(); 1 } ?
undef
: $@;
is(
$x
,
"a"
,
'defer block still runs during exception unwind'
);
is(
$e
,
"Oopsie\n"
,
'Thrown exception still occurs after defer'
);
}
{
my
$sub
=
sub
{
defer {
die
"Oopsie\n"
; }
return
"retval"
;
};
my
$e
=
defined
eval
{
$sub
->(); 1 } ?
undef
: $@;
is(
$e
,
"Oopsie\n"
,
'defer block can throw exception'
);
}
{
my
$sub
=
sub
{
defer {
die
"Oopsie 1\n"
; }
die
"Oopsie 2\n"
;
};
my
$e
=
defined
eval
{
$sub
->(); 1 } ?
undef
: $@;
like(
$e
,
qr/^Oopsie \d\n/
,
'defer block can throw exception during exception unwind'
);
}
{
ok(
defined
eval
'sub { defer { goto HERE; HERE: 1; } }'
,
'goto forwards within defer {} is permitted'
) or
diag(
"Failure was $@"
);
ok(
defined
eval
'sub { defer { HERE: 1; goto HERE; } }'
,
'goto backwards within defer {} is permitted'
) or
diag(
"Failure was $@"
);
}
{
my
$sub
=
sub
{
while
(1) {
goto
HERE;
defer { HERE: 1; }
}
};
my
$e
=
defined
eval
{
$sub
->(); 1 } ?
undef
: $@;
like(
$e
,
qr/^Can't "goto" into a "defer" block /
,
'Cannot goto into defer block'
);
}
{
my
$ok
=
eval
'defer { use strict; foo }'
;
my
$e
= $@;
ok(!
$ok
,
'defer BLOCK finalizes optree'
);
like(
$e
,
qr/^Bareword "foo" not allowed while "strict subs" in use at /
,
'Error from finalization'
);
}