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

use 5.010;
use strict;
use warnings;

use File::chdir;
use File::Path qw(remove_tree);
use File::Slurp::Tiny qw(read_file write_file);
use File::Temp qw(tempdir);
use File::Write::Rotate;
use Monkey::Patch::Action qw(patch_package);

use Test::Exception;
use Test::Warnings qw(:no_end_test warnings);
use Test::More 0.98;

$ENV{TZ} = "UTC";

my $dir = tempdir(CLEANUP=>1);
$CWD = $dir;

subtest "basic" => sub {
    delete_all_files();
    my $fwr = File::Write::Rotate->new(dir=>$dir, prefix=>"a");
    $fwr->write("[1]");
    is(~~read_file("a"), "[1]");
    $fwr->write("[2]", "[3]");
    is(~~read_file("a"), "[1][2][3]");
};

subtest "binmode ':utf8'" => sub {
    delete_all_files();
    my $fwr = File::Write::Rotate->new(dir=>$dir, prefix=>"a",
        binmode => ':utf8');
    my $text = "\x{263a}";
    utf8::upgrade($text);
    my @warnings = warnings {
        use warnings;
        $fwr->write($text);
    };
    ok(!(grep { $_ =~ /wide character/i } @warnings),
        "no 'Wide character in ...' warning");
    is(~~read_file("a", binmode => ':utf8'), $text, "file contents");
};

subtest "rotate by size" => sub {
    delete_all_files();
    my $fwr = File::Write::Rotate->new(dir=>$dir, prefix=>"a", size=>3);
    is(($fwr->file_path())[1], "", "period");
    $fwr->write("[1]");
    is(~~read_file("a"), "[1]");
    $fwr->write("[2]", "[3]");
    is(~~read_file("a"), "[2][3]");
    is(~~read_file("a.1"), "[1]");
};

# just testing at some non-negligible size
subtest "rotate by size = 20Kb" => sub {
    delete_all_files();
    my $fwr = File::Write::Rotate->new(dir=>$dir, prefix=>"a", size=>20*1000);
    my $msg = "x" x 100;
    for (1..200) { $fwr->write($msg) }
    is( (-s 'a')  , 20000, 'first file exists and has 20Kb so far');
    is( (-e 'a.1'), undef, 'rotate files does not exists yet' );
    note('printing one more message to force rotation bondaries');
    $fwr->write($msg);
    is( (-s 'a')  ,   100, 'new file exists and has 100 bytes');
    is( (-s 'a.1'), 20000, 'rotate file exists and has 20Kb');
    test_gzip($fwr, ['a.1']);
};

subtest "rotate by period, daily" => sub {
    delete_all_files();
    my $ph;
    $ph = set_time_to(1356090474); # 2012-12-21 @UTC
    my $fwr = File::Write::Rotate->new(dir=>$dir, prefix=>"a", period=>"daily");
    is(($fwr->file_path())[1], "2012-12-21", "period");
    $fwr->write("[1]");
    is(~~read_file("a.2012-12-21"), "[1]", 'got expected content in the file (1)');
    $fwr->write("[2]", "[3]");
    is(~~read_file("a.2012-12-21"), "[1][2][3]", 'got expected content in the file (2)');
    $ph = set_time_to(1356090474 + 86400); # 2012-12-22 @UTC
    $fwr->write("[4]");
    is(~~read_file("a.2012-12-22"), "[4]", 'got expected content in the file (3)');
    #list_files();
    test_gzip($fwr, ['a.2012-12-21']);
};

subtest "rotate by period, monthly" => sub {
    delete_all_files();
    my $ph;
    $ph = set_time_to(1356090474); # 2012-12-21 @UTC
    my $fwr = File::Write::Rotate->new(dir=>$dir,
                                       prefix=>"a", period=>"monthly");
    is(($fwr->file_path())[1], "2012-12", "period");
    $fwr->write("[1]");
    is(~~read_file("a.2012-12"), "[1]");
    $fwr->write("[2]", "[3]");
    is(~~read_file("a.2012-12"), "[1][2][3]");

    $ph = set_time_to(1356090474 + 31*86400); # 2013-01-21 @UTC
    $fwr->write("[4]");
    is(~~read_file("a.2013-01"), "[4]");
    test_gzip($fwr, ['a.2012-12']);
};

subtest "rotate by period, yearly" => sub {
    delete_all_files();
    my $ph;
    $ph = set_time_to(1356090474); # 2012-12-21 @UTC
    my $fwr = File::Write::Rotate->new(dir=>$dir,
                                       prefix=>"a", period=>"year");
    is(($fwr->file_path())[1], "2012", "period");
    $fwr->write("[1]");
    is(~~read_file("a.2012"), "[1]");
    $fwr->write("[2]", "[3]");
    is(~~read_file("a.2012"), "[1][2][3]");

    $ph = set_time_to(1356090474 + 31*86400); # 2013-01-21 @UTC
    $fwr->write("[4]");
    is(~~read_file("a.2013"), "[4]");
    test_gzip($fwr, ['a.2012']);
};

