summaryrefslogtreecommitdiff
path: root/tests/test-video-audio-recording-parse-pactl-output.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-11-03 15:26:11 -0600
committerCraig Jennings <c@cjennings.net>2025-11-03 15:26:11 -0600
commit0a69c5854378afcafc567d965f206cf6a0a984be (patch)
tree923425a2aaf106c51cf5a3100cdf42f98e534da8 /tests/test-video-audio-recording-parse-pactl-output.el
parent9f8aec55033d46ca4f1cd78fbd315444a0c00bc6 (diff)
test: Add comprehensive test suite for video-audio-recording module
Added 83 test cases across 9 test files with 100% pass rate, covering device detection, parsing, grouping, and complete workflow integration. ## What Was Done ### Refactoring for Testability - Extracted `cj/recording--parse-pactl-output` from `cj/recording-parse-sources` - Separated parsing logic from shell command execution - Enables testing with fixture data instead of live system calls ### Test Fixtures Created - `pactl-output-normal.txt` - All device types (built-in, USB, Bluetooth) - `pactl-output-empty.txt` - Empty output - `pactl-output-single.txt` - Single device - `pactl-output-monitors-only.txt` - Only monitor devices - `pactl-output-inputs-only.txt` - Only input devices - `pactl-output-malformed.txt` - Invalid/malformed output ### Unit Tests (8 files, 78 test cases) 1. **test-video-audio-recording-friendly-state.el** (10 tests) - State name conversion: SUSPENDED→Ready, RUNNING→Active 2. **test-video-audio-recording-parse-pactl-output.el** (14 tests) - Parse raw pactl output into structured data - Handle empty, malformed, and mixed valid/invalid input 3. **test-video-audio-recording-parse-sources.el** (6 tests) - Shell command wrapper testing with mocked output 4. **test-video-audio-recording-detect-mic-device.el** (13 tests) - Documents bugs: Returns ID numbers instead of device names - Doesn't filter monitors (legacy function, not actively used) 5. **test-video-audio-recording-detect-system-device.el** (13 tests) - Works correctly: Returns full device names - Tests monitor detection with various device types 6. **test-video-audio-recording-group-devices-by-hardware.el** (12 tests) - CRITICAL: Bluetooth MAC address normalization (colons vs underscores) - Device pairing logic (mic + monitor from same hardware) - Friendly name assignment - Filters incomplete devices 7. **test-video-audio-recording-check-ffmpeg.el** (3 tests) - ffmpeg availability detection 8. **test-video-audio-recording-get-devices.el** (7 tests) - Auto-detection fallback logic - Error handling for incomplete detection ### Integration Tests (1 file, 5 test cases) 9. **test-integration-recording-device-workflow.el** (5 tests) - Complete workflow: parse → group → friendly names - Bluetooth MAC normalization end-to-end - Incomplete device filtering across components - Malformed data graceful handling ## Key Testing Insights ### Bugs Documented - `cj/recording-detect-mic-device` has bugs (returns IDs, doesn't filter monitors) - These functions appear to be legacy code not used by main workflow - Tests document current behavior to catch regressions if fixed ### Critical Features Validated - **Bluetooth MAC normalization**: Input uses colons (00:1B:66:C0:91:6D), output uses underscores (00_1B_66_C0_91_6D), grouping normalizes correctly - **Device pairing**: Only devices with BOTH mic and monitor are included - **Friendly names**: USB/PCI/Bluetooth patterns correctly identified ### Test Coverage - Normal cases: Valid inputs, typical workflows - Boundary cases: Empty, single device, incomplete pairs - Error cases: Malformed input, missing devices, partial detection ## Test Execution All tests pass: 9/9 files, 83/83 test cases (100% pass rate) ```bash make test-file FILE=test-video-audio-recording-*.el # All pass individually # Integration test also passes make test-file FILE=test-integration-recording-device-workflow.el ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'tests/test-video-audio-recording-parse-pactl-output.el')
-rw-r--r--tests/test-video-audio-recording-parse-pactl-output.el157
1 files changed, 157 insertions, 0 deletions
diff --git a/tests/test-video-audio-recording-parse-pactl-output.el b/tests/test-video-audio-recording-parse-pactl-output.el
new file mode 100644
index 00000000..db49a897
--- /dev/null
+++ b/tests/test-video-audio-recording-parse-pactl-output.el
@@ -0,0 +1,157 @@
+;;; test-video-audio-recording-parse-pactl-output.el --- Tests for cj/recording--parse-pactl-output -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Unit tests for cj/recording--parse-pactl-output function.
+;; Tests parsing of pactl sources output into structured data.
+;; Uses fixture files with sample pactl output for reproducible testing.
+
+;;; Code:
+
+(require 'ert)
+
+;; Stub dependencies before loading the module
+(defvar cj/custom-keymap (make-sparse-keymap)
+ "Stub keymap for testing.")
+
+;; Now load the actual production module
+(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-video-audio-recording-parse-pactl-output-normal-all-devices-returns-list ()
+ "Test parsing normal pactl output with all device types."
+ (let* ((output (test-load-fixture "pactl-output-normal.txt"))
+ (result (cj/recording--parse-pactl-output output)))
+ (should (listp result))
+ (should (= 6 (length result)))
+ ;; Check first device (built-in monitor)
+ (should (equal '("alsa_output.pci-0000_00_1f.3.analog-stereo.monitor"
+ "PipeWire"
+ "SUSPENDED")
+ (nth 0 result)))
+ ;; Check Bluetooth input
+ (should (equal '("bluez_input.00:1B:66:C0:91:6D"
+ "PipeWire"
+ "SUSPENDED")
+ (nth 2 result)))
+ ;; Check USB device
+ (should (equal '("alsa_input.usb-0b0e_Jabra_SPEAK_510_USB_1C48F9C067D5020A00-00.mono-fallback"
+ "PipeWire"
+ "SUSPENDED")
+ (nth 5 result)))))
+
+(ert-deftest test-video-audio-recording-parse-pactl-output-normal-single-device-returns-list ()
+ "Test parsing output with single device."
+ (let* ((output (test-load-fixture "pactl-output-single.txt"))
+ (result (cj/recording--parse-pactl-output output)))
+ (should (listp result))
+ (should (= 1 (length result)))
+ (should (equal '("alsa_input.pci-0000_00_1f.3.analog-stereo"
+ "PipeWire"
+ "SUSPENDED")
+ (car result)))))
+
+(ert-deftest test-video-audio-recording-parse-pactl-output-normal-monitors-only-returns-list ()
+ "Test parsing output with only monitor devices."
+ (let* ((output (test-load-fixture "pactl-output-monitors-only.txt"))
+ (result (cj/recording--parse-pactl-output output)))
+ (should (listp result))
+ (should (= 3 (length result)))
+ ;; All should end with .monitor
+ (dolist (device result)
+ (should (string-suffix-p ".monitor" (car device))))))
+
+(ert-deftest test-video-audio-recording-parse-pactl-output-normal-inputs-only-returns-list ()
+ "Test parsing output with only input devices."
+ (let* ((output (test-load-fixture "pactl-output-inputs-only.txt"))
+ (result (cj/recording--parse-pactl-output output)))
+ (should (listp result))
+ (should (= 3 (length result)))
+ ;; None should end with .monitor
+ (dolist (device result)
+ (should-not (string-suffix-p ".monitor" (car device))))))
+
+;;; Boundary Cases
+
+(ert-deftest test-video-audio-recording-parse-pactl-output-boundary-empty-string-returns-empty-list ()
+ "Test parsing empty string returns empty list."
+ (let ((result (cj/recording--parse-pactl-output "")))
+ (should (listp result))
+ (should (null result))))
+
+(ert-deftest test-video-audio-recording-parse-pactl-output-boundary-empty-file-returns-empty-list ()
+ "Test parsing empty file returns empty list."
+ (let* ((output (test-load-fixture "pactl-output-empty.txt"))
+ (result (cj/recording--parse-pactl-output output)))
+ (should (listp result))
+ (should (null result))))
+
+(ert-deftest test-video-audio-recording-parse-pactl-output-boundary-whitespace-only-returns-empty-list ()
+ "Test parsing whitespace-only string returns empty list."
+ (let ((result (cj/recording--parse-pactl-output " \n\t\n ")))
+ (should (listp result))
+ (should (null result))))
+
+(ert-deftest test-video-audio-recording-parse-pactl-output-boundary-single-newline-returns-empty-list ()
+ "Test parsing single newline returns empty list."
+ (let ((result (cj/recording--parse-pactl-output "\n")))
+ (should (listp result))
+ (should (null result))))
+
+(ert-deftest test-video-audio-recording-parse-pactl-output-boundary-device-with-running-state-parsed ()
+ "Test that RUNNING state (not just SUSPENDED) is parsed correctly."
+ (let* ((output "81\tbluez_output.00_1B_66_C0_91_6D.1.monitor\tPipeWire\ts24le 2ch 48000Hz\tRUNNING\n")
+ (result (cj/recording--parse-pactl-output output)))
+ (should (= 1 (length result)))
+ (should (equal "RUNNING" (nth 2 (car result))))))
+
+(ert-deftest test-video-audio-recording-parse-pactl-output-boundary-device-with-idle-state-parsed ()
+ "Test that IDLE state is parsed correctly."
+ (let* ((output "50\talsa_input.pci-0000_00_1f.3.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tIDLE\n")
+ (result (cj/recording--parse-pactl-output output)))
+ (should (= 1 (length result)))
+ (should (equal "IDLE" (nth 2 (car result))))))
+
+;;; Error Cases
+
+(ert-deftest test-video-audio-recording-parse-pactl-output-error-malformed-lines-ignored ()
+ "Test that malformed lines are silently ignored."
+ (let* ((output (test-load-fixture "pactl-output-malformed.txt"))
+ (result (cj/recording--parse-pactl-output output)))
+ (should (listp result))
+ (should (null result)))) ; All lines malformed, so empty list
+
+(ert-deftest test-video-audio-recording-parse-pactl-output-error-mixed-valid-invalid-returns-valid ()
+ "Test that mix of valid and invalid lines returns only valid ones."
+ (let* ((output (concat "50\talsa_input.pci-0000_00_1f.3.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n"
+ "This is invalid\n"
+ "79\tbluez_input.00:1B:66:C0:91:6D\tPipeWire\tfloat32le 1ch 48000Hz\tSUSPENDED\n"
+ "Also invalid\n"))
+ (result (cj/recording--parse-pactl-output output)))
+ (should (= 2 (length result)))
+ (should (equal "alsa_input.pci-0000_00_1f.3.analog-stereo" (car (nth 0 result))))
+ (should (equal "bluez_input.00:1B:66:C0:91:6D" (car (nth 1 result))))))
+
+(ert-deftest test-video-audio-recording-parse-pactl-output-error-missing-fields-ignored ()
+ "Test that lines with missing fields are ignored."
+ (let* ((output "50\tincomplete-line\tPipeWire\n") ; Missing state and format
+ (result (cj/recording--parse-pactl-output output)))
+ (should (null result))))
+
+(ert-deftest test-video-audio-recording-parse-pactl-output-error-nil-input-returns-error ()
+ "Test that nil input signals an error."
+ (should-error (cj/recording--parse-pactl-output nil)))
+
+(provide 'test-video-audio-recording-parse-pactl-output)
+;;; test-video-audio-recording-parse-pactl-output.el ends here