(in-package :cl-user)

(use-package :test-util)

(defmacro assert-timeout (form)
  (let ((ok (gensym "OK")))
    `(let ((,ok ',ok))
       (unless (eq ,ok
                   (handler-case ,form
                     (timeout ()
                       ,ok)))
         (error "No timeout from form:~%  ~S" ',form)))))

(defun run-sleep (seconds)
  (sb-ext:run-program "sleep" (list (format nil "~D" seconds))
                      :search t :wait t))

(with-test (:name (:deadline :run-program :trivial) :fails-on :win32)
  (assert-timeout (sb-sys:with-deadline (:seconds 1)
                    (run-sleep 3))))

(with-test (:name (:deadline :defer-deadline-1) :fails-on :win32)
  (let ((n 0)
        (final nil))
    (handler-case
        (handler-bind ((sb-sys:deadline-timeout
                        #'(lambda (c)
                            (when (< n 2)
                              (incf n)
                              (sb-sys:defer-deadline 0.1 c)))))
          (sb-sys:with-deadline (:seconds 1)
            (run-sleep 2)))
      (sb-sys:deadline-timeout (c)
        (setf final c)))
    (assert (= n 2))
    (assert final)))

(with-test (:name (:deadline :defer-deadline-2) :fails-on :win32)
  (let ((n 0)
        (final nil))
    (handler-case
        (handler-bind ((sb-sys:deadline-timeout
                        #'(lambda (c)
                            (incf n)
                            (sb-sys:defer-deadline 0.1 c))))
          (sb-sys:with-deadline (:seconds 1)
            (run-sleep 2)))
      (sb-sys:deadline-timeout (c)
        (setf final c)))
    (assert (plusp n))
    (assert (not final))))

(with-test (:name (:deadline :cancel-deadline) :fails-on :win32)
  (let ((n 0)
        (final nil))
    (handler-case
        (handler-bind ((sb-sys:deadline-timeout
                        #'(lambda (c)
                            (incf n)
                            (sb-sys:cancel-deadline c))))
          (sb-sys:with-deadline (:seconds 1)
            (run-sleep 2)))
      (sb-sys:deadline-timeout (c)
        (setf final c)))
    (assert (= n 1))
    (assert (not final))))

(with-test (:name (:deadline :grab-mutex) :skipped-on '(not :sb-thread))
  (assert-timeout
   (let ((lock (sb-thread:make-mutex))
         (waitp t))
     (make-join-thread (lambda ()
                         (sb-thread:grab-mutex lock)
                         (setf waitp nil)
                         (sleep 5)))
     (loop while waitp do (sleep 0.01))
     (sb-sys:with-deadline (:seconds 1)
       (sb-thread:grab-mutex lock)))))

(with-test (:name (:deadline :wait-on-semaphore) :skipped-on '(not :sb-thread))
  (assert-timeout
   (let ((sem (sb-thread::make-semaphore :count 0)))
     (sb-sys:with-deadline (:seconds 1)
       (sb-thread::wait-on-semaphore sem)))))

(with-test (:name (:deadline :join-thread) :skipped-on '(not :sb-thread))
  (assert-timeout
   (sb-sys:with-deadline (:seconds 1)
     (sb-thread:join-thread
      (make-kill-thread (lambda () (loop (sleep 1))))))))

(with-test (:name (:deadline :futex-wait-eintr) :skipped-on '(not :sb-thread))
  (let ((lock (sb-thread:make-mutex))
        (waitp t))
    (make-join-thread (lambda ()
                        (sb-thread:grab-mutex lock)
                        (setf waitp nil)
                        (sleep 5)))
    (loop while waitp do (sleep 0.01))
    (let ((thread (make-join-thread
                   (lambda ()
                     (let ((start (get-internal-real-time)))
                       (handler-case
                           (sb-sys:with-deadline (:seconds 1)
                             (sb-thread:grab-mutex lock))
                         (sb-sys:deadline-timeout (x)
                           (declare (ignore x))
                           (let ((end (get-internal-real-time)))
                             (float (/ (- end start)
                                       internal-time-units-per-second)
                                    0.0)))))))))
      (sleep 0.3)
      (sb-thread:interrupt-thread thread (lambda () 42))
      (let ((seconds-passed (sb-thread:join-thread thread)))
        (format t "Deadline in ~S~%" seconds-passed)
        (assert (< seconds-passed 1.2))))))
