The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

#!perl
BEGIN
{
use strict;
use warnings;
use lib './lib';
use vars qw( $DEBUG $SERIALISER );
use Errno;
# use Nice::Try;
use Test2::V0;
use POSIX ":sys_wait_h";
$DEBUG = exists( $ENV{AUTHOR_TESTING} ) ? $ENV{AUTHOR_TESTING} : 0;
$SERIALISER = exists( $ENV{SERIALISER} ) ? $ENV{SERIALISER} : 'storable';
};
use strict;
eval "use Cache::FastMmap 1.57;";
plan( skip_all => "Cache::FastMmap 1.57 required for testing mmap with Cache::FastMmap" ) if( $@ );
SKIP:
{
use strict;
use warnings;
my $cache = Module::Generic::File::Mmap->new(
debug => $DEBUG,
key => 'test_key',
serialiser => $SERIALISER,
size => 2048,
# destroy => 1,
destroy => 0,
mode => 0666,
);
my $s = $cache->open({ mode => 'w' });
defined( $s ) || do
{
diag( "Failed to open shared cache: ", $cache->error ) if( $DEBUG );
};
local $SIG{__DIE__} = sub
{
diag( "Got error: ", join( '', @_ ), ". Cleaning up shared cache." ) if( $DEBUG );
$s->unlock;
$s->remove;
};
skip( "Failed to create shared cache object: " . $cache->error, 21 ) if( !defined( $s ) );
ok( defined( $s ), 'open return value' );
skip( "Failed to create shared cache object: " . Module::Generic::File::Mmap->error, 21 ) if( !defined( $s ) );
isa_ok( $s => ['Module::Generic::File::Mmap'] );
isa_ok( $s, ['Module::Generic::File::Mmap'], 'Shared cache object' );
my $test_data = { name => 'John Doe', location => 'Tokyo' };
my $cache_object = $s->write( $test_data );
ok( defined( $cache_object ), 'write' );
ok( overload::StrVal( $s ) eq overload::StrVal( $cache_object ), 'write return value' );
my $buffer = $s->read;
ok( defined( $buffer ), 'read no argument' );
ok( ref( $buffer ) eq 'HASH', 'read buffer data integrity' );
if( ref( $buffer ) eq 'HASH' && $buffer->{name} eq 'John Doe' && $buffer->{location} eq 'Tokyo' )
{
pass( 'read data check' );
}
else
{
fail( 'read data check' );
}
my $os = lc( $^O );
if( $os eq 'amigaos' || $os eq 'riscos' || $os eq 'vms' )
{
skip( "Your system does not support fork()", 1 );
}
# Block signal for fork
my $sigset = POSIX::SigSet->new( POSIX::SIGINT );
POSIX::sigprocmask( POSIX::SIG_BLOCK, $sigset ) ||
bail_out( "Cannot block SIGINT for fork: $!" );
select((select(STDOUT), $|=1)[0]);
select((select(STDERR), $|=1)[0]);
my $pid = fork();
skip( "Unable to fork: $!", 9 ) unless( defined( $pid ) );
if( $pid )
{
POSIX::sigprocmask( POSIX::SIG_UNBLOCK, $sigset ) ||
bail_out( "Cannot unblock SIGINT for fork: $!" );
# Is the child still there?
if( kill( 0 => $pid ) || $!{EPERM} )
{
diag( "Child process with pid '$pid' is still running, waiting for it to complete." ) if( $DEBUG );
# Blocking wait
waitpid( $pid, 0 );
my $exit_status = ( $? >> 8 );
my $exit_signal = $? & 127;
my $has_coredump = ( $? & 128 );
diag( "Child process exited with value $?" ) if( $DEBUG );
if( WIFEXITED($?) )
{
diag( "Child with pid '$pid' exited with bit value '$?' (exit=${exit_status}, signal=${exit_signal}, coredump=${has_coredump})." ) if( $DEBUG );
}
else
{
diag( "Child with pid '$pid' exited with bit value '$?' -> $!" ) if( $DEBUG );
}
is( $exit_status, 0, 'sub process shared cache access' );
my $data = $s->read;
ok( ref( $data ) eq 'HASH', 'shared updated data type' );
if( ref( $data ) eq 'HASH' )
{
ok( ( exists( $data->{year} ) && defined( $data->{year} ) && int( $data->{year} ) == 2021 ), 'updated data value' );
}
else
{
fail( 'updated data value' );
}
my $data2;
$s->read( $data2 );
ok( ref( $data2 ) eq 'HASH', 'different read usage' );
if( ref( $data ) eq 'HASH' )
{
ok( ( exists( $data2->{year} ) && defined( $data2->{year} ) && int( $data2->{year} ) == 2021 ), 'different read data check' );
}
else
{
fail( 'different read data check' );
}
# lock is actually a noop
my $rv = $s->lock || diag( "Unable to lock: ", $s->error );
ok( $rv, 'lock' );
# locked is actually a noop too
ok( $s->locked, 'locked' );
if( ref( $data ) eq 'HASH' )
{
$data->{test} = 'ok';
ok( defined( $s->write( $data ) ), 'updated data with lock' );
}
else
{
fail( 'updated data with lock' );
}
# unlock is actually a noop too
ok( defined( $s->unlock ), 'unlock' );
ok( defined( $s->remove ), 'remove' );
# ok( !$s->exists, 'exists after remove' );
}
else
{
diag( "Child process with pid '$pid' is already completed." ) if( $DEBUG );
pass( "sub process exited rapidly" );
}
}
elsif( $pid == 0 )
{
my $cache2 = Module::Generic::File::Mmap->new(
debug => $DEBUG,
key => 'test_key',
serialiser => $SERIALISER,
mode => 0666,
);
my $c = $cache2->open || do
{
diag( "[CHILD] cannot open: ", $cache2->error ) if( $DEBUG );
exit(1);
};
my $ref = $c->read;
defined( $ref ) || do
{
diag( "[CHILD] read returned undef: ", $c->error ) if( $DEBUG );
exit(1);
};
ref( $ref ) eq 'HASH' || do
{
diag( "[CHILD] Shared memory data ($ref) is not an hash reference." ) if( $DEBUG );
exit(1);
};
# $ref = {};
$ref->{year} = 2021;
defined( $c->write( $ref ) ) || do
{
diag( "[CHILD] Unable to write to shared memory: ", $c->error ) if( $DEBUG );
exit(1);
};
exit(0);
}
};
subtest 'serialisation with cbor' => sub
{
SKIP:
{
eval "use CBOR::XS 1.86;";
skip( "CBOR::XS 1.86 required for testing serialisation with CBOR", 1 ) if( $@ );
my $cbor = CBOR::XS->new->allow_sharing;
my $cache = Module::Generic::File::Mmap->new(
debug => $DEBUG,
key => 'test_key',
serialiser => $SERIALISER,
size => 2048,
# destroy => 1,
destroy => 0,
mode => 0666,
) || die( Module::Generic::File::Mmap->error );
my $s = $cache->open({ mode => 'w' });
diag( "Error opening mmap cache file: ", $cache->error ) if( $DEBUG && !defined( $s ) );
ok( $s, 'mmap cache opened' );
skip( "Failed to instantiate a mmap object.", 6 ) if( !defined( $s ) );
ok( $s->write({ name => 'John Doe', location => 'Tokyo' }), 'write to cache mmap' );
eval
{
my $serial = $cbor->encode( $s );
ok( ( defined( $serial ) && length( $serial ) ), 'object serialised' );
my $obj = $cbor->decode( $serial );
isa_ok( $obj => ['Module::Generic::File::Mmap'], 'deserialised object is an Module::Generic::File::Mmap object' );
ok( $s->cache_file eq $obj->cache_file, 'cache mmap file is the same' );
is( $s->cache_file->length, $obj->cache_file->length, 'cache mmap file has same size as before' );
my $h = $obj->read;
is( $h->{name}, 'John Doe', 'stored data matches' );
};
if( $@ )
{
fail( "Error serialising or deserialising: $@" );
}
};
};
subtest 'serialisation with sereal' => sub
{
SKIP:
{
eval "use Sereal 4.023;";
skip( "Sereal 4.023 required for testing serialisation with Sereal", 1 ) if( $@ );
my $enc = Sereal::Encoder->new({ freeze_callbacks => 1 });
my $dec = Sereal::Decoder->new;
my $cache = Module::Generic::File::Mmap->new(
debug => $DEBUG,
key => 'test_key',
serialiser => $SERIALISER,
size => 2048,
# destroy => 1,
destroy => 0,
mode => 0666,
) || die( Module::Generic::File::Mmap->error );
my $s = $cache->open({ mode => 'w' });
ok( $s->write({ name => 'John Doe', location => 'Tokyo' }), 'write to cache mmap' );
eval
{
my $serial = $enc->encode( $s );
ok( ( defined( $serial ) && length( $serial ) ), 'object serialised' );
my $obj = $dec->decode( $serial );
isa_ok( $obj => ['Module::Generic::File::Mmap'], 'deserialised object is an Module::Generic::File::Mmap object' );
ok( $s->cache_file eq $obj->cache_file, 'cache mmap file is the same' );
is( $s->cache_file->length, $obj->cache_file->length, 'cache mmap file has same size as before' );
my $h = $obj->read;
is( $h->{name}, 'John Doe', 'stored data matches' );
};
if( $@ )
{
fail( "Error serialising or deserialising: $@" );
}
};
};
subtest 'serialisation with storable' => sub
{
SKIP:
{
eval "use Storable::Improved v0.1.3;";
skip( "Storable::Improved v0.1.3 required for testing serialisation with Storable", 1 ) if( $@ );
my $cache = Module::Generic::File::Mmap->new(
debug => $DEBUG,
key => 'test_key',
serialiser => $SERIALISER,
size => 2048,
# destroy => 1,
destroy => 0,
mode => 0666,
) || die( Module::Generic::File::Mmap->error );
my $s = $cache->open({ mode => 'w' });
ok( $s->write({ name => 'John Doe', location => 'Tokyo' }), 'write to cache mmap' );
eval
{
my $serial = Storable::Improved::freeze( $s );
ok( ( defined( $serial ) && length( $serial ) ), 'object serialised' );
my $obj = Storable::Improved::thaw( $serial );
isa_ok( $obj => ['Module::Generic::File::Mmap'], 'deserialised object is an Module::Generic::File::Mmap object' );
ok( $s->cache_file eq $obj->cache_file, 'cache mmap file is the same' );
is( $s->cache_file->length, $obj->cache_file->length, 'cache mmap file has same size as before' );
my $h = $obj->read;
is( $h->{name}, 'John Doe', 'stored data matches' );
};
if( $@ )
{
fail( "Error serialising or deserialising: $@" );
}
};
};
done_testing();
__END__