package TestAPRlib::bucket;

# a mix of APR::Bucket and APR::BucketType tests

use strict;
use warnings FATAL => 'all';

use Apache::Test;
use Apache::TestUtil;
use TestCommon::Utils;

use APR::Pool ();
use APR::Bucket ();
use APR::BucketAlloc ();
use APR::BucketType ();
use APR::Table ();

use APR::Const -compile => 'SUCCESS';

sub num_of_tests {
    return 21;
}

sub test {

    my $pool = APR::Pool->new();
    my $ba   = APR::BucketAlloc->new($pool);

    # new: basic
    {
        my $data = "foobar";
        my $b = APR::Bucket->new($ba, $data);

        t_debug('$b is defined');
        ok defined $b;

        t_debug('$b ISA APR::Bucket object');
        ok $b->isa('APR::Bucket');

        my $type = $b->type;
        ok t_cmp $type->name, 'mod_perl SV bucket', "type";

        ok t_cmp $b->length, length($data), "modperl b->length";
    }

    # new: offset
    {
        my $data   = "foobartar";
        my $offset = 3;
        my $real = substr $data, $offset;
        my $b = APR::Bucket->new($ba, $data, $offset);
        my $rlen = $b->read(my $read);
        ok t_cmp $read, $real, 'new($data, $offset)/buffer';
        ok t_cmp $rlen, length($read), 'new($data, $offset)/len';
        ok t_cmp $b->start, $offset, 'offset';

    }

    # new: offset+len
    {
        my $data   = "foobartar";
        my $offset = 3;
        my $len    = 3;
        my $real = substr $data, $offset, $len;
        my $b = APR::Bucket->new($ba, $data, $offset, $len);
        my $rlen = $b->read(my $read);
        ok t_cmp $read, $real, 'new($data, $offset, $len)/buffer';
        ok t_cmp $rlen, length($read), 'new($data, $offse, $lent)/len';
    }

    # new: offset+ too big len
    {
        my $data   = "foobartar";
        my $offset = 3;
        my $len    = 10;
        my $real = substr $data, $offset, $len;
        my $b = eval { APR::Bucket->new($ba, $data, $offset, $len) };
        ok t_cmp $@,
            qr/the length argument can't be bigger than the total/,
            'new($data, $offset, $len_too_big)';
    }

    # modification of the source variable, affects the data
    # inside the bucket
    {
        my $data = "A" x 10;
        my $orig = $data;
        my $b = APR::Bucket->new($ba, $data);
        $data =~ s/^..../BBBB/;
        $b->read(my $read);
        ok t_cmp $read, $data,
            "data inside the bucket should get affected by " .
            "the changes to the Perl variable it's created from";
    }


    # APR::Bucket->new() with the argument PADTMP (which happens when
    # some function is re-entered) and the same SV is passed to
    # different buckets, which must be detected and copied away.
    {
        my @buckets  = ();
        my @data     = qw(ABCD EF);
        my @received = ();
        for my $str (@data) {
            my $b = func($ba, $str);
            push @buckets, $b;
        }

        # the creating of buckets and reading from them is done
        # separately on purpose
        for my $b (@buckets) {
            $b->read(my $out);
            push @received, $out;
        }

        # here we used to get: two pv: "ef\0d"\0, "ef"\0, as you can see
        # the first bucket had corrupted data.
        my @expected = map { lc } @data;
        ok t_cmp \@received, \@expected, "new(PADTMP SV)";

        # this function will pass the same SV to new(), causing two
        # buckets point to the same SV, and having the latest bucket's
        # data override the previous one
        sub func {
            my $ba = shift;
            my $data = shift;
            return APR::Bucket->new($ba, lc $data);
        }

    }

    # read data is tainted
    {
        my $data = "xxx";
        my $b = APR::Bucket->new($ba, $data);
        $b->read(my $read);
        ok t_cmp $read, $data, 'new($data)';
        ok TestCommon::Utils::is_tainted($read);
    }

    # remove/destroy
    {
        my $b = APR::Bucket->new($ba, "aaa");
        # remove $b when it's not attached to anything (not sure if
        # that should be an error)
        $b->remove;
        ok 1;

        # a dangling bucket needs to be destroyed
        $b->destroy;
        ok 1;

        # real remove from bb is tested in many other filter tests
    }

    # setaside
    {
        my $data = "A" x 10;
        my $expected = $data;
        my $b = APR::Bucket->new($ba, $data);
        my $status = $b->setaside($pool);
        ok t_cmp $status, APR::Const::SUCCESS, "setaside status";
        $data =~ s/^..../BBBB/;
        $b->read(my $read);
        ok t_cmp $read, $expected,
            "data inside the setaside bucket is unaffected by " .
            "changes to the Perl variable it's created from";
        $b->destroy;
    }

    # alloc_create on out-of-scope pools
    {
        # later may move that into a dedicated bucket_alloc test
        my $ba = APR::BucketAlloc->new(APR::Pool->new);
        # here if the pool is gone of scope destroy() will segfault
        $ba->destroy;
        ok 1;
    }

    # setaside on out-of-scope pools
    {
        # note that at the moment APR internally handles the situation
        # when the pool goes out of scope, so modperl doesn't need to do
        # any special handling of the pool object passed to setaside()
        # to insure that it survives as long as $b is alive
        #
        # to make sure that this doesn't change internally in APR, the
        # sub-test remains here
        my $data = "A" x 10;
        my $orig = $data;
        my $b = APR::Bucket->new($ba, $data);
        my $status = $b->setaside(APR::Pool->new);
        ok t_cmp $status, APR::Const::SUCCESS, "setaside status";

        # try to overwrite the temp pool data
        my $table = APR::Table::make(APR::Pool->new, 50);
        $table->set($_ => $_) for 'aa'..'za';

        # now test that we are still OK
        $b->read(my $read);
        ok t_cmp $read, $data,
            "data inside the setaside bucket is not corrupted";
        $b->destroy;
    }

    $ba->destroy;
}

1;

