blob: cb75e0580545721f255379322e417bbaa5ba2acc (
plain)
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
|
#!/bin/sh
# Exercise tail's behavior regarding missing files with/without --retry.
# Copyright (C) 2013-2023 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
print_ver_ tail
# Function to count number of lines from tail
# while ignoring transient errors due to resource limits
countlines_ ()
{
grep -Ev 'inotify (resources exhausted|cannot be used)' out | wc -l
}
# Function to check the expected line count in 'out'.
# Called via retry_delay_(). Sleep some time - see retry_delay_() - if the
# line count is still smaller than expected.
wait4lines_ ()
{
local delay=$1
local elc=$2 # Expected line count.
[ "$(countlines_)" -ge "$elc" ] || { sleep $delay; return 1; }
}
# Terminate any background tail process
cleanup_() { kill $pid 2>/dev/null && wait $pid; }
# Speedup the non inotify case
fastpoll='-s.1 --max-unchanged-stats=1'
# === Test:
# Retry without --follow results in a warning.
touch file
tail --retry file > out 2>&1 || fail=1
[ "$(countlines_)" = 1 ] || { cat out; fail=1; }
grep -F 'tail: warning: --retry ignored' out || { cat out; fail=1; }
# === Test:
# The same with a missing file: expect error message and exit 1.
returns_ 1 tail --retry missing > out 2>&1 || fail=1
[ "$(countlines_)" = 2 ] || { cat out; fail=1; }
grep -F 'tail: warning: --retry ignored' out || { cat out; fail=1; }
for mode in '' '---disable-inotify'; do
# === Test:
# Ensure that "tail --retry --follow=name" waits for the file to appear.
# Clear 'out' so that we can check its contents without races
>out || framework_failure_
timeout 10 \
tail $mode $fastpoll --follow=name --retry missing >out 2>&1 & pid=$!
# Wait for "cannot open" error.
retry_delay_ wait4lines_ .1 6 1 || { cat out; fail=1; }
echo "X" > missing || framework_failure_
# Wait for the expected output.
retry_delay_ wait4lines_ .1 6 3 || { cat out; fail=1; }
cleanup_
# Expect 3 lines in the output file.
[ "$(countlines_)" = 3 ] || { fail=1; cat out; }
grep -F 'cannot open' out || { fail=1; cat out; }
grep -F 'has appeared' out || { fail=1; cat out; }
grep '^X$' out || { fail=1; cat out; }
rm -f missing out || framework_failure_
# === Test:
# Ensure that "tail --retry --follow=descriptor" waits for the file to appear.
# tail-8.21 failed at this (since the implementation of the inotify support).
timeout 10 \
tail $mode $fastpoll --follow=descriptor --retry missing >out 2>&1 & pid=$!
# Wait for "cannot open" error.
retry_delay_ wait4lines_ .1 6 2 || { cat out; fail=1; }
echo "X1" > missing || framework_failure_
# Wait for the expected output.
retry_delay_ wait4lines_ .1 6 4 || { cat out; fail=1; }
# Ensure truncation is detected
# tail-8.25 failed at this (as assumed non file and went into blocking mode)
echo "X" > missing || framework_failure_
retry_delay_ wait4lines_ .1 6 6 || { cat out; fail=1; }
cleanup_
[ "$(countlines_)" = 6 ] || { fail=1; cat out; }
grep -F 'retry only effective for the initial open' out \
|| { fail=1; cat out; }
grep -F 'cannot open' out || { fail=1; cat out; }
grep -F 'has appeared' out || { fail=1; cat out; }
grep '^X1$' out || { fail=1; cat out; }
grep -F 'file truncated' out || { fail=1; cat out; }
grep '^X$' out || { fail=1; cat out; }
rm -f missing out || framework_failure_
# === Test:
# Ensure that tail --follow=descriptor --retry exits when the file appears
# untailable. Expect exit status 1.
timeout 10 \
tail $mode $fastpoll --follow=descriptor --retry missing >out 2>&1 & pid=$!
# Wait for "cannot open" error.
retry_delay_ wait4lines_ .1 6 2 || { cat out; fail=1; }
mkdir missing || framework_failure_ # Create untailable
# Wait for the expected output.
retry_delay_ wait4lines_ .1 6 4 || { cat out; fail=1; }
wait $pid
rc=$?
[ "$(countlines_)" = 4 ] || { fail=1; cat out; }
grep -F 'retry only effective for the initial open' out \
|| { fail=1; cat out; }
grep -F 'cannot open' out || { fail=1; cat out; }
grep -F 'replaced with an untailable file' out || { fail=1; cat out; }
grep -F 'no files remaining' out || { fail=1; cat out; }
[ $rc = 1 ] || { fail=1; cat out; }
rm -fd missing out || framework_failure_
# === Test:
# Ensure that --follow=descriptor (without --retry) does *not* try
# to open a file after an initial fail, even when there are other
# tailable files. This was an issue in <= 8.25.
touch existing || framework_failure_
tail $mode $fastpoll --follow=descriptor missing existing >out 2>&1 & pid=$!
retry_delay_ wait4lines_ .1 6 2 || { cat out; fail=1; }
[ "$(countlines_)" = 2 ] || { fail=1; cat out; }
grep -F 'cannot open' out || { fail=1; cat out; }
echo "Y" > missing || framework_failure_
echo "X" > existing || framework_failure_
retry_delay_ wait4lines_ .1 6 3 || { cat out; fail=1; }
[ "$(countlines_)" = 3 ] || { fail=1; cat out; }
grep '^X$' out || { fail=1; cat out; }
grep '^Y$' out && { fail=1; cat out; }
cleanup_
rm -f missing out existing || framework_failure_
# === Test:
# Ensure that --follow=descriptor (without --retry) does *not wait* for the
# file to appear. Expect 2 lines in the output file ("cannot open" +
# "no files remaining") and exit status 1.
returns_ 1 tail $mode --follow=descriptor missing >out 2>&1 || fail=1
[ "$(countlines_)" = 2 ] || { fail=1; cat out; }
grep -F 'cannot open' out || { fail=1; cat out; }
grep -F 'no files remaining' out || { fail=1; cat out; }
rm -f out || framework_failure_
# === Test:
# Likewise for --follow=name (without --retry).
returns_ 1 tail $mode --follow=name missing >out 2>&1 || fail=1
[ "$(countlines_)" = 2 ] || { fail=1; cat out; }
grep -F 'cannot open' out || { fail=1; cat out; }
grep -F 'no files remaining' out || { fail=1; cat out; }
rm -f out || framework_failure_
# === Test:
# Ensure that tail -F retries when the file is initially untailable.
if ! cat . >/dev/null; then
mkdir untailable || framework_failure_
timeout 10 \
tail $mode $fastpoll -F untailable >out 2>&1 & pid=$!
# Wait for "cannot follow" error.
retry_delay_ wait4lines_ .1 6 2 || { cat out; fail=1; }
{ rmdir untailable; echo foo > untailable; } || framework_failure_
# Wait for the expected output.
retry_delay_ wait4lines_ .1 6 4 || { cat out; fail=1; }
cleanup_
[ "$(countlines_)" = 4 ] || { fail=1; cat out; }
grep -F 'cannot follow' out || { fail=1; cat out; }
# The first is the common case, "has appeared" arises with slow rmdir.
grep -E 'become accessible|has appeared' out || { fail=1; cat out; }
grep -F 'giving up' out && { fail=1; cat out; }
grep -F 'foo' out || { fail=1; cat out; }
rm -fd untailable out || framework_failure_
fi
done
Exit $fail
|