diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/fixtures/pactl-sink-inputs-active.txt | 18 | ||||
| -rw-r--r-- | tests/fixtures/pactl-sink-inputs-different-sink.txt | 18 | ||||
| -rw-r--r-- | tests/fixtures/pactl-sink-inputs-empty.txt | 1 | ||||
| -rw-r--r-- | tests/test-video-audio-recording--sink-has-active-audio-p.el | 91 | ||||
| -rw-r--r-- | tests/test-video-audio-recording--source-exists-p.el | 67 | ||||
| -rw-r--r-- | tests/test-video-audio-recording-command-structure.el | 72 | ||||
| -rw-r--r-- | tests/test-video-audio-recording-ffmpeg-functions.el | 32 | ||||
| -rw-r--r-- | tests/test-video-audio-recording-get-devices.el | 26 | ||||
| -rw-r--r-- | tests/test-video-audio-recording-process-cleanup.el | 43 | ||||
| -rw-r--r-- | tests/test-video-audio-recording-validate-system-audio.el | 162 |
10 files changed, 479 insertions, 51 deletions
diff --git a/tests/fixtures/pactl-sink-inputs-active.txt b/tests/fixtures/pactl-sink-inputs-active.txt new file mode 100644 index 00000000..d06589e3 --- /dev/null +++ b/tests/fixtures/pactl-sink-inputs-active.txt @@ -0,0 +1,18 @@ +Sink Input #42 + Driver: PipeWire + Owner Module: 4294967295 + Client: 51 + Sink: 65 + Sample Specification: float32le 2ch 48000Hz + Channel Map: front-left,front-right + Format: pcm, format.sample_format = "\"float32le\"" format.rate = "48000" format.channels = "2" + Corked: no + Mute: no + Volume: front-left: 65536 / 100% / 0.00 dB, front-right: 65536 / 100% / 0.00 dB + balance 0.00 + Buffer Latency: 0 usec + Sink Latency: 0 usec + Resample method: PipeWire + Properties: + media.name = "Playback" + application.name = "Firefox" diff --git a/tests/fixtures/pactl-sink-inputs-different-sink.txt b/tests/fixtures/pactl-sink-inputs-different-sink.txt new file mode 100644 index 00000000..859010ed --- /dev/null +++ b/tests/fixtures/pactl-sink-inputs-different-sink.txt @@ -0,0 +1,18 @@ +Sink Input #42 + Driver: PipeWire + Owner Module: 4294967295 + Client: 51 + Sink: 73 + Sample Specification: float32le 2ch 48000Hz + Channel Map: front-left,front-right + Format: pcm, format.sample_format = "\"float32le\"" format.rate = "48000" format.channels = "2" + Corked: no + Mute: no + Volume: front-left: 65536 / 100% / 0.00 dB, front-right: 65536 / 100% / 0.00 dB + balance 0.00 + Buffer Latency: 0 usec + Sink Latency: 0 usec + Resample method: PipeWire + Properties: + media.name = "Playback" + application.name = "Firefox" diff --git a/tests/fixtures/pactl-sink-inputs-empty.txt b/tests/fixtures/pactl-sink-inputs-empty.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/tests/fixtures/pactl-sink-inputs-empty.txt @@ -0,0 +1 @@ + diff --git a/tests/test-video-audio-recording--sink-has-active-audio-p.el b/tests/test-video-audio-recording--sink-has-active-audio-p.el new file mode 100644 index 00000000..eab0745a --- /dev/null +++ b/tests/test-video-audio-recording--sink-has-active-audio-p.el @@ -0,0 +1,91 @@ +;;; test-video-audio-recording--sink-has-active-audio-p.el --- Tests for cj/recording--sink-has-active-audio-p -*- lexical-binding: t; -*- + +;;; Commentary: +;; Unit tests for cj/recording--sink-has-active-audio-p function. +;; Tests parsing of pactl sink-inputs output to detect active audio streams. + +;;; Code: + +(require 'ert) + +;; Stub dependencies before loading the module +(defvar cj/custom-keymap (make-sparse-keymap) + "Stub keymap for testing.") + +(require 'video-audio-recording) + +;;; Test Fixtures Helper + +(defun test-load-fixture (filename) + "Load fixture file FILENAME from tests/fixtures directory." + (let ((fixture-path (expand-file-name + (concat "tests/fixtures/" filename) + user-emacs-directory))) + (with-temp-buffer + (insert-file-contents fixture-path) + (buffer-string)))) + +;;; Normal Cases + +(ert-deftest test-sink-has-active-audio-p-normal-active-sink-returns-t () + "Test that active audio on our sink returns non-nil." + (let ((output (test-load-fixture "pactl-sink-inputs-active.txt"))) + (should (cj/recording--sink-has-active-audio-p "65" output)))) + +(ert-deftest test-sink-has-active-audio-p-normal-different-sink-returns-nil () + "Test that audio on a different sink returns nil." + (let ((output (test-load-fixture "pactl-sink-inputs-different-sink.txt"))) + (should-not (cj/recording--sink-has-active-audio-p "65" output)))) + +;;; Boundary Cases + +(ert-deftest test-sink-has-active-audio-p-boundary-empty-output-returns-nil () + "Test that empty pactl output returns nil." + (should-not (cj/recording--sink-has-active-audio-p "65" ""))) + +(ert-deftest test-sink-has-active-audio-p-boundary-no-sink-inputs-returns-nil () + "Test that output with no sink inputs returns nil." + (let ((output (test-load-fixture "pactl-sink-inputs-empty.txt"))) + (should-not (cj/recording--sink-has-active-audio-p "65" output)))) + +(ert-deftest test-sink-has-active-audio-p-boundary-multiple-inputs-one-matches () + "Test that multiple sink inputs where one matches returns non-nil." + (let ((output (concat "Sink Input #42\n" + "\tSink: 73\n" + "\tCorked: no\n" + "Sink Input #43\n" + "\tSink: 65\n" + "\tCorked: no\n"))) + (should (cj/recording--sink-has-active-audio-p "65" output)))) + +(ert-deftest test-sink-has-active-audio-p-boundary-index-substring-no-false-match () + "Test that sink index 6 does not match sink 65." + (let ((output (test-load-fixture "pactl-sink-inputs-active.txt"))) + (should-not (cj/recording--sink-has-active-audio-p "6" output)))) + +;;; get-sink-index tests + +(ert-deftest test-get-sink-index-normal-returns-index () + "Test that sink name is resolved to its index." + (let ((output "65\talsa_output.usb-Jabra-00.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n")) + (should (equal "65" (cj/recording--get-sink-index + "alsa_output.usb-Jabra-00.analog-stereo" output))))) + +(ert-deftest test-get-sink-index-normal-nonexistent-returns-nil () + "Test that non-existent sink name returns nil." + (let ((output "65\talsa_output.usb-Jabra-00.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n")) + (should-not (cj/recording--get-sink-index "nonexistent-sink" output)))) + +(ert-deftest test-get-sink-index-boundary-empty-output-returns-nil () + "Test that empty output returns nil." + (should-not (cj/recording--get-sink-index "any-sink" ""))) + +(ert-deftest test-get-sink-index-normal-multiple-sinks () + "Test correct index returned when multiple sinks present." + (let ((output (concat "65\talsa_output.pci-0000.hdmi-stereo\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n" + "69\talsa_output.usb-Jabra-00.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n"))) + (should (equal "69" (cj/recording--get-sink-index + "alsa_output.usb-Jabra-00.analog-stereo" output))))) + +(provide 'test-video-audio-recording--sink-has-active-audio-p) +;;; test-video-audio-recording--sink-has-active-audio-p.el ends here diff --git a/tests/test-video-audio-recording--source-exists-p.el b/tests/test-video-audio-recording--source-exists-p.el new file mode 100644 index 00000000..f062ac0f --- /dev/null +++ b/tests/test-video-audio-recording--source-exists-p.el @@ -0,0 +1,67 @@ +;;; test-video-audio-recording--source-exists-p.el --- Tests for cj/recording--source-exists-p -*- lexical-binding: t; -*- + +;;; Commentary: +;; Unit tests for cj/recording--source-exists-p function. +;; Tests checking whether a PulseAudio source exists in pactl output. + +;;; Code: + +(require 'ert) + +;; Stub dependencies before loading the module +(defvar cj/custom-keymap (make-sparse-keymap) + "Stub keymap for testing.") + +(require 'video-audio-recording) + +;;; Test Fixtures Helper + +(defun test-load-fixture (filename) + "Load fixture file FILENAME from tests/fixtures directory." + (let ((fixture-path (expand-file-name + (concat "tests/fixtures/" filename) + user-emacs-directory))) + (with-temp-buffer + (insert-file-contents fixture-path) + (buffer-string)))) + +;;; Normal Cases + +(ert-deftest test-source-exists-p-normal-existing-device-returns-t () + "Test that an existing device returns non-nil." + (let ((output (test-load-fixture "pactl-output-normal.txt"))) + (should (cj/recording--source-exists-p + "alsa_output.pci-0000_00_1f.3.analog-stereo.monitor" output)))) + +(ert-deftest test-source-exists-p-normal-input-device-returns-t () + "Test that an existing input device returns non-nil." + (let ((output (test-load-fixture "pactl-output-normal.txt"))) + (should (cj/recording--source-exists-p + "alsa_input.pci-0000_00_1f.3.analog-stereo" output)))) + +(ert-deftest test-source-exists-p-normal-bluetooth-device-returns-t () + "Test that a Bluetooth device returns non-nil." + (let ((output (test-load-fixture "pactl-output-normal.txt"))) + (should (cj/recording--source-exists-p + "bluez_input.00:1B:66:C0:91:6D" output)))) + +;;; Boundary Cases + +(ert-deftest test-source-exists-p-boundary-nonexistent-device-returns-nil () + "Test that a non-existent device returns nil." + (let ((output (test-load-fixture "pactl-output-normal.txt"))) + (should-not (cj/recording--source-exists-p + "nonexistent_device.monitor" output)))) + +(ert-deftest test-source-exists-p-boundary-empty-output-returns-nil () + "Test that empty pactl output returns nil." + (should-not (cj/recording--source-exists-p "any-device" ""))) + +(ert-deftest test-source-exists-p-boundary-partial-name-no-match () + "Test that partial device name does not match." + (let ((output (test-load-fixture "pactl-output-normal.txt"))) + (should-not (cj/recording--source-exists-p + "alsa_output.pci-0000_00_1f.3.analog-stereo" output)))) + +(provide 'test-video-audio-recording--source-exists-p) +;;; test-video-audio-recording--source-exists-p.el ends here diff --git a/tests/test-video-audio-recording-command-structure.el b/tests/test-video-audio-recording-command-structure.el index c964b246..f4c24c39 100644 --- a/tests/test-video-audio-recording-command-structure.el +++ b/tests/test-video-audio-recording-command-structure.el @@ -52,7 +52,9 @@ ((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) (should (string-match-p "wf-recorder" command)))) (test-command-structure-teardown))) @@ -66,7 +68,9 @@ ((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) (should (string-match-p "wf-recorder -y" command)))) (test-command-structure-teardown))) @@ -80,7 +84,9 @@ ((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) ;; Should use libx264, not h264 (should (string-match-p "-c libx264" command)) @@ -96,7 +102,9 @@ ((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) (should (string-match-p "-m matroska" command)))) (test-command-structure-teardown))) @@ -110,7 +118,9 @@ ((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) ;; Should use -f /dev/stdout, not -o - (should (string-match-p "-f /dev/stdout" command)) @@ -126,7 +136,9 @@ ((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) ;; These flags don't exist in wf-recorder (should-not (string-match-p "--no-audio" command)) @@ -142,7 +154,9 @@ ((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) ;; Should pipe wf-recorder to ffmpeg (should (string-match-p "wf-recorder.*|.*ffmpeg" command)))) @@ -157,7 +171,9 @@ ((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) (should (string-match-p "ffmpeg -i pipe:0" command)))) (test-command-structure-teardown))) @@ -173,7 +189,9 @@ ((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) (should (string-match-p "x11grab" command)))) (test-command-structure-teardown))) @@ -187,7 +205,9 @@ ((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) (should-not (string-match-p "wf-recorder" command)))) (test-command-structure-teardown))) @@ -201,7 +221,9 @@ ((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) (should (string-match-p "-i :0" command)))) (test-command-structure-teardown))) @@ -216,7 +238,9 @@ (cl-letf (((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-audio" :command '("sleep" "1000"))))) + (make-process :name "fake-audio" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-audio audio-recordings-dir) (should (string-match-p "^ffmpeg " command)))) (test-command-structure-teardown))) @@ -229,7 +253,9 @@ (cl-letf (((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-audio" :command '("sleep" "1000"))))) + (make-process :name "fake-audio" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-audio audio-recordings-dir) (should (string-match-p "-f pulse" command)))) (test-command-structure-teardown))) @@ -242,7 +268,9 @@ (cl-letf (((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-audio" :command '("sleep" "1000"))))) + (make-process :name "fake-audio" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-audio audio-recordings-dir) (should (string-match-p "\\.m4a" command)))) (test-command-structure-teardown))) @@ -258,7 +286,9 @@ ((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) (should (string-match-p "-filter_complex" command)) (should (string-match-p "amerge" command)))) @@ -273,7 +303,9 @@ ((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) (should (string-match-p "-map 0:v" command)) (should (string-match-p "-map.*\\[out\\]" command)))) @@ -288,7 +320,9 @@ ((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) (should (string-match-p "-c:v copy" command)))) (test-command-structure-teardown))) @@ -302,7 +336,9 @@ ((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) (should (string-match-p "\\.mkv" command)))) (test-command-structure-teardown))) diff --git a/tests/test-video-audio-recording-ffmpeg-functions.el b/tests/test-video-audio-recording-ffmpeg-functions.el index a3bac0cf..549aa317 100644 --- a/tests/test-video-audio-recording-ffmpeg-functions.el +++ b/tests/test-video-audio-recording-ffmpeg-functions.el @@ -52,7 +52,9 @@ (cl-letf (((symbol-function 'start-process-shell-command) (lambda (_name _buffer _command) (setq process-created t) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) (should process-created) (should cj/video-recording-ffmpeg-process))) @@ -69,7 +71,9 @@ ((symbol-function 'set-process-sentinel) (lambda (_proc sentinel) (should (eq sentinel #'cj/recording-process-sentinel)) - (setq sentinel-attached t)))) + (setq sentinel-attached t))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) (should sentinel-attached))) (test-ffmpeg-teardown))) @@ -83,7 +87,9 @@ (lambda (_name _buffer _command) (make-process :name "fake-video" :command '("sleep" "1000")))) ((symbol-function 'force-mode-line-update) - (lambda (&optional _all) (setq update-called t)))) + (lambda (&optional _all) (setq update-called t))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) (should update-called))) (test-ffmpeg-teardown))) @@ -96,7 +102,9 @@ (cl-letf (((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) (should (string-match-p "test-mic-device" command)) (should (string-match-p "test-monitor-device" command)) @@ -114,7 +122,9 @@ (cl-letf (((symbol-function 'start-process-shell-command) (lambda (_name _buffer _command) (setq process-created t) - (make-process :name "fake-audio" :command '("sleep" "1000"))))) + (make-process :name "fake-audio" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-audio audio-recordings-dir) (should process-created) (should cj/audio-recording-ffmpeg-process))) @@ -131,7 +141,9 @@ ((symbol-function 'set-process-sentinel) (lambda (_proc sentinel) (should (eq sentinel #'cj/recording-process-sentinel)) - (setq sentinel-attached t)))) + (setq sentinel-attached t))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-audio audio-recordings-dir) (should sentinel-attached))) (test-ffmpeg-teardown))) @@ -145,7 +157,9 @@ (lambda (_name _buffer _command) (make-process :name "fake-audio" :command '("sleep" "1000")))) ((symbol-function 'force-mode-line-update) - (lambda (&optional _all) (setq update-called t)))) + (lambda (&optional _all) (setq update-called t))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-audio audio-recordings-dir) (should update-called))) (test-ffmpeg-teardown))) @@ -158,7 +172,9 @@ (cl-letf (((symbol-function 'start-process-shell-command) (lambda (_name _buffer cmd) (setq command cmd) - (make-process :name "fake-audio" :command '("sleep" "1000"))))) + (make-process :name "fake-audio" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-audio audio-recordings-dir) (should (string-match-p "\\.m4a" command)))) (test-ffmpeg-teardown))) diff --git a/tests/test-video-audio-recording-get-devices.el b/tests/test-video-audio-recording-get-devices.el index 0af02bb3..66adecd1 100644 --- a/tests/test-video-audio-recording-get-devices.el +++ b/tests/test-video-audio-recording-get-devices.el @@ -39,10 +39,12 @@ (progn (setq cj/recording-mic-device "preset-mic") (setq cj/recording-system-device "preset-monitor") - (let ((result (cj/recording-get-devices))) - (should (consp result)) - (should (equal "preset-mic" (car result))) - (should (equal "preset-monitor" (cdr result))))) + (cl-letf (((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) + (let ((result (cj/recording-get-devices))) + (should (consp result)) + (should (equal "preset-mic" (car result))) + (should (equal "preset-monitor" (cdr result)))))) (test-get-devices-teardown))) (ert-deftest test-video-audio-recording-get-devices-normal-calls-quick-setup () @@ -54,7 +56,9 @@ (lambda () (setq quick-setup-called t) (setq cj/recording-mic-device "quick-mic") - (setq cj/recording-system-device "quick-monitor")))) + (setq cj/recording-system-device "quick-monitor"))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/recording-get-devices) (should quick-setup-called))) (test-get-devices-teardown))) @@ -66,7 +70,9 @@ (cl-letf (((symbol-function 'cj/recording-quick-setup) (lambda () (setq cj/recording-mic-device "test-mic") - (setq cj/recording-system-device "test-monitor")))) + (setq cj/recording-system-device "test-monitor"))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (let ((result (cj/recording-get-devices))) (should (consp result)) (should (equal "test-mic" (car result))) @@ -87,7 +93,9 @@ (lambda () (setq quick-setup-called t) (setq cj/recording-mic-device "new-mic") - (setq cj/recording-system-device "new-monitor")))) + (setq cj/recording-system-device "new-monitor"))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/recording-get-devices) (should quick-setup-called)))) (test-get-devices-teardown))) @@ -104,7 +112,9 @@ (lambda () (setq quick-setup-called t) (setq cj/recording-mic-device "new-mic") - (setq cj/recording-system-device "new-monitor")))) + (setq cj/recording-system-device "new-monitor"))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/recording-get-devices) (should quick-setup-called)))) (test-get-devices-teardown))) diff --git a/tests/test-video-audio-recording-process-cleanup.el b/tests/test-video-audio-recording-process-cleanup.el index d1cd442c..42b5b96d 100644 --- a/tests/test-video-audio-recording-process-cleanup.el +++ b/tests/test-video-audio-recording-process-cleanup.el @@ -166,7 +166,9 @@ so ffmpeg sees EOF on its video input pipe and starts finalizing the file." 0)) ((symbol-function 'start-process-shell-command) (lambda (_name _buffer _command) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) (should pkill-called) (should (member "-INT" pkill-args)) @@ -186,7 +188,9 @@ so ffmpeg sees EOF on its video input pipe and starts finalizing the file." 0)) ((symbol-function 'start-process-shell-command) (lambda (_name _buffer _command) - (make-process :name "fake-video" :command '("sleep" "1000"))))) + (make-process :name "fake-video" :command '("sleep" "1000")))) + ((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) (cj/ffmpeg-record-video video-recordings-dir) (should-not pkill-called))) (test-cleanup-teardown))) @@ -299,13 +303,15 @@ This is an integration test that requires wf-recorder and Wayland." (skip-unless (cj/recording--wayland-p)) (test-cleanup-setup) (unwind-protect - (let ((initial-count (test-cleanup--count-wf-recorder-processes))) - (cj/ffmpeg-record-video video-recordings-dir) - (sit-for 1.0) - (should (> (test-cleanup--count-wf-recorder-processes) initial-count)) - (cj/video-recording-stop) - (sit-for 1.0) - (should (= (test-cleanup--count-wf-recorder-processes) initial-count))) + (cl-letf (((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) + (let ((initial-count (test-cleanup--count-wf-recorder-processes))) + (cj/ffmpeg-record-video video-recordings-dir) + (sit-for 1.0) + (should (> (test-cleanup--count-wf-recorder-processes) initial-count)) + (cj/video-recording-stop) + (sit-for 1.0) + (should (= (test-cleanup--count-wf-recorder-processes) initial-count)))) (test-cleanup-teardown) (ignore-errors (call-process "pkill" nil nil nil "-INT" "wf-recorder")))) @@ -317,7 +323,8 @@ This is an integration test that requires wf-recorder and Wayland." (skip-unless (cj/recording--wayland-p)) (test-cleanup-setup) (unwind-protect - (progn + (cl-letf (((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) ;; Create an orphan wf-recorder (simulating a crash) (start-process "orphan-wf" nil "wf-recorder" "-c" "libx264" "-m" "matroska" "-f" "/dev/null") (sit-for 0.5) @@ -342,13 +349,15 @@ This is an integration test that requires wf-recorder and Wayland." (skip-unless (cj/recording--wayland-p)) (test-cleanup-setup) (unwind-protect - (let ((initial-count (test-cleanup--count-wf-recorder-processes))) - (dotimes (_ 3) - (cj/ffmpeg-record-video video-recordings-dir) - (sit-for 0.5) - (cj/video-recording-stop) - (sit-for 0.5)) - (should (= (test-cleanup--count-wf-recorder-processes) initial-count))) + (cl-letf (((symbol-function 'cj/recording--validate-system-audio) + (lambda () nil))) + (let ((initial-count (test-cleanup--count-wf-recorder-processes))) + (dotimes (_ 3) + (cj/ffmpeg-record-video video-recordings-dir) + (sit-for 0.5) + (cj/video-recording-stop) + (sit-for 0.5)) + (should (= (test-cleanup--count-wf-recorder-processes) initial-count)))) (test-cleanup-teardown) (ignore-errors (call-process "pkill" nil nil nil "-INT" "wf-recorder")))) diff --git a/tests/test-video-audio-recording-validate-system-audio.el b/tests/test-video-audio-recording-validate-system-audio.el new file mode 100644 index 00000000..ef730ab3 --- /dev/null +++ b/tests/test-video-audio-recording-validate-system-audio.el @@ -0,0 +1,162 @@ +;;; test-video-audio-recording-validate-system-audio.el --- Tests for cj/recording--validate-system-audio -*- lexical-binding: t; -*- + +;;; Commentary: +;; Unit tests for cj/recording--validate-system-audio function. +;; Tests the pre-recording validation that catches stale/drifted system +;; audio devices before they cause silent recordings. + +;;; Code: + +(require 'ert) + +;; Stub dependencies before loading the module +(defvar cj/custom-keymap (make-sparse-keymap) + "Stub keymap for testing.") + +(require 'video-audio-recording) + +;;; Setup and Teardown + +(defun test-validate-setup () + "Reset device variables before each test." + (setq cj/recording-system-device nil)) + +(defun test-validate-teardown () + "Clean up device variables after each test." + (setq cj/recording-system-device nil)) + +;;; Normal Cases + +(ert-deftest test-validate-system-audio-normal-device-matches-default-no-change () + "Test that no change occurs when device matches current default and audio is active." + (test-validate-setup) + (unwind-protect + (let ((cj/recording-system-device "alsa_output.usb-Jabra-00.analog-stereo.monitor")) + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (cmd) + (cond + ((string-match-p "sources short" cmd) + "65\talsa_output.usb-Jabra-00.analog-stereo.monitor\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n") + ((string-match-p "sinks short" cmd) + "65\talsa_output.usb-Jabra-00.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n") + ((string-match-p "sink-inputs" cmd) "Sink Input #1\n\tSink: 65\n") + ((string-match-p "get-default-sink" cmd) "alsa_output.usb-Jabra-00.analog-stereo") + (t ""))))) + (cj/recording--validate-system-audio) + (should (equal "alsa_output.usb-Jabra-00.analog-stereo.monitor" + cj/recording-system-device)))) + (test-validate-teardown))) + +(ert-deftest test-validate-system-audio-normal-stale-device-auto-updates () + "Test that a stale (non-existent) device is auto-updated to current default." + (test-validate-setup) + (unwind-protect + (let ((cj/recording-system-device "old_disappeared_device.monitor") + (messages nil)) + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (cmd) + (cond + ((string-match-p "sources short" cmd) + ;; Old device NOT in list + "65\talsa_output.usb-Jabra-00.analog-stereo.monitor\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n") + ((string-match-p "sinks short" cmd) + "65\talsa_output.usb-Jabra-00.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n") + ((string-match-p "sink-inputs" cmd) "Sink Input #1\n\tSink: 65\n") + ((string-match-p "get-default-sink" cmd) "alsa_output.usb-Jabra-00.analog-stereo") + (t "")))) + ((symbol-function 'message) + (lambda (fmt &rest args) + (push (apply #'format fmt args) messages)))) + (cj/recording--validate-system-audio) + (should (equal "alsa_output.usb-Jabra-00.analog-stereo.monitor" + cj/recording-system-device)) + (should (cl-some (lambda (m) (string-match-p "no longer exists" m)) messages)))) + (test-validate-teardown))) + +(ert-deftest test-validate-system-audio-normal-drifted-default-auto-updates () + "Test that device is updated when default sink has drifted." + (test-validate-setup) + (unwind-protect + (let ((cj/recording-system-device "alsa_output.pci-0000.hdmi-stereo.monitor") + (messages nil)) + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (cmd) + (cond + ((string-match-p "sources short" cmd) + ;; Old device still exists + (concat "65\talsa_output.pci-0000.hdmi-stereo.monitor\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n" + "69\talsa_output.usb-Jabra-00.analog-stereo.monitor\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n")) + ((string-match-p "sinks short" cmd) + "69\talsa_output.usb-Jabra-00.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n") + ((string-match-p "sink-inputs" cmd) "Sink Input #1\n\tSink: 69\n") + ;; But default has changed to Jabra + ((string-match-p "get-default-sink" cmd) "alsa_output.usb-Jabra-00.analog-stereo") + (t "")))) + ((symbol-function 'message) + (lambda (fmt &rest args) + (push (apply #'format fmt args) messages)))) + (cj/recording--validate-system-audio) + (should (equal "alsa_output.usb-Jabra-00.analog-stereo.monitor" + cj/recording-system-device)) + (should (cl-some (lambda (m) (string-match-p "default output changed" m)) messages)))) + (test-validate-teardown))) + +(ert-deftest test-validate-system-audio-normal-no-audio-warns () + "Test that no active audio triggers a y-or-n-p warning." + (test-validate-setup) + (unwind-protect + (let ((cj/recording-system-device "alsa_output.usb-Jabra-00.analog-stereo.monitor") + (prompted nil)) + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (cmd) + (cond + ((string-match-p "sources short" cmd) + "65\talsa_output.usb-Jabra-00.analog-stereo.monitor\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n") + ((string-match-p "sinks short" cmd) + "65\talsa_output.usb-Jabra-00.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n") + ;; No sink inputs — nothing playing + ((string-match-p "sink-inputs" cmd) "") + ((string-match-p "get-default-sink" cmd) "alsa_output.usb-Jabra-00.analog-stereo") + (t "")))) + ((symbol-function 'y-or-n-p) + (lambda (prompt) + (setq prompted prompt) + t))) ; User says yes, continue + (cj/recording--validate-system-audio) + (should prompted) + (should (string-match-p "No audio is playing" prompted)))) + (test-validate-teardown))) + +(ert-deftest test-validate-system-audio-normal-no-audio-user-cancels () + "Test that user declining the warning cancels recording." + (test-validate-setup) + (unwind-protect + (let ((cj/recording-system-device "alsa_output.usb-Jabra-00.analog-stereo.monitor")) + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (cmd) + (cond + ((string-match-p "sources short" cmd) + "65\talsa_output.usb-Jabra-00.analog-stereo.monitor\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n") + ((string-match-p "sinks short" cmd) + "65\talsa_output.usb-Jabra-00.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n") + ((string-match-p "sink-inputs" cmd) "") + ((string-match-p "get-default-sink" cmd) "alsa_output.usb-Jabra-00.analog-stereo") + (t "")))) + ((symbol-function 'y-or-n-p) + (lambda (_prompt) nil))) ; User says no + (should-error (cj/recording--validate-system-audio) :type 'user-error))) + (test-validate-teardown))) + +;;; Boundary Cases + +(ert-deftest test-validate-system-audio-boundary-nil-device-skips-validation () + "Test that nil system device skips all validation." + (let ((cj/recording-system-device nil) + (shell-called nil)) + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (_cmd) (setq shell-called t) ""))) + (cj/recording--validate-system-audio) + (should-not shell-called)))) + +(provide 'test-video-audio-recording-validate-system-audio) +;;; test-video-audio-recording-validate-system-audio.el ends here |
