# GNU Shepherd --- Test log rotation.
# Copyright © 2024-2025 Ludovic Courtès <ludo@gnu.org>
#
# This file is part of the GNU Shepherd.
#
# The GNU Shepherd 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.
#
# The GNU Shepherd 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 the GNU Shepherd.  If not, see <http://www.gnu.org/licenses/>.

shepherd --version
herd --version

socket="t-socket-$$"
conf="t-conf-$$"
log="t-log-$$"
pid="t-pid-$$"
service_log1="$PWD/t-service-log1-$$"
service_log2="$PWD/t-service-log2-$$"
service_log3="$PWD/t-service-log3-$$"
external_log="$PWD/t-service-extlog-$$"
invalid_external_log="$PWD/t-service-invalid-extlog-$$"

herd="herd -s $socket"

trap "zcat $log.* || true; cat $log || true;
      rm -f $socket $conf $log* $service_log1* $service_log2* $service_log3* $external_log*;
      rmdir $invalid_external_log;
      test -f $pid && kill \`cat $pid\` || true; rm -f $pid" EXIT

cat > "$conf" <<EOF
(use-modules (shepherd service log-rotation)
             (shepherd service timer)
             (srfi srfi-19))

(define logging-command
  '("$SHELL" "-c" "while true; do echo logging things; sleep 0.2; done"))

(define today
  (time-utc->date (current-time time-utc)))

(define past-month
  (if (= 1 (date-month today))
      12
      (- (date-month today) 1)))

(%default-rotation-size-threshold 0)

(define services
  (list (service '(one)
		 #:start (make-forkexec-constructor
                          logging-command
                          #:log-file "$service_log1")
		 #:stop (make-kill-destructor))
        (service '(two)
		 #:start (make-forkexec-constructor
                          '("sleep" "600")
                          #:log-file "$service_log2")
		 #:stop (make-kill-destructor))
        (service '(three)
                 #:start (make-timer-constructor
                          (calendar-event
                            #:months
                            (if (<= (date-month (current-date)) 6)
                                '(12)
                                '(1)))
                          (command '("sh" "-c" "echo Timer triggered from \$PWD.")
	                           #:directory "$PWD")
                          #:log-file "$service_log3")
                 #:stop (make-timer-destructor)
                 #:actions (list timer-trigger-action))
        (log-rotation-service
          ;; Arrange so that it does not trigger automatically.
          (calendar-event #:months (list past-month))
          #:external-log-files '("$invalid_external_log" "$external_log")
          #:rotation-size-threshold 0)))

(register-services services)
EOF

rm -f "$pid"
shepherd -I -s "$socket" -c "$conf" -l "$log" --pid="$pid" &

# Wait till it's ready.
while ! test -f "$pid" ; do sleep 0.3 ; done

shepherd_pid="$(cat "$pid")"

file_descriptor_count ()
{
    ls -l /proc/$shepherd_pid/fd/[0-9]* | wc -l
}


$herd start one
$herd start two
$herd start log-rotation

sleep 0.5

for file in "$service_log1" "$service_log2" "$external_log" "$invalid_external_log" "$log"
do
    $herd files log-rotation | grep "$file"
done

test -f "$service_log1"
test -f "$service_log2"
echo "This is an external log file." > "$external_log"

mkdir "$invalid_external_log"			# not a regular file, so should not be rotated

# First rotation.
$herd trigger log-rotation

until grep "Rotated " "$log"; do sleep 0.5; done

test -f "$service_log1"
test -f "$service_log2"
test -f "$log"
until test -f "$service_log1.1.gz"; do sleep 0.5; done
until test -f "$log.1.gz"; do sleep 0.5; done
test -f "$service_log2.1.gz" && false

until test -f "$external_log.1.gz"; do sleep 0.5; done
gunzip < "$external_log.1.gz" | grep "external log file"
test -f "$external_log"
"$GUILE" -c "(exit (zero? (stat:size (stat \"$external_log\"))))"

zgrep "Not rotating .*$invalid_external_log" "$log"* # not a regular file

# Second rotation.
$herd trigger log-rotation

until test -f "$service_log1.2.gz"; do sleep 0.5; done
until test -f "$service_log1.1.gz"; do sleep 0.5; done
until test -f "$log.2.gz"; do sleep 0.5; done
test -f "$service_log1"
test -f "$service_log2.1.gz" && false

# Third rotation, with deletion of old log file.  Wait until "$service_log1"
# is non-empty since we want it to be rotated.
touch -d "2017-10-01" "$service_log1.2.gz"
until test -s "$service_log1"; do sleep 0.2; done
$herd trigger log-rotation

# The "Deleting ..." message might end up in the current $log or in $log.1.gz,
# depending on the order in which they are rotated.
until zgrep "Deleting .*$service_log1.2.gz" "$log"*; do sleep 0.2; tail -10 "$log"; done
until test -f "$service_log1.2.gz"; do sleep 0.2; done
until test -f "$service_log1.1.gz"; do sleep 0.2; done
test -f "$service_log1.3.gz" && false

# Rotating the log file of a timer, after the timer has run.  At that
# point the file logger is still up but its output port is closed (because
# the process the timer spawned has terminated).  Log rotation should still
# be able to proceed.
initial_fd_count=$(file_descriptor_count)
$herd start three
$herd trigger three
until test -f "$service_log3"; do sleep 0.2; done
while $herd status three | grep "Child process"; do sleep 0.2; done
$herd status three | grep "Timer triggered from $PWD"

# Check that the timer's log is subject to rotation and trigger rotation.
$herd files log-rotation | grep "$service_log3"
$herd trigger log-rotation
until test -f "$service_log3.1.gz"; do sleep 0.2; done
$herd status three
$herd status three | grep "Timer triggered from $PWD"

# Do it once more.
$herd trigger three
$herd trigger log-rotation
until test -f "$service_log3.2.gz"; do sleep 0.2; done
$herd status three
$herd stop three
test $(file_descriptor_count) -le $initial_fd_count

$herd stop log-rotation
