format: fix render_bytes with CLIFormatter
[pve-common.git] / test / lock_file.pl
1 #!/usr/bin/perl
2
3 use lib '../src';
4 use strict;
5 use warnings;
6
7 use Socket;
8 use POSIX (); # don't import assert()
9
10 use PVE::Tools 'lock_file_full';
11
12 my $name = "test.lockfile.$$-";
13
14 END {
15 system("rm $name*");
16 };
17
18 # Utilities:
19
20 sub forked($$) {
21 my ($code1, $code2) = @_;
22
23 pipe(my $except_r, my $except_w) or die "pipe: $!\n";
24
25 my $pid = fork();
26 die "fork failed: $!\n" if !defined($pid);
27
28 if ($pid == 0) {
29 close($except_r);
30 eval { $code1->() };
31 if ($@) {
32 print {$except_w} $@;
33 $except_w->flush();
34 POSIX::_exit(1);
35 }
36 POSIX::_exit(0);
37 }
38 close($except_w);
39
40 eval { $code2->() };
41 my $err = $@;
42 if ($err) {
43 kill(15, $pid);
44 } else {
45 my $err = do { local $/ = undef; <$except_r> };
46 }
47 die "interrupted\n" if waitpid($pid, 0) != $pid;
48 die $err if $err;
49
50 # Check exit code:
51 my $status = POSIX::WEXITSTATUS($?);
52 if ($? == -1) {
53 die "failed to execute\n";
54 } elsif (POSIX::WIFSIGNALED($?)) {
55 my $sig = POSIX::WTERMSIG($?);
56 die "got signal $sig\n";
57 } elsif ($status != 0) {
58 die "exit code $status\n";
59 }
60 }
61
62 # Book-keeping:
63
64 my %_ran;
65 sub new {
66 %_ran = ();
67 }
68 sub ran {
69 my ($what) = @_;
70 $_ran{$what} = 1;
71 }
72 sub assert {
73 my ($what) = @_;
74 die "code didn't run: $what\n" if !$_ran{$what};
75 }
76 sub assert_not {
77 my ($what) = @_;
78 die "code shouldn't have run: $what\n" if $_ran{$what};
79 }
80
81 # Regular lock:
82 new();
83 lock_file_full($name, 10, 0, sub { ran('single lock') });
84 assert('single lock');
85
86 # Lock multiple times in a row:
87 new();
88 lock_file_full($name, 10, 0, sub { ran('lock A') });
89 assert('lock A');
90 lock_file_full($name, 10, 0, sub { ran('lock B') });
91 assert('lock B');
92
93 # Nested lock:
94 new();
95 lock_file_full($name, 10, 0, sub {
96 ran('lock A');
97 lock_file_full($name, 10, 0, sub { ran('lock B') });
98 assert('lock B');
99 ran('lock C');
100 });
101 assert('lock A');
102 assert('lock B');
103 assert('lock C');
104
105 # Independent locks:
106 new();
107 lock_file_full($name, 10, 0, sub {
108 ran('lock A');
109 # locks file "${name}2"
110 lock_file_full($name.2, 10, 0, sub { ran('lock B') });
111 assert('lock B');
112 ran('lock C');
113 });
114 assert('lock A');
115 assert('lock B');
116 assert('lock C');
117
118 # Does it actually lock? (shared=0)
119 # Can we get two simultaneous shared locks? (shared=1)
120 sub forktest1($) {
121 my ($shared) = @_;
122 new();
123 # socket pair for synchronization
124 socketpair(my $fmain, my $fother, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
125 or die "socketpair(): $!\n";
126 forked sub {
127 # other side
128 close($fmain);
129 my $line;
130 lock_file_full($name, 60, $shared, sub {
131 ran('other side');
132 # tell parent we've acquired the lock
133 print {$fother} "1\n";
134 $fother->flush();
135 # wait for parent to be done trying to lock
136 $line = <$fother>;
137 });
138 die $@ if $@;
139 die "parent failed\n" if !$line || $line ne "2\n";
140 assert('other side');
141 }, sub {
142 # main process
143 # Wait for our child to lock:
144 close($fother);
145 my $line = <$fmain>;
146 die "child failed to acquire a lock\n" if !$line || $line ne "1\n";
147 lock_file_full($name, 1, $shared, sub {
148 ran('local side');
149 });
150 if ($shared) {
151 assert('local side');
152 } else {
153 assert_not('local side');
154 }
155 print {$fmain} "2\n";
156 $fmain->flush();
157 };
158 close($fmain);
159 }
160 forktest1(0);
161 forktest1(1);