 e2ecb8045b
			
		
	
	
	
	
		
			
		
	
	e2ecb8045bThis makes the behaviour backwards compatible with all previous uses of mcron. * src/mcron/base.scm: establish %do-logging parameter and act on it * src/mcron/scripts/mcron.scm: set %do-logging according to command line * tests/base.scm: some tests require %do-logging to be set
		
			
				
	
	
		
			295 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Scheme
		
	
	
	
	
	
			
		
		
	
	
			295 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Scheme
		
	
	
	
	
	
| ;;;; base.scm -- tests for (mcron base) module
 | |
| ;;; Copyright © 2018 Mathieu Lirzin <mthl@gnu.org>
 | |
| ;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
 | |
| ;;;
 | |
| ;;; This file is part of GNU Mcron.
 | |
| ;;;
 | |
| ;;; GNU Mcron 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.
 | |
| ;;;
 | |
| ;;; GNU Mcron 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 GNU Mcron.  If not, see <http://www.gnu.org/licenses/>.
 | |
| 
 | |
| (use-modules ((rnrs base) #:select (assert))
 | |
|              (srfi srfi-64)
 | |
|              (srfi srfi-111)
 | |
|              (mcron base))
 | |
| 
 | |
| (test-begin "base")
 | |
| 
 | |
| (setlocale LC_ALL "C")
 | |
| (setenv "TZ" "UTC0")
 | |
| 
 | |
| ;;; Import private procedures.
 | |
| (define make-schedule (@@ (mcron base) make-schedule))
 | |
| (define schedule-current (@@ (mcron base) schedule-current))
 | |
| (define schedule-user (@@ (mcron base) schedule-user))
 | |
| (define schedule-system (@@ (mcron base) schedule-system))
 | |
| (define make-job (@@ (mcron base) make-job))
 | |
| (define find-next-jobs (@@ (mcron base) find-next-jobs))
 | |
| 
 | |
| (define %user0 #("user0" "x" 0 0 "user0" "/var/empty" "/bin/sh"))
 | |
| (define %user1 #("user1" "x" 1 1 "user1" "/var/empty" "/bin/sh"))
 | |
| 
 | |
| (define* (make-dummy-job #:optional (displayable "dummy")
 | |
|                          #:key
 | |
|                          (user (getpw))
 | |
|                          (time-proc 1+)
 | |
|                          (action (lambda () "dummy action"))
 | |
|                          (environment '())
 | |
|                          (next-time 0))
 | |
|   (make-job user time-proc action environment displayable next-time))
 | |
| 
 | |
| ;;; Check 'use-system-job-list' and 'use-user-job-list' effect
 | |
| (let ((schdl (make-schedule '() '() 'user)))
 | |
|   (use-system-job-list #:schedule schdl)
 | |
|   (test-eq "use-system-job-list"
 | |
|     'system
 | |
|     (schedule-current schdl))
 | |
| 
 | |
|   (use-user-job-list #:schedule schdl)
 | |
|   (test-eq "use-user-job-list"
 | |
|     'user
 | |
|     (schedule-current schdl)))
 | |
| 
 | |
| ;;; Check that 'remove-user-jobs' with only one type of user job clears the
 | |
| ;;; schedule.
 | |
| (let* ((job (make-dummy-job #:user %user0))
 | |
|        (schdl (make-schedule (list job) '() 'user)))
 | |
|   (remove-user-jobs %user0 #:schedule schdl)
 | |
|   (test-equal "remove-user-jobs: only one"
 | |
|     '()
 | |
|     (schedule-user schdl)))
 | |
| 
 | |
| ;;; Check that 'remove-user-jobs' with only two types of user jobs keep the
 | |
| ;;; other user jobs in the schedule.
 | |
| (let* ((job0 (make-dummy-job #:user %user0))
 | |
|        (job1 (make-dummy-job #:user %user1))
 | |
|        (schdl (make-schedule (list job0 job1) '() 'user)))
 | |
|   (remove-user-jobs %user0 #:schedule schdl)
 | |
|   (test-equal "remove-user-jobs: keep one"
 | |
|     (list job1)
 | |
|     (schedule-user schdl)))
 | |
| 
 | |
| ;;; Check that 'clear-system-jobs' removes all system jobs and has no effect
 | |
| ;;; on the user jobs.
 | |
| (let* ((job0 (make-dummy-job #:user %user0))
 | |
|        (job1 (make-dummy-job #:user %user1))
 | |
|        (schdl (make-schedule (list job0) (list job1) 'user)))
 | |
|   (clear-system-jobs #:schedule schdl)
 | |
|   (test-assert "clear-system-jobs: basic"
 | |
|     (and (equal? (list job0) (schedule-user schdl))
 | |
|          (equal? '() (schedule-system schdl)))))
 | |
| 
 | |
| ;;; Check that 'add-job' adds a user job when the current schedule is 'user.
 | |
| (let ((schdl (make-schedule '() '() 'user)))
 | |
|   (add-job 1+ (const #t) "job0" 0 "user" #:schedule schdl)
 | |
|   (test-assert "add-job: user schedule"
 | |
|     (and (= 1 (length (schedule-user schdl)))
 | |
|          (= 0 (length (schedule-system schdl))))))
 | |
| 
 | |
| ;;; Check that 'add-job' adds a system job when the current schedule is
 | |
| ;;; 'system.
 | |
| (let ((schdl (make-schedule '() '() 'system)))
 | |
|   (add-job 1+ (const #t) "job0" 0 "user" #:schedule schdl)
 | |
|   (test-assert "add-job: system schedule"
 | |
|     (and (= 0 (length (schedule-user schdl)))
 | |
|          (= 1 (length (schedule-system schdl))))))
 | |
| 
 | |
| ;;; Check that 'find-next-jobs' find the soonest job.
 | |
| (let* ((job0 (make-dummy-job #:user %user0 #:next-time 5))
 | |
|        (job1 (make-dummy-job #:user %user1 #:next-time 10))
 | |
|        (schdl (make-schedule (list job0) (list job1) 'user)))
 | |
|   (test-equal "find-next-jobs: basic"
 | |
|     (list 5 job0)
 | |
|     (find-next-jobs #:schedule schdl)))
 | |
| 
 | |
| ;;; Check that 'find-next-jobs' can find multiple soonest jobs.
 | |
| (let* ((job0 (make-dummy-job #:user %user0 #:next-time 5))
 | |
|        (job1 (make-dummy-job #:user %user1 #:next-time 5))
 | |
|        (schdl (make-schedule (list job0) (list job1) 'user)))
 | |
|   (test-equal "find-next-jobs: two jobs"
 | |
|     (list 5 job0 job1)
 | |
|     (find-next-jobs #:schedule schdl)))
 | |
| 
 | |
| ;;; Check that 'find-next-jobs' returns #f when the schedule is empty.
 | |
| (let ((schdl (make-schedule '() '() 'user)))
 | |
|   (test-equal "find-next-jobs: empty"
 | |
|     (list #f)
 | |
|     (find-next-jobs #:schedule schdl)))
 | |
| 
 | |
| ;;; Check output of 'display-schedule' with a basic schedule.
 | |
| (test-assert "display-schedule: basic"
 | |
|   (and (equal?
 | |
|         "Thu Jan  1 00:00:05 1970 +0000\ndummy\n\n"
 | |
|         (let* ((job0 (make-dummy-job #:user %user0 #:next-time 5))
 | |
|                (job1 (make-dummy-job #:user %user1 #:next-time 10))
 | |
|                (schdl (make-schedule (list job0) (list job1) 'user)))
 | |
|           (with-output-to-string
 | |
|             (λ () (display-schedule 1 #:schedule schdl)))))
 | |
|        (equal?
 | |
|         (string-append
 | |
|          "Thu Jan  1 00:00:05 1970 +0000\ndummy\n\n"
 | |
|          "Thu Jan  1 00:00:06 1970 +0000\ndummy\n\n")
 | |
|         (let* ((job0 (make-dummy-job #:user %user0 #:next-time 5))
 | |
|                (job1 (make-dummy-job #:user %user1 #:next-time 10))
 | |
|                (schdl (make-schedule (list job0) (list job1) 'user)))
 | |
|           (with-output-to-string
 | |
|             (λ () (display-schedule 2 #:schedule schdl)))))))
 | |
| 
 | |
| ;;; Check output of 'display-schedule' with an empty schedule.
 | |
| (let ((schdl (make-schedule '() '() 'user)))
 | |
|   (test-equal "display-schedule: empty"
 | |
|     ""
 | |
|     (with-output-to-string
 | |
|       (λ () (display-schedule 1 #:schedule schdl)))))
 | |
| 
 | |
| ;;;
 | |
| ;;; Running jobs
 | |
| ;;;
 | |
| 
 | |
| ;;; Import private global.
 | |
| (define number-children (@@ (mcron base) number-children))
 | |
| 
 | |
| ;;; Import private procedures.
 | |
| (define update-number-children! (@@ (mcron base) update-number-children!))
 | |
| (define child-cleanup (@@ (mcron base) child-cleanup))
 | |
| (define run-job (@@ (mcron base) run-job))
 | |
| 
 | |
| ;;; Check 'number-children' initial value.
 | |
| (test-equal "number-children: init"
 | |
|   0
 | |
|   (unbox number-children))
 | |
| 
 | |
| ;;; Check 'update-number-children!' incrementation.
 | |
| (test-equal "update-number-children!: 1+"
 | |
|   2
 | |
|   (begin
 | |
|     (update-number-children! 1+)
 | |
|     (update-number-children! 1+)
 | |
|     (unbox number-children)))
 | |
| 
 | |
| ;;; Check 'update-number-children!' decrementation.
 | |
| (test-equal "update-number-children!: 1-"
 | |
|   1
 | |
|   (begin
 | |
|     (update-number-children! 1-)
 | |
|     (unbox number-children)))
 | |
| 
 | |
| ;;; Check 'update-number-children!' constant value.
 | |
| (test-equal "update-number-children!: set value"
 | |
|   0
 | |
|   (begin
 | |
|     (update-number-children! (const 0))
 | |
|     (unbox number-children)))
 | |
| 
 | |
| ;;; Check 'run-job' and 'child-cleanup'.
 | |
| ;;; XXX: Having to use the filesystem for a unit test is wrong.
 | |
| (let* ((filename (tmpnam))
 | |
|        (action (lambda () (close-port (open-output-file filename))))
 | |
|        (job (make-dummy-job #:user (getpw (getuid)) #:action action)))
 | |
|   (dynamic-wind
 | |
|     (const #t)
 | |
|     (lambda ()
 | |
|       (sigaction SIGCHLD (const #t))
 | |
|       (let ((child-data (run-job job)))
 | |
|         ;; Wait for the SIGCHLD signal sent when job exits.
 | |
|         (pause)
 | |
|         ;; Check 'run-job' result and if the number of children is up-to-date.
 | |
|         (test-equal "run-job: basic"
 | |
|           1
 | |
|           (and (access? filename F_OK)
 | |
|                (unbox number-children)))
 | |
|         (child-cleanup (list child-data)))
 | |
|       ;; Check that 'child-cleanup' updates the number of children.
 | |
|       (test-equal "child-cleanup: one" 0 (unbox number-children)))
 | |
|     (lambda ()
 | |
|       (and (access? filename F_OK) (delete-file filename))
 | |
|       (sigaction SIGCHLD SIG_DFL))))
 | |
| 
 | |
| (define (dummy-job/capture-output action)
 | |
|   "Return the output of a dummy-job that ran ACTION."
 | |
|   (with-output-to-string
 | |
|     (lambda ()
 | |
|       (dynamic-wind
 | |
|         (const #t)
 | |
|         (lambda ()
 | |
|           (sigaction SIGCHLD (const #t))
 | |
|           (let ((child-data
 | |
|                  (run-job
 | |
|                   (make-dummy-job
 | |
|                    #:user (getpw (getuid))
 | |
|                    #:action action))))
 | |
|             (pause)
 | |
|             (child-cleanup (list child-data))))
 | |
|         (lambda ()
 | |
|           #t
 | |
|           (sigaction SIGCHLD SIG_DFL))))))
 | |
| 
 | |
| (test-assert "run-job, output"
 | |
|   (let ((output (parameterize ((%do-logging #t))
 | |
|                   (dummy-job/capture-output
 | |
|                    (lambda ()
 | |
|                      (format #t "output line 1~%")
 | |
|                      (format #t "output line 2\nand 3~%")
 | |
|                      (system "echo poutine")
 | |
|                      (format (current-error-port)
 | |
|                              "some error~%"))))))
 | |
|     (assert (string-contains output "dummy: running"))
 | |
|     (assert (string-contains output "dummy: output line 1"))
 | |
|     (assert (string-contains output "dummy: and 3"))
 | |
|     (assert (string-contains output "dummy: poutine"))
 | |
|     (assert (string-contains output "dummy: some error"))
 | |
|     (assert (string-contains output "dummy: completed in"))))
 | |
| 
 | |
| (test-assert "validate-date-format, valid"
 | |
|   (validate-date-format "~1"))
 | |
| 
 | |
| (test-assert "validate-date-format, invalid"
 | |
|   (catch 'mcron-error
 | |
|     (lambda ()
 | |
|       (validate-date-format "~¾")
 | |
|       #f)
 | |
|     (const #t)))
 | |
| 
 | |
| (test-assert "validate-log-format, valid"
 | |
|   (validate-log-format "the message only: ~3@*~a~%"))
 | |
| 
 | |
| (test-assert "validate-log-format, invalid"
 | |
|   (catch 'mcron-error
 | |
|     (lambda ()
 | |
|       ;; There aren't that many arguments!
 | |
|       (validate-log-format "~20@*~a~%")
 | |
|       #f)
 | |
|     (const #t)))
 | |
| 
 | |
| (test-assert "run-job, output with custom format"
 | |
|   (let ((output (parameterize ((%do-logging #t)
 | |
|                                (%log-format "the message only: ~3@*~a~%"))
 | |
|                   (dummy-job/capture-output
 | |
|                    (lambda ()
 | |
|                      (format #t "output line 1~%"))))))
 | |
|     (string-contains output "the message only: output line 1\n")))
 | |
| 
 | |
| (test-assert "run-job, failure"
 | |
|   (let ((output (dummy-job/capture-output
 | |
|                  (lambda ()
 | |
|                    (error "that didn't go well")))))
 | |
|     (assert (string-contains output "that didn't go well"))
 | |
|     (assert (string-contains output "failed after"))))
 | |
| 
 | |
| (test-assert "run-job, failure in shell action"
 | |
|   (let ((output (dummy-job/capture-output
 | |
|                  (lambda ()
 | |
|                    (system "exit 1")))))
 | |
|     (assert (string-contains output "unclean exit status"))
 | |
|     (assert (string-contains output "failed after"))))
 | |
| 
 | |
| (test-end)
 |