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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
|
test_description='Validate cache'
. ./setup.sh
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
}
test_lazy_prereq PERL_RUNS '
"$PERL_PATH" -e 1
'
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";
defined(my $pid = fork) or die "$!\n";
if (!$pid) {
alarm 60;
exec @ARGV or die "$ARGV[0]: $!\n";
}
waitpid($pid, 0);
exit($? ? 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
'
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 "<table class=.list refs." refs.out &&
test_grep ! "data-repo=.foo." refs.out &&
test_grep "holds a different key, consider a larger cache-size" refs.err &&
cgit_rc size1rc "url=foo/log/" >log.out 2>log.err &&
test_grep "data-page=.log. data-repo=.foo." log.out &&
test_grep "<table class=.list log." log.out &&
test_grep ! "data-repo=.bar." log.out &&
test_grep "holds a different key, consider a larger cache-size" log.err &&
ls cache >output &&
test_line_count = 1 output
'
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.full >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
'
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
'
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 &&
sed -e "s|^cache-root=.*|cache-root=$PWD/cache3|" bigrc >bigkeyrc &&
{
echo "cache-max-slot-size=32" &&
cat bigkeyrc
} >slotsizerc &&
rm -rf cache2 && mkdir cache2
'
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.first) -gt 65536 &&
ls cache2 >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 >big.nocache.body &&
strip_headers <big.second >big.second.body &&
test_cmp big.nocache.body big.second.body
'
test_expect_success 'a cache key past 4095 bytes leaves no slot behind' '
rm -rf cache3 && mkdir cache3 &&
cgit_rc bigkeyrc "url=bigpage/&q=$(printf "%04063d" 0)" >key.out 2>key.err &&
test_grep "</html>" key.out &&
test_grep "Cache key too long for a slot" key.err &&
test_dir_is_empty cache3
'
test_expect_success 'a cache key of 4095 bytes fills a slot' '
rm -rf cache3 && mkdir cache3 &&
cgit_ok bigkeyrc "url=bigpage/&q=$(printf "%04062d" 0)" >/dev/null &&
ls cache3 >key.slots &&
test_line_count = 1 key.slots &&
tr "\000" "\n" <"cache3/$(cat key.slots)" | awk "{ print length; exit }" >key.length &&
echo 4095 >expect &&
test_cmp expect key.length
'
test_expect_success 'set up a repo missing a parent object' '
mkrepo repos/broken 2 &&
parent=$(git -C repos/broken rev-parse HEAD^) &&
tip=$(git -C repos/broken rev-parse HEAD) &&
rm "repos/broken/.git/objects/$(echo "$parent" | cut -c1-2)/$(echo "$parent" | cut -c3-)" &&
{
echo "virtual-root=/" &&
echo "cache-root=$PWD/cache4" &&
echo "cache-size=64" &&
echo "repo.url=broken" &&
echo "repo.path=$PWD/repos/broken/.git"
} >brokenrc &&
rm -rf cache4 && mkdir cache4
'
test_expect_success 'an error after output began ends the page with it and caches nothing' '
cgit_rc brokenrc "url=broken/commit/&id=$tip" >broken.out &&
test $(grep -c "^Status:" broken.out) = 1 &&
sed -n -e "/<table class=.commit-info.>/s/.*/info/p" -e "/Not a commit/s/.*/error/p" \
-e "/<\/html>/s/.*/end/p" broken.out >broken.order &&
test_write_lines info error end >expect &&
test_cmp expect broken.order &&
test_dir_is_empty cache4 &&
cgit_ok brokenrc "url=broken/tree/" >/dev/null &&
ls cache4 >broken.slots &&
test_line_count = 1 broken.slots
'
test_expect_success 'an error page leaves no slot behind' '
rm -rf cache3 && mkdir cache3 &&
cgit_rc bigkeyrc "url=bigpage/commit/&id=$(test_oid deadbeef)" >error.out &&
test_grep "Not a commit" error.out &&
test_dir_is_empty cache3
'
test_expect_success 'a clone file leaves no slot behind' '
rm -rf cache3 && mkdir cache3 &&
cgit_rc bigkeyrc "url=bigpage/info/refs" >clone.out &&
grep "refs/heads/master" clone.out &&
cgit_rc bigkeyrc "url=bigpage/objects/info/packs" >packs.out &&
grep "^Status: 200" packs.out &&
test_dir_is_empty cache3
'
test_expect_success 'a response over cache-max-slot-size is served but not kept' '
rm -rf cache3 && mkdir cache3 &&
cgit_rc slotsizerc "url=bigpage/tree/big.txt" >big.unkept &&
tail -c 200 big.unkept | grep "</html>" &&
test_dir_is_empty cache3 &&
cgit_rc slotsizerc "url=bigpage/" >/dev/null &&
ls cache3 >kept.slots &&
test_line_count = 1 kept.slots
'
test_expect_success 'an expired slot is dropped when its refill is too large' '
rm -rf cache3 && mkdir cache3 &&
cgit_ok bigkeyrc "url=bigpage/tree/big.txt" >/dev/null &&
ls cache3 >filled.slots &&
test_line_count = 1 filled.slots &&
echo "the expired copy" >>"cache3/$(cat filled.slots)" &&
touch -t 200001010000 cache3/* &&
cgit_ok slotsizerc "url=bigpage/tree/big.txt" >big.refill &&
test_grep ! "the expired copy" big.refill &&
test_dir_is_empty cache3
'
test_expect_success 'a cache-max-slot-size of 0 or below lifts the limit' '
for size in 0 -1
do
rm -rf cache3 && mkdir cache3 &&
{
echo "cache-max-slot-size=32" &&
echo "cache-max-slot-size=$size" &&
cat bigkeyrc
} >unlimitedrc &&
cgit_ok unlimitedrc "url=bigpage/tree/big.txt" >/dev/null &&
ls cache3 >unlimited.slots &&
test_line_count = 1 unlimited.slots ||
{ echo "cache-max-slot-size=$size kept no slot" && return 1; }
done
'
test_expect_success 'an expired slot is refreshed with the page as it is now' '
rm -rf cache3 && mkdir cache3 &&
mkrepo repos/fresh 1 &&
cgitrc_repo freshrc fresh "cache-root=$PWD/cache3" cache-size=64 cache-dynamic-ttl=60 &&
cgit_ok freshrc "url=fresh/log/" >/dev/null &&
slot=$(ls cache3) &&
git -C repos/fresh commit --allow-empty -q -m "a newer commit" &&
cgit_ok freshrc "url=fresh/log/" >stale.out &&
test_grep ! "a newer commit" stale.out &&
touch -t 200001010000 "cache3/$slot" &&
cgit_ok freshrc "url=fresh/log/" >fresh.out &&
test_grep "a newer commit" fresh.out &&
tr "\000" "\n" <"cache3/$slot" >slot.txt &&
test_grep "a newer commit" slot.txt
'
test_expect_success 'a negative page ttl never expires the slot' '
rm -rf cache3 && mkdir cache3 &&
{
echo "cache-dynamic-ttl=-1" &&
cat bigkeyrc
} >neverrc &&
cgit_ok neverrc "url=bigpage/log/" >/dev/null &&
slot=$(ls cache3) &&
echo "the kept copy" >>"cache3/$slot" &&
touch -t 200001010000 "cache3/$slot" &&
cgit_ok neverrc "url=bigpage/log/" >never.out &&
test_grep "the kept copy" never.out
'
test_expect_success 'pages following a branch take the dynamic ttl' '
rm -rf cache3 && mkdir cache3 &&
{
echo "cache-dynamic-ttl=0" &&
cat bigkeyrc
} >dynamicrc &&
cgit_rc dynamicrc "url=bigpage/log/" >/dev/null &&
cgit_rc dynamicrc "url=bigpage/log/&h=master" >/dev/null &&
test_dir_is_empty cache3 &&
cgit_rc dynamicrc "url=bigpage/" >/dev/null &&
cgit_rc dynamicrc "url=bigpage/summary/" >/dev/null &&
ls cache3 >summary.slots &&
test_line_count = 2 summary.slots
'
test_expect_success 'only a page pinned to full ids takes the static ttl' '
rm -rf cache3 && mkdir cache3 &&
{
echo "cache-dynamic-ttl=0" &&
cat bigkeyrc
} >staticrc &&
first=$(git -C repos/bigpage rev-parse HEAD~1) &&
short=$(git -C repos/bigpage rev-parse --short HEAD~1) &&
for query in "commit/&id=$short" "commit/&id=master" "diff/&id=$first&id2=$short" "diff/&id=master&id2=$first"
do
cgit_ok staticrc "url=bigpage/$query" >/dev/null || return 1
done &&
test_dir_is_empty cache3 &&
cgit_ok staticrc "url=bigpage/commit/&id=$first" >/dev/null &&
ls cache3 >static.slots &&
test_line_count = 1 static.slots &&
cgit_ok staticrc "url=bigpage/diff/&id=$first&id2=$first" >/dev/null &&
ls cache3 >static.slots &&
test_line_count = 2 static.slots
'
test_expect_success 'a page ttl of 0 leaves that page uncached' '
first=$(git -C repos/bigpage rev-parse HEAD~1) &&
while read setting query
do
rm -rf cache3 && mkdir cache3 &&
{
echo "snapshots=tar.gz" &&
echo "$setting=0" &&
cat bigkeyrc &&
echo "repo.readme=:file-1"
} >ttl0rc &&
sed "/^$setting=/s/=0/=1/" ttl0rc >ttl1rc &&
cgit_ok ttl0rc "url=$query" >/dev/null &&
test_dir_is_empty cache3 &&
cgit_ok ttl1rc "url=$query" >/dev/null &&
ls cache3 >ttl.slots &&
test_line_count = 1 ttl.slots ||
{ echo "$setting does not govern url=$query" && return 1; }
done <<-EOF
cache-index-ttl
cache-summary-ttl bigpage/
cache-about-ttl bigpage/about/
cache-snapshot-ttl bigpage/snapshot/master.tar.gz
cache-static-ttl bigpage/commit/&id=$first
cache-dynamic-ttl bigpage/log/
EOF
'
test_expect_success 'a failed final write still delivers the page' '
rm -rf cache3 && mkdir cache3 &&
(
trap "" XFSZ &&
ulimit -f 2 &&
{ cgit_rc bigkeyrc "url=bigpage/" 2>flush.err; echo $? >flush.status; }
) | cat >flush.out &&
test "$(cat flush.status)" = 0 &&
test_dir_is_empty cache3 &&
test $(grep -c "^Status:" flush.out) = 1 &&
test_grep "^Status: 200" flush.out &&
test_grep ! "called many times" flush.err &&
sed "s/^cache-size=.*/cache-size=0/" bigkeyrc >flushnocacherc &&
cgit_ok flushnocacherc "url=bigpage/" >flush.nocache &&
strip_headers <flush.nocache >expect &&
strip_headers <flush.out >actual &&
test_cmp expect actual
'
start_first_request() {
rm -f filter-running release
cgit_rc lockrc "url=bigpage/about/" >first.out 2>first.err &
first_pid=$!
n=0
while ! test -f filter-running && test $n -lt 30
do
sleep 1
n=$((n + 1))
done
test -f filter-running
}
finish_first_request() {
touch release
wait "$first_pid"
}
overlap_requests() {
start_first_request &&
cgit_rc lockrc "url=bigpage/log/" >second.out 2>second.err
overlap_status=$?
finish_first_request &&
return $overlap_status
}
test_expect_success 'a slot stays locked while an exec filter runs' '
rm -rf cache3 && mkdir cache3 &&
write_script slow-filter.sh <<-EOF &&
touch "$PWD/filter-running"
n=0
while ! test -f "$PWD/release" && test \$n -lt 60
do
sleep 1
n=\$((n + 1))
done
cat
EOF
{
echo "cache-size=1" &&
sed "/^cache-size=/d" bigkeyrc &&
echo "repo.readme=:file-1" &&
echo "repo.about-filter=exec:$PWD/slow-filter.sh"
} >lockrc &&
overlap_requests &&
test $(grep -c "^Status:" first.out) = 1 &&
test_grep "<div id=.about.>1$" first.out &&
test_grep "</html>" first.out &&
test_must_be_empty first.err &&
test_grep "data-page=.log." second.out &&
test_grep "</html>" second.out &&
test_must_be_empty second.err &&
tr "\000" "\n" <cache3/00000000 | head -n 1 >slot.key &&
test_grep "about" slot.key &&
test_grep ! "log" slot.key
'
test_lazy_prereq STRACE_DELAY '
strace -o /dev/null -e trace=%file,flock -e inject=flock:delay_enter=1 true
'
start_traced_request() {
rm -f second.trace
strace -o second.trace -e trace=%file,flock \
-e inject=flock:delay_enter=4000000 \
-E CGIT_CONFIG="$PWD/lockrc" -E QUERY_STRING="url=bigpage/log/" \
cgit >second.out 2>second.err &
second_pid=$!
n=0
while ! grep "00000000\.lock" second.trace >/dev/null 2>&1 && test $n -lt 30
do
sleep 1
n=$((n + 1))
done
grep "00000000\.lock" second.trace >/dev/null 2>&1
}
overlap_traced_requests() {
second_pid=
start_first_request &&
start_traced_request
overlap_status=$?
finish_first_request || overlap_status=1
if test -n "$second_pid"
then
wait "$second_pid" || overlap_status=1
fi
return $overlap_status
}
test_expect_success STRACE_DELAY 'a lock taken after the rename leaves the slot alone' '
rm -rf cache3 && mkdir cache3 &&
overlap_traced_requests &&
test_grep -E "^flock\(.*\) += 0" second.trace &&
test_grep "<div id=.about.>1$" first.out &&
test_grep "</html>" second.out &&
tr "\000" "\n" <cache3/00000000 | head -n 1 >slot.key &&
test_grep "about" slot.key &&
test_grep ! "log" slot.key
'
test_expect_success PERL_RUNS 'a filter gets the lock file only as stdout and never the client' '
rm -rf cache3 && mkdir cache3 &&
write_script fd-filter "$PERL_PATH" <<-\EOF &&
use File::Basename qw(dirname);
use POSIX qw(fstat);
1 while <STDIN>;
for my $file ("cache3/00000000.lock", "fd.out") {
my $path = dirname($0) . "/$file";
my @file = stat $path or die "$path: $!\n";
my @fds = grep {
my @fd = fstat($_);
@fd && $fd[0] == $file[0] && $fd[1] == $file[1]
} 0 .. 255;
print "$file on fd ", @fds ? "@fds" : "none", ".\n";
}
EOF
{
echo "cache-size=1" &&
sed "/^cache-size=/d" bigkeyrc &&
echo "repo.readme=:file-1" &&
echo "repo.about-filter=exec:$PWD/fd-filter"
} >fdrc &&
cgit_rc fdrc "url=bigpage/about/" >fd.out &&
grep -F "cache3/00000000.lock on fd 1." fd.out &&
grep -F "fd.out on fd none." fd.out
'
test_expect_success SANITY 'a refresh that cannot lock serves the expired slot and says why' '
rm -rf cache3 && mkdir cache3 &&
cgit_rc bigkeyrc "url=bigpage/" >/dev/null &&
slot=$(ls cache3) &&
echo "the expired copy" >>"cache3/$slot" &&
touch -t 200001010000 "cache3/$slot" &&
test_when_finished "chmod u+w cache3" &&
chmod a-w cache3 &&
cgit_rc bigkeyrc "url=bigpage/" >stale.out 2>stale.err &&
grep "the expired copy" stale.out &&
grep "Unable to open .*\.lock: Permission denied" stale.err
'
test_expect_success PERL_RUNS 'a refresh finding the lock held serves the expired slot quietly' '
write_hold_lock &&
rm -rf cache3 && mkdir cache3 &&
cgit_ok bigkeyrc "url=bigpage/" >/dev/null &&
slot=$(ls cache3) &&
echo "the expired copy" >>"cache3/$slot" &&
touch -t 200001010000 "cache3/$slot" &&
CGIT_CONFIG="$PWD/bigkeyrc" QUERY_STRING="url=bigpage/" \
./hold-lock "cache3/$slot.lock" cgit >held.out 2>held.err &&
test_grep "^Status: 200" held.out &&
test_grep "the expired copy" held.out &&
test_must_be_empty held.err
'
test -n "$CGIT_VALGRIND" || test_set_prereq NO_VALGRIND
test_expect_success NO_VALGRIND 'a slot lock that cannot take its key is let go' '
rm -rf cache3 && mkdir cache3 &&
(
trap "" XFSZ &&
ulimit -f 0 &&
{ cgit_rc bigkeyrc "url=bigpage/" 2>&1; echo "exit status $?"; }
) | cat >nokey.out &&
test_grep "^exit status 0$" nokey.out &&
test_grep "</html>" nokey.out &&
test_grep "Unable to write .*\.lock" nokey.out &&
test_dir_is_empty cache3
'
test_expect_success 'set up a cached scan of two repositories' '
mkrepo scanned/one 1 &&
mkrepo scanned/two 1 &&
{
echo "virtual-root=/" &&
echo "cache-root=$PWD/cache5" &&
echo "cache-size=64" &&
echo "cache-index-ttl=0" &&
echo "scan-path=$PWD/scanned"
} >scanrc &&
rm -rf cache5 && mkdir cache5 &&
cgit_rc scanrc "url=" >/dev/null &&
scanned_rc=$(ls cache5) &&
test_path_is_file "cache5/$scanned_rc"
'
test_expect_success NO_VALGRIND 'a scan whose list cannot be written lists each repository once' '
rm -rf cache9 && mkdir cache9 &&
sed "s|^cache-root=.*|cache-root=$PWD/cache9|" scanrc >unwritablerc &&
(
trap "" XFSZ &&
ulimit -f 0 &&
{ cgit_rc unwritablerc "url=" 2>&1; echo "exit status $?"; }
) | cat >unwritable.out &&
test_grep "^exit status 0$" unwritable.out &&
test_grep "Unable to write .*/cache9/rc-[0-9a-f]*\.lock" unwritable.out &&
test $(grep -c ">one</a>" unwritable.out) = 1 &&
test $(grep -c ">two</a>" unwritable.out) = 1 &&
test_dir_is_empty cache9
'
test_expect_success PERL_RUNS 'a scan finding its lock held runs uncached' '
write_hold_lock &&
rm "cache5/$scanned_rc" &&
CGIT_CONFIG="$PWD/scanrc" QUERY_STRING="url=" \
./hold-lock "cache5/$scanned_rc.lock" cgit >held.out 2>held.err &&
grep ">one</a>" held.out &&
grep ">two</a>" held.out &&
test_path_is_missing "cache5/$scanned_rc" &&
test_must_be_empty held.err
'
test_expect_success 'a stale lock file reaches neither the scan nor a slot' '
{
printf "%01024d\n" 0 0 0 0 &&
echo "repo.url=ghost" &&
echo "repo.path=$PWD/scanned/one/.git"
} >stale.lock &&
rm -f "cache5/$scanned_rc" &&
cp stale.lock "cache5/$scanned_rc.lock" &&
cgit_ok scanrc "url=" >scan.out &&
test_grep ">one</a>" scan.out &&
test_grep "^repo.url=one$" "cache5/$scanned_rc" &&
test_grep ! "^repo.url=ghost$" "cache5/$scanned_rc" &&
cgit_ok scanrc "url=" >scan.out &&
test_grep ">one</a>" scan.out &&
test_grep ! ">ghost</a>" scan.out &&
rm -rf cache3 && mkdir cache3 &&
cgit_ok bigkeyrc "url=bigpage/" >page.out &&
slot=$(ls cache3) &&
rm "cache3/$slot" &&
{ cat page.out page.out && echo "STALE-TAIL"; } >"cache3/$slot.lock" &&
cgit_ok bigkeyrc "url=bigpage/" >/dev/null &&
cgit_ok bigkeyrc "url=bigpage/" >replay.out &&
test_cmp page.out replay.out
'
test_expect_success 'a config that scans differently keeps its own list' '
touch scanned/one/.git/export-ok &&
{
echo "strict-export=export-ok" &&
cat scanrc
} >strictrc &&
cgit_rc scanrc "url=" >all.out &&
cgit_rc strictrc "url=" >strict.out &&
grep ">two</a>" all.out &&
grep ">one</a>" strict.out &&
! grep ">two</a>" strict.out
'
test_expect_success 'a config that trusts its scans keeps its own list' '
mkrepo trusted/one 1 &&
echo "commit-filter=exec:$FILTER_DIRECTORY/dump.sh" >trusted/one/.git/cgitrc &&
{
echo "virtual-root=/" &&
echo "cache-root=$PWD/cache7" &&
echo "cache-size=64" &&
echo "cache-dynamic-ttl=0" &&
echo "scan-path=$PWD/trusted"
} >distrustrc &&
{
echo "trust-scan-config=1" &&
cat distrustrc
} >trustrc &&
rm -rf cache7 && mkdir cache7 &&
cgit_ok trustrc "url=one/commit/" >trust.out &&
test_grep "<div class=.commit-subject.>COMMIT 1" trust.out &&
cgit_rc distrustrc "url=one/commit/" >distrust.out 2>distrust.err &&
test_grep "^Status: 200" distrust.out &&
test_grep "<div class=.commit-subject.>commit 1" distrust.out &&
test_grep "Ignoring commit-filter in $PWD/trusted/one/.git/: trust-scan-config is not set" distrust.err &&
ls cache7/rc-* >lists &&
test_line_count = 2 lists
'
test_expect_success 'every setting the scan depends on keeps its own list' '
mkrepo keyed/one 1 &&
echo one >projects &&
{
echo "virtual-root=/" &&
echo "cache-root=$PWD/cache8" &&
echo "cache-size=64" &&
echo "scan-path=$PWD/keyed"
} >keyedrc &&
rm -rf cache8 && mkdir cache8 &&
cgit_ok keyedrc "url=" >/dev/null &&
sed "s|^scan-path=.*|scan-path=$PWD/keyed/one|" keyedrc >keyed-setting-rc &&
cgit_ok keyed-setting-rc "url=" >/dev/null &&
ls cache8/rc-* >lists &&
test_line_count = 2 lists &&
lists=2 &&
while read setting
do
{
echo "$setting" &&
cat keyedrc
} >keyed-setting-rc &&
cgit_ok keyed-setting-rc "url=" >/dev/null &&
lists=$((lists + 1)) &&
ls cache8/rc-* >lists &&
test_line_count = $lists lists ||
{ echo "$setting shares a list" && return 1; }
done <<-EOF
project-list=$PWD/projects
strict-export=git-daemon-export-ok
section=Sec
module-link=https://example.org/%s
clone-url=https://example.org/\$CGIT_REPO_URL
scan-hidden-path=1
case-sensitive-sort=0
remove-suffix=1
section-from-path=1
enable-git-config=1
trust-scan-config=1
snapshots=zip
max-stats=year
branch-sort=age
commit-sort=date
enable-blame=1
enable-commit-graph=1
enable-commit-trailers=1
enable-follow-links=1
enable-gitmodules-links=1
enable-html-serving=1
enable-log-filecount=1
enable-log-linecount=1
enable-mailmap=0
enable-remote-branches=1
enable-subject-links=1
about-filter=exec:$FILTER_DIRECTORY/dump.sh
commit-filter=exec:$FILTER_DIRECTORY/dump.sh
email-filter=exec:$FILTER_DIRECTORY/dump.sh
source-filter=exec:$FILTER_DIRECTORY/dump.sh
trailer-filter=exec:$FILTER_DIRECTORY/dump.sh
EOF
'
test_expect_success 'a negative cache-scan-ttl never expires the list' '
{
echo "cache-scan-ttl=-1" &&
cat scanrc
} >foreverrc &&
rm -f "cache5/$scanned_rc" &&
cgit_ok scanrc "url=" >/dev/null &&
test_path_is_file "cache5/$scanned_rc" &&
{
echo "repo.url=kept" &&
echo "repo.path=$PWD/scanned/one/.git"
} >>"cache5/$scanned_rc" &&
touch -t 200001010000 "cache5/$scanned_rc" &&
{ cgit_rc foreverrc "url=" 9>&1 >forever.out 2>forever.err; echo $? >forever.status; } | cat &&
test "$(cat forever.status)" = 0 &&
test_must_be_empty forever.err &&
test_grep ">kept</a>" forever.out &&
test_grep "^repo.url=kept$" "cache5/$scanned_rc" &&
{ cgit_rc scanrc "url=" 9>&1 >rescan.out 2>rescan.err; echo $? >rescan.status; } | cat &&
test "$(cat rescan.status)" = 0 &&
test_must_be_empty rescan.err &&
test_grep ">kept</a>" rescan.out &&
test_grep "^repo.url=one$" "cache5/$scanned_rc" &&
test_grep ! "^repo.url=kept$" "cache5/$scanned_rc"
'
start_rescan_request() {
rm -f rescan.done
{
{
cat /dev/zero 2>/dev/null |
cgit_rc pipelistrc "url=" 2>&1 |
cat >rescan.out &&
touch rescan.done
} 9>&1 >/dev/null | cat
} &
rescan_pid=$!
n=0
while ! test -f rescan.done && test $n -lt 30
do
sleep 1
n=$((n + 1))
done
test -f rescan.done
}
finish_rescan() {
"$PERL_PATH" -e 'alarm 30; open(my $list, ">", shift) or die "$!\n"; print $list "one\n"' pipelist
finish_status=$?
wait "$rescan_pid"
return $finish_status
}
rescan_behind_request() {
start_rescan_request
rescan_status=$?
finish_rescan &&
return $rescan_status
}
test_expect_success PIPE,PERL_RUNS 'a background rescan lets the request finish' '
mkrepo piped/one 1 &&
echo one >pipelist &&
{
echo "virtual-root=/" &&
echo "cache-root=$PWD/cache10" &&
echo "cache-size=64" &&
echo "cache-index-ttl=0" &&
echo "project-list=$PWD/pipelist" &&
echo "scan-path=$PWD/piped"
} >pipelistrc &&
rm -rf cache10 && mkdir cache10 &&
cgit_ok pipelistrc "url=" >/dev/null &&
list=$(ls cache10/rc-*) &&
{
echo "repo.url=kept" &&
echo "repo.path=$PWD/piped/one/.git"
} >>"$list" &&
touch -t 200001010000 "$list" &&
rm pipelist &&
mkfifo pipelist &&
rescan_behind_request &&
test_grep ">kept</a>" rescan.out &&
test_grep "</html>" rescan.out &&
test_grep ! "^\[cgit\]" rescan.out &&
test_grep "^repo.url=one$" "$list" &&
test_grep ! "^repo.url=kept$" "$list"
'
test_expect_success 'a cached scan leaves the description placeholder out' '
rm -rf cache5 && mkdir cache5 &&
cgit_ok scanrc "url=" >/dev/null &&
rc=$(ls cache5/rc-*) &&
test_grep "^repo.url=one$" "$rc" &&
test_grep ! "^repo.desc=" "$rc" &&
cgit_ok scanrc "url=" >tmp &&
test_grep "\[no description\]" tmp &&
cgit_ok scanrc "url=one/" >tmp &&
test_grep "data-page=.summary. data-repo=.one." tmp &&
test_grep ! "<meta name=.description." tmp
'
test_expect_success 'a cached scan keeps what a repository sets' '
mkrepo kept/one 1 &&
sub=$(git -C kept/one rev-parse HEAD) &&
(
cd kept/one &&
git update-index --add --cacheinfo 160000,$sub,submod &&
git commit -m gitlink &&
git branch aaa &&
newest=$(echo newest | GIT_COMMITTER_DATE="@2000000000 +0000" git commit-tree HEAD^{tree} -p HEAD) &&
git update-ref refs/heads/zzz $newest
) &&
{
echo "branch-sort=name" &&
echo "about-filter=" &&
echo "readme=:file-1" &&
echo "module-link.submod=https://example.org/s/?id=%s"
} >kept/one/.git/cgitrc &&
{
echo "virtual-root=/" &&
echo "cache-root=$PWD/cache6" &&
echo "cache-size=64" &&
echo "trust-scan-config=1" &&
echo "branch-sort=age" &&
echo "scan-path=$PWD/kept"
} >unfilteredrc &&
{
echo "about-filter=exec:$FILTER_DIRECTORY/dump.sh" &&
cat unfilteredrc
} >keptrc &&
rm -rf cache6 && mkdir cache6 &&
cgit_rc unfilteredrc "url=" >/dev/null &&
cgit_rc keptrc "url=" >/dev/null &&
cgit_rc keptrc "url=one/refs/" >tmp &&
sed -n "s/^<tr class=.branch.><td><a [^>]*>\([a-z]*\)<.*/\1/p" tmp >branches &&
test_write_lines aaa master zzz >expect &&
test_cmp expect branches &&
cgit_rc keptrc "url=one/about/" >tmp &&
grep "<pre class=.plaintext.>1$" tmp &&
cgit_rc keptrc "url=one/tree/" >tmp &&
grep "href=.https://example.org/s/?id=$sub." tmp
'
test_expect_success 'a negative cache-size leaves the scan uncached' '
{
echo "virtual-root=/" &&
echo "cache-root=$PWD/cache-negative" &&
echo "cache-size=-1" &&
echo "scan-path=$PWD/scanned"
} >negativerc &&
rm -rf cache-negative && mkdir cache-negative &&
cgit_rc negativerc "url=" >tmp &&
grep ">one</a>" tmp &&
test_dir_is_empty cache-negative
'
test_done
|