subtest "rotate by period + size, suffix" => sub {
    delete_all_files();
    my $ph;
    $ph = set_time_to(1356090474); # 2012-12-21 @UTC
    my $fwr = File::Write::Rotate->new(dir=>$dir, prefix=>"a", suffix=>".log",
                                       size=>3, period=>"daily");
    is(($fwr->file_path())[1], "2012-12-21", "period");
    $fwr->write("[1]");
    is(~~read_file("a.2012-12-21.log"), "[1]");
    $fwr->write("[2]", "[3]");
    is(~~read_file("a.2012-12-21.log"), "[2][3]");
    is(~~read_file("a.2012-12-21.log.1"), "[1]");
    $fwr->write("[4]");
    is(~~read_file("a.2012-12-21.log"), "[4]");
    is(~~read_file("a.2012-12-21.log.1"), "[2][3]");
    is(~~read_file("a.2012-12-21.log.2"), "[1]");

    $ph = set_time_to(1356090474 + 86400); # 2012-12-22 @UTC
    $fwr->write("[5]");
    is(~~read_file("a.2012-12-22.log"), "[5]");
    test_gzip($fwr, ['a.2012-12-21.log', 'a.2012-12-21.log.1', 'a.2012-12-21.log.2']);
};

subtest "two writers, one rotates" => sub {
    delete_all_files();
    my $fwr1 = File::Write::Rotate->new(dir=>$dir, prefix=>"a");
    my $fwr2 = File::Write::Rotate->new(dir=>$dir, prefix=>"a", size=>3);
    $fwr1->write("[1.1]");
    is(~~read_file("a"), "[1.1]");
    $fwr2->write("[2.1]");
    is(~~read_file("a"), "[2.1]");
    is(~~read_file("a.1"), "[1.1]");
    $fwr1->write("[1.2]");
    is(~~read_file("a"), "[2.1][1.2]");
    is(~~read_file("a.1"), "[1.1]");
    test_gzip($fwr1, ['a.1']);
};

# if FWR only rotates after second write(), then there will be cases where the
# file won't get rotated at all.
subtest "rotate on first write()" => sub {
    delete_all_files();
    write_file("$dir/a", "123");
    my $fwr = File::Write::Rotate->new(dir=>$dir, prefix=>"a", size=>3);
    $fwr->write("[1]");
    is(~~read_file("a"), "[1]");
    is(~~read_file("a.1"), "123");
    test_gzip($fwr, ['a.1']);
};

subtest "buffer (success)" => sub {
    delete_all_files();
    my $fwr = File::Write::Rotate->new(dir=>$dir, prefix=>"a", buffer_size=>2);
    $fwr->{_hook_before_print} = sub { die };

    lives_ok { $fwr->write("[1]") } "first message to buffer";
    lives_ok { $fwr->write("[2]") } "second message to buffer";

    undef $fwr->{_hook_before_print};

    $fwr->write("[3]");

    is(~~read_file("a"), "[1][2][3]", "buffered messages gets logged");
    $fwr->write("[4]");
    is(~~read_file("a"), "[1][2][3][4]", "buffered is emptied");
};

subtest "buffer (failed, full), buffer_size attribute" => sub {
    delete_all_files();
    my $fwr = File::Write::Rotate->new(dir=>$dir, prefix=>"a");
    $fwr->buffer_size(2);
    is($fwr->buffer_size, 2, 'buffer_size()');

    local $fwr->{_hook_before_print} = sub { die };

    lives_ok  { $fwr->write("[1]") } "first message to buffer";
    lives_ok  { $fwr->write("[2]") } "second message to buffer";
    throws_ok { $fwr->write("[3]") } qr/\Q[1][2][3]\E/, "buffer is full";
};

done_testing;

if (Test::More->builder->is_passing) {
    $CWD = "/";
} else {
    diag "there are failing tests, not deleting test data dir $dir";
}

sub delete_all_files {
    # remove all files first
    opendir my($dh), ".";
    while (my $e = readdir($dh)) {
        next if $e eq '.' || $e eq '..';
        remove_tree($e);
    }
}

sub list_files {
    opendir my $dh, ".";
    diag explain [grep {$_ ne '.' && $_ ne '..'} readdir $dh];
}

our $Time;
sub _time() { $Time }

sub set_time_to {
    $Time = shift;
    my $ph = patch_package("File::Write::Rotate", 'time', 'replace', \&_time);
    return $ph;
}

sub test_gzip {
    my $fwr = shift;
    my $files_ref = shift;
    my @sizes;

    for my $filename(@{$files_ref}) {
        push(@sizes, (-s $filename));
    }

    my $ret = $fwr->compress;
    ok($ret, 'compress method returns true');

  SKIP: {
        skip 'compress method did not return true', (2 * scalar(@{$files_ref})) unless ($ret);
        my $counter = 0;

        for my $filename(@{$files_ref}) {
            my $orig_size = $sizes[$counter];
            $counter++;
    	    my $new_file = $filename . '.gz';
            # sane value
            my $comp_size = 0;
            ok( $comp_size = (-s $new_file), "rotated file $filename was compressed");
            #if (defined($comp_size)) {
            #    cmp_ok($comp_size, '<', $orig_size, 'compressed file size is smaller than before compression');
            #} else {
            #    fail("there is no compressed $filename, cannot compare sizes");
            #}
        }
    }
}