--- /dev/null
+#!perl -T
+
+use strict;
+use warnings;
+
+use lib 't/lib';
+use Test::Leaner 'no_plan';
+
+use Scope::Upper qw<unwind UP HERE>;
+
+# perl 5.8.0 is not happy when @args is a lexical, so we have to use a global.
+# It's slightly faster too.
+
+our @args;
+
+my $args_code;
+if ("$]" < 5.008) {
+ # perl 5.6.x is really bad at closures, hence make it compile a function call
+ # instead.
+ *_get_args = sub { @args };
+ $args_code = '_get_args()';
+} else {
+ $args_code = '@args';
+}
+
+my $call = sub {
+ my ($height, $level, $i) = @_;
+ $level = $level ? 'UP ' x $level : 'HERE';
+ return [ [ "unwind($args_code => $level)\n", [ \@args ] ] ];
+};
+
+# @_[0 .. $#_] also ought to work, but it sometimes evaluates to nonsense in
+# scalar context on perl 5.8.5 and below.
+
+sub list { wantarray ? @_ : $_[$#_] }
+
+my @blocks = (
+ [ 'sub {', '}->()' ],
+ [ 'eval {', '}' ],
+);
+
+my @contexts = (
+ [ '', '; ()', 'v' ],
+ [ 'scalar(', ')', 's' ],
+ [ 'list(', ')', 'l' ],
+);
+
+for my $block (@blocks) {
+ $_ .= "\n" for @$block[0, 1];
+}
+for my $cxt (@contexts) {
+ $_ .= "\n" for @$cxt[0, 1];
+}
+
+sub contextify {
+ my ($cxt, $active, $exp, @items) = @_;
+ return $exp unless $active;
+ if ($cxt eq 'v') {
+ return [ ];
+ } elsif ($cxt eq 's') {
+ return [ $cxt, @$exp ];
+ } else {
+ return [ @items, @$exp ];
+ }
+}
+
+my $integer = 0;
+my $items = 0;
+
+sub gen {
+ my ($height, $level, $i) = @_;
+ push @_, $i = 0 if @_ == 2;
+ my @res;
+ my $up = $i == $height + 1 ? $call->(@_) : gen($height, $level, $i + 1);
+ my $active = $i <= ($height - $level);
+ for my $base (@$up) {
+ my ($code, $exp) = @$base;
+ for my $blk (@blocks) {
+ for my $cx (@contexts) {
+ push @res, [
+ $blk->[0] . $cx->[0] . $code . $cx->[1] . $blk->[1],
+ contextify($cx->[2], $active, $exp),
+ ];
+ my @items = map $integer++, 0 .. ($items++ % 3);
+ my $list = join ', ', @items;
+ push @res, [
+ $blk->[0] . $cx->[0] . "($list, $code)" . $cx->[1] . $blk->[1],
+ contextify($cx->[2], $active, $exp, @items),
+ ];
+ }
+ }
+ }
+ return \@res;
+}
+
+sub linearize { join ', ', map { defined($_) ? $_ : '(undef)' } @_ }
+
+sub expect {
+ my @spec = @{$_[0]};
+ my @acc;
+ for my $s (reverse @spec) {
+ if (ref $s) {
+ unshift @acc, @$s;
+ } elsif ($s =~ /^[0-9]+$/) {
+ unshift @acc, $s;
+ } elsif ($s eq 's') {
+ @acc = (@acc ? $acc[-1] : undef);
+ } else {
+ return 'XXX';
+ }
+ }
+ return linearize @acc;
+}
+
+my @arg_lists = ([ ], [ 'A' ], [ qw<B C> ]);
+
+for my $height (0 .. 1) {
+ for my $level (0 .. 1) {
+ my $i;
+ my $tests = gen $height, $level;
+ for (@$tests) {
+ my ($code, $exp_spec) = @$_;
+ ++$i;
+ my $desc = "stress unwind $height $level $i";
+ my $cb = do {
+ no warnings 'void';
+ eval "sub { $code }";
+ };
+ if ($@) {
+ fail "$desc : test did not compile" for 1 .. @arg_lists;
+ } else {
+ for (@arg_lists) {
+ @args = @$_;
+ my $res = linearize $cb->();
+ my $exp = expect $exp_spec;
+ if ($res ne $exp) {
+ diag <<DIAG;
+=== This testcase failed ===
+$code;
+==== vvvvv Errors vvvvvv ===
+DIAG
+ }
+ is $res, $exp, "$desc [@args]";
+ }
+ }
+ }
+ }
+}