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

use strict;
use warnings;

use t::tfiles;

use Test::More 0.95;
use Test::Fatal;

use lib 'lib';

use IPC::System::Simple;
use autodie qw(system);
use Hash::Persistent;

# saving state (2)
subtest 'saving state' => sub {
    plan tests => 2;

    my $state = Hash::Persistent->new('tfiles/state');
    $state->{a} = 5;
    $state->{b} = {c => 6};
    $state->commit;
    undef $state;
    $state = Hash::Persistent->new('tfiles/state');
    is $state->{a}, 5, 'a key saved';
    is $state->{b}{c}, 6, 'b->c key saved';
};

subtest 'auto_commit' => sub {
    plan tests => 1;

    my $state = Hash::Persistent->new('tfiles/state', { auto_commit => 0 });
    $state->{a} = 0;
    undef $state;
    $state = Hash::Persistent->new('tfiles/state');
    is $state->{a}, 5, "commit didn't happen";
};

subtest 'keys' => sub {
    plan tests => 1;

    my $state = Hash::Persistent->new('tfiles/state');
    my @keys = sort keys %$state;
    is(($keys[0] eq 'a') && ($keys[1] eq 'b'), 1, "key names OK");
};

subtest 'mode' => sub {
    plan tests => 1;

    my $state = Hash::Persistent->new('tfiles/state', {mode => 0765});
    $state->commit;
    undef $state;

    my $mode = (stat('tfiles/state'))[2];
    is($mode & 07777, 0765, "mode set right");
};

subtest 'read_only' => sub {
    plan tests => 5;

    like(
        exception {
            my $state = Hash::Persistent->new('tfiles/state', { read_only => 1, auto_commit => 1 });
        },
        qr/Only one of .* options can be true/,
        'read_only incompatible with auto_commit'
    );
    is(
        exception {
            my $state = Hash::Persistent->new('tfiles/state', { read_only => 1, auto_commit => 0 });
        },
        undef,
        'read_only lives when auto_commit is 0'
    );
    is(
        exception {
            my $state = Hash::Persistent->new('tfiles/state', { read_only => 1 });
        },
        undef,
        'read_only lives when auto_commit is not specified'
    );

    my $state = Hash::Persistent->new('tfiles/state', { read_only => 1 });
    like(
        exception { $state->commit },
        qr/read only/,
        "read_only objects can't be commited"
    );

    undef $state;
    $state = Hash::Persistent->new('tfiles/state', { read_only => 1 });
    $state->{c} = 5;
    undef $state;
    $state = Hash::Persistent->new('tfiles/state', { read_only => 1 });
    is($state->{c}, undef, 'read_only turns auto_commit off');
};

subtest 'write_only' => sub {
    plan tests => 8;

    system("rm tfiles/state");

    my $state;
    is
        exception { $state = Hash::Persistent->new('tfiles/state', { write_only => 1, auto_commit => 0}) },
        undef,
        "no file";
    is scalar(keys %$state), 0, "no data";

    $state->{a} = "b";
    $state->commit; undef $state;
    is
        exception { $state = Hash::Persistent->new('tfiles/state', { write_only => 1, auto_commit => 0}) },
        undef,
        "write_only";
    is scalar(keys %$state), 0, "no data";

    $state->commit; undef $state;
    system('echo "{" > tfiles/state');
    is
        exception { $state = Hash::Persistent->new('tfiles/state', { write_only => 1, format => "json", auto_commit => 0}) },
        undef,
        "corrupt file";
    is scalar(keys %$state), 0, "no data";

    $state->commit; undef $state;
    system('echo -n > tfiles/state');
    is
        exception { $state = Hash::Persistent->new('tfiles/state', { write_only => 1, format => "json", auto_commit => 0}) },
        undef,
        "empty file";
    is scalar(keys %$state), 0, "no data";
    $state->commit; undef $state;
};

subtest 'lock' => sub {
    plan tests => 2;

    unless (fork) {
        my $state = Hash::Persistent->new('tfiles/state', { lock => { shared => 1} });
        sleep 2;
        exec('true');
    }
    sleep 1;
    is(Hash::Persistent->new('tfiles/state', { lock => { blocking => 0 } }), undef, "two persistents can't exist together");
    is(
        exception {
            Hash::Persistent->new('tfiles/state', { lock => { blocking => 0, shared => 1 } });
        },
        undef,
        "two shared persistents can exist together"
    );
};

subtest 'terse mode in Dumper' => sub {
    plan tests => 2;

    use Data::Dumper;
    $Data::Dumper::Terse = 1;

    my $state = Hash::Persistent->new('tfiles/terse');
    $state->{a} = 'b';
    $state->commit;
    undef $state;
    is(
        exception { $state = Hash::Persistent->new('tfiles/terse') },
        undef,
        'save/load works when terse mode is enabled in Data::Dumper globally'
    );
    is($state->{a}, 'b', 'data is correct when loading with Terse enabled');
};

subtest 'diamond references in dumper and storable formats' => sub {
    plan tests => 2;

    for my $format (qw/ dumper storable /) {
        my $state_code = sub { Hash::Persistent->new('tfiles/selfref', { format => $format }) };
        my $state = $state_code->();
        my $x = ['abc'];
        $state->{a} = [ $x, $x ];
        $state->commit;
        undef $state;

        $state = $state_code->();
        is($state->{a}[0], $state->{a}[1], "diamond refs are equal in format $format");
    }
};

subtest 'remove method' => sub {
    plan tests => 3;

    my $state = Hash::Persistent->new('tfiles/tbd');
    $state->{a} = 'b';
    $state->commit;
    ok(-e 'tfiles/tbd', 'file exists');
    $state->remove;
    ok(not(-e 'tfiles/tbd'), 'file removed');

    is_deeply {%$state}, {}, 'in-memory object is cleared too';
};

subtest 'remove method without commit' => sub {
    my $state = Hash::Persistent->new('tfiles/tbd');
    is(exception { $state->remove }, undef, 'remove does nothing if there is no file');
};

done_testing;