aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/video-audio-recording-capture.el23
-rw-r--r--tests/test-video-audio-recording-process-sentinel.el152
2 files changed, 171 insertions, 4 deletions
diff --git a/modules/video-audio-recording-capture.el b/modules/video-audio-recording-capture.el
index 41b72ebd..5e7860fc 100644
--- a/modules/video-audio-recording-capture.el
+++ b/modules/video-audio-recording-capture.el
@@ -76,9 +76,13 @@ is killed externally."
(cj/recording--start-failed-p (- (float-time) start)
cj/recording-start-fail-threshold))
;; Died almost immediately and the user didn't stop it: wf-recorder
- ;; couldn't grab the screen. Say so instead of silently clearing,
- ;; so Craig isn't left blind-retrying (which bursts fragment files).
- (message "Video recording failed to start (wf-recorder couldn't grab the screen). Try again.")
+ ;; couldn't grab the screen. Delete the ~0.5s stub file the failed
+ ;; start wrote (it would otherwise litter the recordings directory
+ ;; and get swept up by *.mkv globs downstream), then say so instead
+ ;; of silently clearing, so Craig isn't left blind-retrying.
+ (progn
+ (cj/recording--delete-failed-start-stub process)
+ (message "Video recording failed to start (wf-recorder couldn't grab the screen). Try again."))
(message "Video recording stopped: %s" (string-trim event))))))
(force-mode-line-update t)))
@@ -129,6 +133,15 @@ launching too soon loses the grab and produces a ~0.5s fragment file."
A failed wf-recorder start exits almost immediately; a real recording does not."
(< elapsed threshold))
+(defun cj/recording--delete-failed-start-stub (process)
+ "Delete the stub output file a failed video start left behind.
+Reads the output path from PROCESS's `cj-output-file' property (stamped
+by `cj/ffmpeg-record-video'). A no-op when the property is absent (a
+process started before the property existed) or the file never hit disk."
+ (let ((file (process-get process 'cj-output-file)))
+ (when (and file (file-exists-p file))
+ (delete-file file))))
+
;;; Dependency Checks
(defun cj/recording-check-ffmpeg ()
@@ -332,8 +345,10 @@ Uses wf-recorder on Wayland, x11grab on X11."
(set-process-query-on-exit-flag cj/video-recording-ffmpeg-process nil)
(set-process-sentinel cj/video-recording-ffmpeg-process #'cj/recording-process-sentinel)
;; Stamp the start time so the sentinel can tell a ~0.5s failed start
- ;; (wf-recorder couldn't grab the screen) from a normal recording.
+ ;; (wf-recorder couldn't grab the screen) from a normal recording, and
+ ;; the output path so the failed-start branch can delete the stub file.
(process-put cj/video-recording-ffmpeg-process 'cj-start-time (float-time))
+ (process-put cj/video-recording-ffmpeg-process 'cj-output-file filename)
(force-mode-line-update t)
(message "Started video recording to %s (%s, mic: %.1fx, system: %.1fx)."
filename
diff --git a/tests/test-video-audio-recording-process-sentinel.el b/tests/test-video-audio-recording-process-sentinel.el
index 92fb3f0d..d733e46f 100644
--- a/tests/test-video-audio-recording-process-sentinel.el
+++ b/tests/test-video-audio-recording-process-sentinel.el
@@ -190,5 +190,157 @@
(should (null cj/audio-recording-ffmpeg-process))))
(test-sentinel-teardown)))
+;;; Failed-Start Stub Deletion
+;;
+;; On Wayland a wf-recorder that fails to grab the compositor capture
+;; still writes a ~500KB, ~0.5s stub .mkv before dying. The sentinel
+;; detects the failed start (exit sooner than
+;; `cj/recording-start-fail-threshold' without a user stop); these tests
+;; pin that it also deletes the stub file stamped on the process as the
+;; `cj-output-file' property — and that normal stops and user stops
+;; never delete anything.
+
+(defun test-sentinel--make-exited-process ()
+ "Return a real process that has already exited.
+Drives the sentinel with a genuinely dead process so `process-status'
+and the process plist behave for real instead of through mocks."
+ (let ((proc (make-process :name "test-sentinel-exited"
+ :command '("true")
+ :sentinel #'ignore)))
+ (while (process-live-p proc)
+ (accept-process-output proc 0.05))
+ proc))
+
+(defun test-sentinel--make-stub-file ()
+ "Create and return a temp file standing in for the stub .mkv."
+ (make-temp-file "test-sentinel-stub-" nil ".mkv" "stub-content"))
+
+(ert-deftest test-video-audio-recording-process-sentinel-normal-failed-start-deletes-stub ()
+ "Normal: a failed video start deletes the stub output file."
+ (test-sentinel-setup)
+ (unwind-protect
+ (let ((proc (test-sentinel--make-exited-process))
+ (stub (test-sentinel--make-stub-file)))
+ (unwind-protect
+ (progn
+ (setq cj/video-recording-ffmpeg-process proc)
+ ;; Exited immediately after its start time — a failed start.
+ (process-put proc 'cj-start-time (float-time))
+ (process-put proc 'cj-output-file stub)
+ (cj/recording-process-sentinel proc "exited abnormally\n")
+ (should-not (file-exists-p stub)))
+ (when (file-exists-p stub) (delete-file stub))))
+ (test-sentinel-teardown)))
+
+(ert-deftest test-video-audio-recording-process-sentinel-normal-failed-start-still-messages ()
+ "Normal: the failed-start branch still reports the failure to the user."
+ (test-sentinel-setup)
+ (unwind-protect
+ (let ((proc (test-sentinel--make-exited-process))
+ (stub (test-sentinel--make-stub-file))
+ (failure-messaged nil))
+ (unwind-protect
+ (progn
+ (setq cj/video-recording-ffmpeg-process proc)
+ (process-put proc 'cj-start-time (float-time))
+ (process-put proc 'cj-output-file stub)
+ (cl-letf (((symbol-function 'message)
+ (lambda (fmt &rest args)
+ (let ((msg (apply #'format fmt args)))
+ (when (string-match-p "failed to start" msg)
+ (setq failure-messaged t))))))
+ (cj/recording-process-sentinel proc "exited abnormally\n"))
+ (should failure-messaged))
+ (when (file-exists-p stub) (delete-file stub))))
+ (test-sentinel-teardown)))
+
+(ert-deftest test-video-audio-recording-process-sentinel-normal-long-run-keeps-file ()
+ "Normal: a recording that ran past the threshold keeps its output file."
+ (test-sentinel-setup)
+ (unwind-protect
+ (let ((proc (test-sentinel--make-exited-process))
+ (stub (test-sentinel--make-stub-file)))
+ (unwind-protect
+ (progn
+ (setq cj/video-recording-ffmpeg-process proc)
+ ;; Ran well past the fail threshold — a real recording.
+ (process-put proc 'cj-start-time
+ (- (float-time)
+ (* 10 cj/recording-start-fail-threshold)))
+ (process-put proc 'cj-output-file stub)
+ (cj/recording-process-sentinel proc "finished\n")
+ (should (file-exists-p stub)))
+ (when (file-exists-p stub) (delete-file stub))))
+ (test-sentinel-teardown)))
+
+(ert-deftest test-video-audio-recording-process-sentinel-boundary-user-stop-keeps-file ()
+ "Boundary: a quick user stop (cj-stopping) never deletes the file."
+ (test-sentinel-setup)
+ (unwind-protect
+ (let ((proc (test-sentinel--make-exited-process))
+ (stub (test-sentinel--make-stub-file)))
+ (unwind-protect
+ (progn
+ (setq cj/video-recording-ffmpeg-process proc)
+ ;; Quick exit, but the user asked for it.
+ (process-put proc 'cj-start-time (float-time))
+ (process-put proc 'cj-stopping t)
+ (process-put proc 'cj-output-file stub)
+ (cj/recording-process-sentinel proc "finished\n")
+ (should (file-exists-p stub)))
+ (when (file-exists-p stub) (delete-file stub))))
+ (test-sentinel-teardown)))
+
+(ert-deftest test-video-audio-recording-process-sentinel-boundary-missing-stub-no-error ()
+ "Boundary: failed start whose stub never hit disk signals no error."
+ (test-sentinel-setup)
+ (unwind-protect
+ (let ((proc (test-sentinel--make-exited-process)))
+ (setq cj/video-recording-ffmpeg-process proc)
+ (process-put proc 'cj-start-time (float-time))
+ (process-put proc 'cj-output-file "/nonexistent/dir/never-written.mkv")
+ ;; Must not signal even though the file is absent.
+ (cj/recording-process-sentinel proc "exited abnormally\n")
+ (should (null cj/video-recording-ffmpeg-process)))
+ (test-sentinel-teardown)))
+
+(ert-deftest test-video-audio-recording-process-sentinel-error-nil-output-property-no-error ()
+ "Error: failed start with no cj-output-file property signals no error.
+Covers processes started before the property existed (a live daemon
+mid-upgrade) — the sentinel degrades to the old message-only path."
+ (test-sentinel-setup)
+ (unwind-protect
+ (let ((proc (test-sentinel--make-exited-process)))
+ (setq cj/video-recording-ffmpeg-process proc)
+ (process-put proc 'cj-start-time (float-time))
+ ;; No cj-output-file property at all.
+ (cj/recording-process-sentinel proc "exited abnormally\n")
+ (should (null cj/video-recording-ffmpeg-process)))
+ (test-sentinel-teardown)))
+
+(ert-deftest test-video-audio-recording-process-sentinel-normal-start-stamps-output-file ()
+ "Normal: `cj/ffmpeg-record-video' stamps cj-output-file on the process."
+ (test-sentinel-setup)
+ (unwind-protect
+ (let ((cj/recording-mic-device "test-mic-device")
+ (cj/recording-system-device "test-monitor-device")
+ (cj/recording-mic-boost 2.0)
+ (cj/recording-system-volume 1.0))
+ (cl-letf (((symbol-function 'cj/recording--wayland-p) (lambda () nil))
+ ((symbol-function 'cj/recording--validate-system-audio)
+ (lambda () nil))
+ ((symbol-function 'start-process-shell-command)
+ (lambda (_name _buffer _command)
+ (make-process :name "fake-video" :command '("sleep" "1000")))))
+ (cj/ffmpeg-record-video "/tmp/video-recordings/")
+ (let ((output-file (process-get cj/video-recording-ffmpeg-process
+ 'cj-output-file)))
+ (should (stringp output-file))
+ (should (string-suffix-p ".mkv" output-file))
+ (should (string-prefix-p "/tmp/video-recordings/" output-file))))
+ (when cj/video-recording-ffmpeg-process
+ (ignore-errors (delete-process cj/video-recording-ffmpeg-process))))
+ (test-sentinel-teardown)))
+
(provide 'test-video-audio-recording-process-sentinel)
;;; test-video-audio-recording-process-sentinel.el ends here