The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!perl -w

# test the MULTICALL macros
# Note: as of Oct 2010, there are not yet comprehensive tests
# for these macros.

use warnings;
use strict;

use Test::More tests => 80;
use XS::APItest;


{
    my $sum = 0;
    sub add { $sum += $_++ }

    my @a = (1..3);
    XS::APItest::multicall_each \&add, @a;
    is($sum, 6, "sum okay");
    is($a[0], 2, "a[0] okay");
    is($a[1], 3, "a[1] okay");
    is($a[2], 4, "a[2] okay");
}

# [perl #78070]
# multicall using a sub that already has CvDEPTH > 1 caused sub
# to be prematurely freed

{
    my $destroyed = 0;
    sub REC::DESTROY { $destroyed = 1 }

    my $closure_var;
    {
	my $f = sub {
	    no warnings 'void';
	    $closure_var;
	    my $sub = shift;
	    if (defined $sub) {
		XS::APItest::multicall_each \&$sub, 1,2,3;
	    }
	};
	bless $f,  'REC';
	$f->($f);
	is($destroyed, 0, "f not yet destroyed");
    }
    is($destroyed, 1, "f now destroyed");

}

# [perl #115602]
# deep recursion realloced the CX stack, but the dMULTICALL local var
# 'cx' still pointed to the old one.
# This doesn't actually test the failure (I couldn't think of a way to
# get the failure to show at the perl level) but it allows valgrind or
# similar to spot any errors.

{
    sub rec { my $c = shift; rec($c-1) if $c > 0 };
    my @r = XS::APItest::multicall_each { rec(90) } 1,2,3;
    pass("recursion");
}



# Confirm that MULTICALL handles arg return correctly in the various
# contexts. Also check that lvalue subs are handled the same way, as
# these take different code paths.
# Whenever an explicit 'return' is used, it is followed by '1;' to avoid
# the return being optimised into a leavesub.
# Adding a 'for' loop pushes extra junk on the stack, which we we want to
# avoid being interpreted as a return arg.

{
    package Ret;

    use XS::APItest qw(multicall_return G_VOID G_SCALAR G_ARRAY);

    # Helper function for the block that follows:
    # check that @$got matches what would be expected if a function returned
    # the items in @$args in $gimme context.

    sub gimme_check {
        my ($gimme, $got, $args, $desc) = @_;

        if ($gimme == G_VOID) {
            ::is (scalar @$got, 0, "G_VOID:   $desc");
        }
        elsif ($gimme == G_SCALAR) {
            ::is (scalar @$got, 1, "G_SCALAR: $desc: expect 1 arg");
            ::is ($got->[0], (@$args ? $args->[-1] : undef),
                        "G_SCALAR: $desc: correct arg");
        }
        else {
            ::is (join('-',@$got), join('-', @$args), "G_ARRAY:  $desc");
        }
    }

    for my $gimme (G_VOID, G_SCALAR, G_ARRAY) {
        my @a;

        # zero args

        @a = multicall_return {()} $gimme;
        gimme_check($gimme, \@a, [], "()");
        sub f1 :lvalue { () }
        @a = multicall_return \&f1, $gimme;
        gimme_check($gimme, \@a, [], "() lval");

        @a = multicall_return { return; 1 } $gimme;
        gimme_check($gimme, \@a, [], "return");
        sub f2 :lvalue { return; 1 }
        @a = multicall_return \&f2, $gimme;
        gimme_check($gimme, \@a, [], "return lval");


        @a = multicall_return { for (1,2) { return; 1 } } $gimme;
        gimme_check($gimme, \@a, [], "for-return");
        sub f3 :lvalue { for (1,2) { return; 1 } }
        @a = multicall_return \&f3, $gimme;
        gimme_check($gimme, \@a, [], "for-return lval");

        # one arg

        @a = multicall_return {"one"} $gimme;
        gimme_check($gimme, \@a, ["one"], "one arg");
        sub f4 :lvalue { "one" }
        @a = multicall_return \&f4, $gimme;
        gimme_check($gimme, \@a, ["one"], "one arg lval");

        @a = multicall_return { return "one"; 1} $gimme;
        gimme_check($gimme, \@a, ["one"], "return one arg");
        sub f5 :lvalue { return "one"; 1 }
        @a = multicall_return \&f5, $gimme;
        gimme_check($gimme, \@a, ["one"], "return one arg lval");

        @a = multicall_return { for (1,2) { return "one"; 1} } $gimme;
        gimme_check($gimme, \@a, ["one"], "for-return one arg");
        sub f6 :lvalue { for (1,2) { return "one"; 1 } }
        @a = multicall_return \&f6, $gimme;
        gimme_check($gimme, \@a, ["one"], "for-return one arg lval");

        # two args

        @a = multicall_return {"one", "two" } $gimme;
        gimme_check($gimme, \@a, ["one", "two"], "two args");
        sub f7 :lvalue { "one", "two" }
        @a = multicall_return \&f7, $gimme;
        gimme_check($gimme, \@a, ["one", "two"], "two args lval");

        @a = multicall_return { return "one", "two"; 1} $gimme;
        gimme_check($gimme, \@a, ["one", "two"], "return two args");
        sub f8 :lvalue { return "one", "two"; 1 }
        @a = multicall_return \&f8, $gimme;
        gimme_check($gimme, \@a, ["one", "two"], "return two args lval");

        @a = multicall_return { for (1,2) { return "one", "two"; 1} } $gimme;
        gimme_check($gimme, \@a, ["one", "two"], "for-return two args");
        sub f9 :lvalue { for (1,2) { return "one", "two"; 1 } }
        @a = multicall_return \&f9, $gimme;
        gimme_check($gimme, \@a, ["one", "two"], "for-return two args lval");
    }

    # MULTICALL *shouldn't* clear savestack after each call

    sub f10 { my $x = 1; $x };
    my @a = XS::APItest::multicall_return \&f10, G_SCALAR;
    ::is($a[0], 1, "leave scope");
}