#!/bin/sh
# Checks the page cache and the scan cache. A page is kept in a slot named
# after its request, filled under a lock that a second request must respect,
# and served stale while another request refreshes it, within the ttls and
# size bounds cgitrc sets. A scan-path list is kept in a file named after the
# settings that shaped it and rebuilt in the background once it expires.
test_description='Validate cache'
. ./setup.sh
# The counting tests below visit the same pages, the index plus six pages in
# each of two repositories, so the counts they expect stay tied to this one
# list of thirteen requests.
request_all_pages() {
cgit_ok "$1" "url=" >/dev/null &&
for repo in foo bar
do
for page in "" /refs /tree /log /diff /patch
do
cgit_ok "$1" "url=$repo$page" >/dev/null || return 1
done
done
}
# The PERL prerequisite says only that the build allows perl, so the tests
# with perl helpers ask the interpreter itself, and skip where it is missing.
test_lazy_prereq PERL_RUNS '
"$PERL_PATH" -e 1
'
# Runs a command while holding the flock on the file named first, as another
# request at work on the same lock would.
write_hold_lock() {
write_script hold-lock "$PERL_PATH" <<-\EOF
use Fcntl qw(:flock);
open(my $lock, ">>", shift) or die "$!\n";
flock($lock, LOCK_EX | LOCK_NB) or die "$!\n";
exit(system(@ARGV) ? 1 : 0);
EOF
}
test_expect_success 'a cache-size of 0 leaves no slot behind' '
rm -f cache/* &&
sed "s/^cache-size=.*/cache-size=0/" cgitrc >size0rc &&
request_all_pages size0rc &&
test_dir_is_empty cache
'
# With one slot every request lands on the same file, so the key it holds is
# all that keeps one page from being served for another.
test_expect_success 'one slot serves each request its own page' '
rm -f cache/* &&
sed "s/^cache-size=.*/cache-size=1/" cgitrc >size1rc &&
cgit_ok size1rc "url=foo/log/" >/dev/null &&
cgit_rc size1rc "url=bar/refs/" >refs.out 2>refs.err &&
test_grep "^Status: 200" refs.out &&
test_grep "data-page=.refs. data-repo=.bar." refs.out &&
test_grep "
log.out 2>log.err &&
test_grep "data-page=.log. data-repo=.foo." log.out &&
test_grep "output &&
test_line_count = 1 output
'
# The listing is a page like any other and takes a slot of its own, so a
# fresh render of it would list fourteen slots. Thirteen on the second
# request shows the first was replayed, which a dynamic ttl that never
# expires keeps true however slow the run.
test_expect_success 'a full table gives each request a slot of its own' '
rm -f cache/* &&
{
echo "cache-dynamic-ttl=-1" &&
sed "s/^cache-size=.*/cache-size=1021/" cgitrc
} >size1021rc &&
request_all_pages size1021rc &&
ls cache >output &&
test_line_count = 13 output &&
cgit_ok size1021rc "url=foo/ls_cache" >output.full &&
strip_headers output &&
test_line_count = 13 output &&
ls cache >output &&
test_line_count = 14 output &&
cgit_ok size1021rc "url=foo/ls_cache" >output.second &&
test_cmp output.full output.second
'
# The listing names the cache path and the urls other visitors asked for, so
# it answers only where enable-cache-list is set, and as plain text.
test_expect_success 'the cache listing answers only where it is enabled' '
sed -e "/^enable-cache-list=/d" -e "s/^cache-size=.*/cache-size=0/" cgitrc >nolistrc &&
cgit_rc nolistrc "url=foo/ls_cache" >nolist.out &&
test_grep "^Status: 404" nolist.out &&
test_grep ! -F "$PWD/cache" nolist.out &&
sed "s/^cache-size=.*/cache-size=0/" cgitrc >listrc &&
cgit_ok listrc "url=foo/ls_cache" >list.out &&
test_grep "^Content-Type: text/plain" list.out &&
test_grep -F "$PWD/cache/" list.out
'
test_expect_success 'a cache listing without its directory says so' '
sed -e "s|^cache-root=.*|cache-root=$PWD/no-such-cache|" -e "s/^cache-size=.*/cache-size=0/" cgitrc >nocacherc &&
cgit_rc nocacherc "url=foo/ls_cache" >nocache.out 2>nocache.err &&
test_grep "^Status: 200" nocache.out &&
test_grep -F "Unable to open $PWD/no-such-cache: No such file or directory" nocache.err
'
# Page output is buffered, so a slot only holds the whole page if the buffer is
# emptied before the size of what was generated is taken. The blob set up here
# is larger than one buffer, so a missing flush would leave the cached copy cut
# short.
test_expect_success 'set up a repo with a page larger than the output buffer' '
mkrepo repos/bigpage 1 &&
(
cd repos/bigpage &&
awk "BEGIN{for(i=0;i<20000;i++) print \"line \" i \" of the big file\"}" >big.txt &&
git add big.txt &&
git commit -m "add big.txt"
) &&
{
echo "virtual-root=/" &&
echo "cache-root=$PWD/cache2" &&
echo "cache-size=64" &&
echo "enable-tree-linenumbers=1" &&
echo "repo.url=bigpage" &&
echo "repo.path=$PWD/repos/bigpage/.git"
} >bigrc &&
rm -rf cache2 && mkdir cache2
'
# The slot holds the key, a NUL and then the page, so the page it keeps is
# what follows the first line once the NUL is a newline. The replay has to
# match that and a render with the cache off.
test_expect_success 'a page larger than the buffer is kept and replayed whole' '
cgit_ok bigrc "url=bigpage/tree/big.txt" >big.first &&
test $(wc -c big.slots &&
test_line_count = 1 big.slots &&
tr "\000" "\n" <"cache2/$(cat big.slots)" | sed 1d >slot.page &&
test_cmp big.first slot.page &&
cgit_ok bigrc "url=bigpage/tree/big.txt" >big.second &&
test_cmp big.first big.second &&
sed -e "s/^cache-size=64$/cache-size=0/" bigrc >bignocache &&
cgit_ok bignocache "url=bigpage/tree/big.txt" >big.nocache &&
strip_headers big.nocache.body &&
strip_headers big.second.body &&
test_cmp big.nocache.body big.second.body
'
# A slot stores its key ahead of the content and only the first few kilobytes of
# it are read back, so a key longer than that could never match. A request
# carrying one used to leave a slot behind on every visit that nothing could
# ever go on to use.
test_expect_success 'an over-long cache key leaves no slot behind' '
rm -rf cache3 && mkdir cache3 &&
sed -e "s|^cache-root=.*|cache-root=$PWD/cache3|" bigrc >bigkeyrc &&
long=$(awk "BEGIN{s=\"\";for(i=0;i<6000;i++)s=s \"k\"; print s}") &&
cgit_rc bigkeyrc "url=bigpage/&q=$long" >key.out 2>/dev/null &&
grep "