File: file-locked.t

package info (click to toggle)
liblog-dispatch-perl 2.71-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 560 kB
  • sloc: perl: 1,457; sh: 24; makefile: 2
file content (149 lines) | stat: -rw-r--r-- 3,230 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
use strict;
use warnings;

use Test::More;

use File::Spec;
use File::Temp qw( tempdir );
use Log::Dispatch;
use Log::Dispatch::File::Locked;
use POSIX qw( :sys_wait_h );
use Try::Tiny;

my $ChildCount = 10;
for my $close_after_write ( 0, 1 ) {
    my @v = _run_children($close_after_write);
    subtest(
        "close_after_write = $close_after_write",
        sub {
            _test_file_locked(@v);
        }
    );
}

done_testing();

sub _run_children {
    my $close_after_write = shift;

    my $dir  = tempdir( CLEANUP => 1 );
    my $file = File::Spec->catfile( $dir, 'lock-test.log' );

    my $logger = _dispatch_for_file( $close_after_write, $file );

    my %pids;
    for ( 1 .. $ChildCount ) {
        if ( my $pid = fork ) {
            $pids{$pid} = 1;
        }
        else {
            _write_to_file( $close_after_write, $file );
            exit 0;
        }
    }

    my %exit_status;
    try {
        local $SIG{ALRM}
            = sub { die 'Waited 30 seconds for children to exit' };
        alarm 30;

        while ( keys %pids ) {
            my $pid = waitpid( -1, WNOHANG );
            if ( delete $pids{$pid} ) {
                $exit_status{$pid} = $?;
            }
        }
    };

    return ( $file, $@, \%exit_status );
}

sub _write_to_file {
    my $close_after_write = shift;
    my $file              = shift;

    my $dispatch = _dispatch_for_file( $close_after_write, $file );

    # The sleep makes a deadlock much more likely if the locking logic is not
    # working correctly. Without it each child process runs so quickly that
    # they are unlikely to step on each other.
    $dispatch->info(1);
    sleep 1;
    $dispatch->info(2);
    $dispatch->info(3);

    return;
}

sub _dispatch_for_file {
    my $close_after_write = shift;
    my $file              = shift;

    return Log::Dispatch->new(
        outputs => [
            [
                'File::Locked',
                filename          => $file,
                mode              => 'append',
                close_after_write => $close_after_write,
                min_level         => 'debug',
                newline           => 1,
            ]
        ],
    );
}

sub _test_file_locked {
    my $file  = shift;
    my $exc   = shift;
    my $exits = shift;

    is(
        $exc,
        q{},
        'no exception forking children and writing to file'
    );

    is(
        keys %{$exits},
        $ChildCount,
        "$ChildCount children exited",
    );

    for my $pid ( keys %{$exits} ) {
        is(
            $exits->{$pid},
            0,
            "$pid exited with 0"
        );
    }

    _test_file_content($file);
}

sub _test_file_content {
    my $file = shift;

    open my $fh, '<', $file
        or die "Cannot read $file: $!";
    my @lines;
    while ( defined( my $line = <$fh> ) ) {
        chomp $line;
        push @lines, $line;
    }

    close $fh or die $!;

    return if is_deeply(
        [ sort @lines ],
        [ (1) x $ChildCount, (2) x $ChildCount, (3) x $ChildCount ],
        'file contains expected content'
    );

    open my $diag_fh, '<', $file or die $!;
    diag(
        do { local $/ = undef; <$diag_fh> }
    );
    close $diag_fh or die $!;
}