base.scm 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. ;;;; base.scm -- tests for (mcron base) module
  2. ;;; Copyright © 2018 Mathieu Lirzin <mthl@gnu.org>
  3. ;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
  4. ;;;
  5. ;;; This file is part of GNU Mcron.
  6. ;;;
  7. ;;; GNU Mcron is free software: you can redistribute it and/or modify
  8. ;;; it under the terms of the GNU General Public License as published by
  9. ;;; the Free Software Foundation, either version 3 of the License, or
  10. ;;; (at your option) any later version.
  11. ;;;
  12. ;;; GNU Mcron is distributed in the hope that it will be useful,
  13. ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;;; GNU General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with GNU Mcron. If not, see <http://www.gnu.org/licenses/>.
  19. (use-modules ((rnrs base) #:select (assert))
  20. (srfi srfi-64)
  21. (srfi srfi-111)
  22. (mcron base))
  23. (test-begin "base")
  24. (setlocale LC_ALL "C")
  25. (setenv "TZ" "UTC0")
  26. ;;; Import private procedures.
  27. (define make-schedule (@@ (mcron base) make-schedule))
  28. (define schedule-current (@@ (mcron base) schedule-current))
  29. (define schedule-user (@@ (mcron base) schedule-user))
  30. (define schedule-system (@@ (mcron base) schedule-system))
  31. (define make-job (@@ (mcron base) make-job))
  32. (define find-next-jobs (@@ (mcron base) find-next-jobs))
  33. (define %user0 #("user0" "x" 0 0 "user0" "/var/empty" "/bin/sh"))
  34. (define %user1 #("user1" "x" 1 1 "user1" "/var/empty" "/bin/sh"))
  35. (define* (make-dummy-job #:optional (displayable "dummy")
  36. #:key
  37. (user (getpw))
  38. (time-proc 1+)
  39. (action (lambda () "dummy action"))
  40. (environment '())
  41. (next-time 0))
  42. (make-job user time-proc action environment displayable next-time))
  43. ;;; Check 'use-system-job-list' and 'use-user-job-list' effect
  44. (let ((schdl (make-schedule '() '() 'user)))
  45. (use-system-job-list #:schedule schdl)
  46. (test-eq "use-system-job-list"
  47. 'system
  48. (schedule-current schdl))
  49. (use-user-job-list #:schedule schdl)
  50. (test-eq "use-user-job-list"
  51. 'user
  52. (schedule-current schdl)))
  53. ;;; Check that 'remove-user-jobs' with only one type of user job clears the
  54. ;;; schedule.
  55. (let* ((job (make-dummy-job #:user %user0))
  56. (schdl (make-schedule (list job) '() 'user)))
  57. (remove-user-jobs %user0 #:schedule schdl)
  58. (test-equal "remove-user-jobs: only one"
  59. '()
  60. (schedule-user schdl)))
  61. ;;; Check that 'remove-user-jobs' with only two types of user jobs keep the
  62. ;;; other user jobs in the schedule.
  63. (let* ((job0 (make-dummy-job #:user %user0))
  64. (job1 (make-dummy-job #:user %user1))
  65. (schdl (make-schedule (list job0 job1) '() 'user)))
  66. (remove-user-jobs %user0 #:schedule schdl)
  67. (test-equal "remove-user-jobs: keep one"
  68. (list job1)
  69. (schedule-user schdl)))
  70. ;;; Check that 'clear-system-jobs' removes all system jobs and has no effect
  71. ;;; on the user jobs.
  72. (let* ((job0 (make-dummy-job #:user %user0))
  73. (job1 (make-dummy-job #:user %user1))
  74. (schdl (make-schedule (list job0) (list job1) 'user)))
  75. (clear-system-jobs #:schedule schdl)
  76. (test-assert "clear-system-jobs: basic"
  77. (and (equal? (list job0) (schedule-user schdl))
  78. (equal? '() (schedule-system schdl)))))
  79. ;;; Check that 'add-job' adds a user job when the current schedule is 'user.
  80. (let ((schdl (make-schedule '() '() 'user)))
  81. (add-job 1+ (const #t) "job0" 0 "user" #:schedule schdl)
  82. (test-assert "add-job: user schedule"
  83. (and (= 1 (length (schedule-user schdl)))
  84. (= 0 (length (schedule-system schdl))))))
  85. ;;; Check that 'add-job' adds a system job when the current schedule is
  86. ;;; 'system.
  87. (let ((schdl (make-schedule '() '() 'system)))
  88. (add-job 1+ (const #t) "job0" 0 "user" #:schedule schdl)
  89. (test-assert "add-job: system schedule"
  90. (and (= 0 (length (schedule-user schdl)))
  91. (= 1 (length (schedule-system schdl))))))
  92. ;;; Check that 'find-next-jobs' find the soonest job.
  93. (let* ((job0 (make-dummy-job #:user %user0 #:next-time 5))
  94. (job1 (make-dummy-job #:user %user1 #:next-time 10))
  95. (schdl (make-schedule (list job0) (list job1) 'user)))
  96. (test-equal "find-next-jobs: basic"
  97. (list 5 job0)
  98. (find-next-jobs #:schedule schdl)))
  99. ;;; Check that 'find-next-jobs' can find multiple soonest jobs.
  100. (let* ((job0 (make-dummy-job #:user %user0 #:next-time 5))
  101. (job1 (make-dummy-job #:user %user1 #:next-time 5))
  102. (schdl (make-schedule (list job0) (list job1) 'user)))
  103. (test-equal "find-next-jobs: two jobs"
  104. (list 5 job0 job1)
  105. (find-next-jobs #:schedule schdl)))
  106. ;;; Check that 'find-next-jobs' returns #f when the schedule is empty.
  107. (let ((schdl (make-schedule '() '() 'user)))
  108. (test-equal "find-next-jobs: empty"
  109. (list #f)
  110. (find-next-jobs #:schedule schdl)))
  111. ;;; Check output of 'display-schedule' with a basic schedule.
  112. (test-assert "display-schedule: basic"
  113. (and (equal?
  114. "Thu Jan 1 00:00:05 1970 +0000\ndummy\n\n"
  115. (let* ((job0 (make-dummy-job #:user %user0 #:next-time 5))
  116. (job1 (make-dummy-job #:user %user1 #:next-time 10))
  117. (schdl (make-schedule (list job0) (list job1) 'user)))
  118. (with-output-to-string
  119. (λ () (display-schedule 1 #:schedule schdl)))))
  120. (equal?
  121. (string-append
  122. "Thu Jan 1 00:00:05 1970 +0000\ndummy\n\n"
  123. "Thu Jan 1 00:00:06 1970 +0000\ndummy\n\n")
  124. (let* ((job0 (make-dummy-job #:user %user0 #:next-time 5))
  125. (job1 (make-dummy-job #:user %user1 #:next-time 10))
  126. (schdl (make-schedule (list job0) (list job1) 'user)))
  127. (with-output-to-string
  128. (λ () (display-schedule 2 #:schedule schdl)))))))
  129. ;;; Check output of 'display-schedule' with an empty schedule.
  130. (let ((schdl (make-schedule '() '() 'user)))
  131. (test-equal "display-schedule: empty"
  132. ""
  133. (with-output-to-string
  134. (λ () (display-schedule 1 #:schedule schdl)))))
  135. ;;;
  136. ;;; Running jobs
  137. ;;;
  138. ;;; Import private global.
  139. (define number-children (@@ (mcron base) number-children))
  140. ;;; Import private procedures.
  141. (define update-number-children! (@@ (mcron base) update-number-children!))
  142. (define child-cleanup (@@ (mcron base) child-cleanup))
  143. (define run-job (@@ (mcron base) run-job))
  144. ;;; Check 'number-children' initial value.
  145. (test-equal "number-children: init"
  146. 0
  147. (unbox number-children))
  148. ;;; Check 'update-number-children!' incrementation.
  149. (test-equal "update-number-children!: 1+"
  150. 2
  151. (begin
  152. (update-number-children! 1+)
  153. (update-number-children! 1+)
  154. (unbox number-children)))
  155. ;;; Check 'update-number-children!' decrementation.
  156. (test-equal "update-number-children!: 1-"
  157. 1
  158. (begin
  159. (update-number-children! 1-)
  160. (unbox number-children)))
  161. ;;; Check 'update-number-children!' constant value.
  162. (test-equal "update-number-children!: set value"
  163. 0
  164. (begin
  165. (update-number-children! (const 0))
  166. (unbox number-children)))
  167. ;;; Check 'run-job' and 'child-cleanup'.
  168. ;;; XXX: Having to use the filesystem for a unit test is wrong.
  169. (let* ((filename (tmpnam))
  170. (action (lambda () (close-port (open-output-file filename))))
  171. (job (make-dummy-job #:user (getpw (getuid)) #:action action)))
  172. (dynamic-wind
  173. (const #t)
  174. (lambda ()
  175. (sigaction SIGCHLD (const #t))
  176. (let ((child-data (run-job job)))
  177. ;; Wait for the SIGCHLD signal sent when job exits.
  178. (pause)
  179. ;; Check 'run-job' result and if the number of children is up-to-date.
  180. (test-equal "run-job: basic"
  181. 1
  182. (and (access? filename F_OK)
  183. (unbox number-children)))
  184. (child-cleanup (list child-data)))
  185. ;; Check that 'child-cleanup' updates the number of children.
  186. (test-equal "child-cleanup: one" 0 (unbox number-children)))
  187. (lambda ()
  188. (and (access? filename F_OK) (delete-file filename))
  189. (sigaction SIGCHLD SIG_DFL))))
  190. (define (dummy-job/capture-output action)
  191. "Return the output of a dummy-job that ran ACTION."
  192. (with-output-to-string
  193. (lambda ()
  194. (dynamic-wind
  195. (const #t)
  196. (lambda ()
  197. (sigaction SIGCHLD (const #t))
  198. (let ((child-data
  199. (run-job
  200. (make-dummy-job
  201. #:user (getpw (getuid))
  202. #:action action))))
  203. (pause)
  204. (child-cleanup (list child-data))))
  205. (lambda ()
  206. #t
  207. (sigaction SIGCHLD SIG_DFL))))))
  208. (test-assert "run-job, output"
  209. (let ((output (dummy-job/capture-output
  210. (lambda ()
  211. (format #t "output line 1~%")
  212. (format #t "output line 2\nand 3~%")
  213. (system "echo poutine")
  214. (format (current-error-port)
  215. "some error~%")))))
  216. (assert (string-contains output "dummy: running"))
  217. (assert (string-contains output "dummy: output line 1"))
  218. (assert (string-contains output "dummy: and 3"))
  219. (assert (string-contains output "dummy: poutine"))
  220. (assert (string-contains output "dummy: some error"))
  221. (assert (string-contains output "dummy: completed in"))))
  222. (test-assert "validate-date-format, valid"
  223. (validate-date-format "~1"))
  224. (test-assert "validate-date-format, invalid"
  225. (catch 'mcron-error
  226. (lambda ()
  227. (validate-date-format "~¾")
  228. #f)
  229. (const #t)))
  230. (test-assert "validate-log-format, valid"
  231. (validate-log-format "the message only: ~3@*~a~%"))
  232. (test-assert "validate-log-format, invalid"
  233. (catch 'mcron-error
  234. (lambda ()
  235. ;; There aren't that many arguments!
  236. (validate-log-format "~20@*~a~%")
  237. #f)
  238. (const #t)))
  239. (test-assert "run-job, output with custom format"
  240. (let ((output (parameterize ((%log-format "the message only: ~3@*~a~%"))
  241. (dummy-job/capture-output
  242. (lambda ()
  243. (format #t "output line 1~%"))))))
  244. (string-contains output "the message only: output line 1\n")))
  245. (test-assert "run-job, failure"
  246. (let ((output (dummy-job/capture-output
  247. (lambda ()
  248. (error "that didn't go well")))))
  249. (assert (string-contains output "that didn't go well"))
  250. (assert (string-contains output "failed after"))))
  251. (test-assert "run-job, failure in shell action"
  252. (let ((output (dummy-job/capture-output
  253. (lambda ()
  254. (system "exit 1")))))
  255. (assert (string-contains output "unclean exit status"))
  256. (assert (string-contains output "failed after"))))
  257. (test-end)