diff options
| author | Craig Jennings <c@cjennings.net> | 2025-11-03 15:26:11 -0600 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-11-03 15:26:11 -0600 |
| commit | 0a69c5854378afcafc567d965f206cf6a0a984be (patch) | |
| tree | 923425a2aaf106c51cf5a3100cdf42f98e534da8 /tests/test-video-audio-recording-group-devices-by-hardware.el | |
| parent | 9f8aec55033d46ca4f1cd78fbd315444a0c00bc6 (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-group-devices-by-hardware.el')
| -rw-r--r-- | tests/test-video-audio-recording-group-devices-by-hardware.el | 194 |
1 files changed, 194 insertions, 0 deletions
diff --git a/tests/test-video-audio-recording-group-devices-by-hardware.el b/tests/test-video-audio-recording-group-devices-by-hardware.el new file mode 100644 index 00000000..0abe5f6c --- /dev/null +++ b/tests/test-video-audio-recording-group-devices-by-hardware.el @@ -0,0 +1,194 @@ +;;; test-video-audio-recording-group-devices-by-hardware.el --- Tests for cj/recording-group-devices-by-hardware -*- lexical-binding: t; -*- + +;;; Commentary: +;; Unit tests for cj/recording-group-devices-by-hardware function. +;; Tests grouping of audio sources by physical hardware device. +;; Critical test: Bluetooth MAC address normalization (colons vs underscores). +;; +;; This function is used by the quick setup command to automatically pair +;; microphone and monitor devices from the same hardware. + +;;; 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-group-devices-by-hardware-normal-all-types-grouped () + "Test grouping of all three device types (built-in, USB, Bluetooth). +This is the key test validating the complete grouping logic." + (let ((output (test-load-fixture "pactl-output-normal.txt"))) + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (_cmd) output))) + (let ((result (cj/recording-group-devices-by-hardware))) + (should (listp result)) + (should (= 3 (length result))) + ;; Check that we have all three device types + (let ((names (mapcar #'car result))) + (should (member "Built-in Laptop Audio" names)) + (should (member "Bluetooth Headset" names)) + (should (member "Jabra SPEAK 510 USB" names))) + ;; Verify each device has both mic and monitor + (dolist (device result) + (should (stringp (car device))) ; friendly name + (should (stringp (cadr device))) ; mic device + (should (stringp (cddr device))) ; monitor device + (should-not (string-suffix-p ".monitor" (cadr device))) ; mic not monitor + (should (string-suffix-p ".monitor" (cddr device)))))))) ; monitor has suffix + +(ert-deftest test-video-audio-recording-group-devices-by-hardware-normal-built-in-paired () + "Test that built-in laptop audio devices are correctly paired." + (let ((output (test-load-fixture "pactl-output-normal.txt"))) + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (_cmd) output))) + (let* ((result (cj/recording-group-devices-by-hardware)) + (built-in (assoc "Built-in Laptop Audio" result))) + (should built-in) + (should (string-match-p "pci-0000_00_1f" (cadr built-in))) + (should (string-match-p "pci-0000_00_1f" (cddr built-in))) + (should (equal "alsa_input.pci-0000_00_1f.3.analog-stereo" (cadr built-in))) + (should (equal "alsa_output.pci-0000_00_1f.3.analog-stereo.monitor" (cddr built-in))))))) + +(ert-deftest test-video-audio-recording-group-devices-by-hardware-normal-usb-paired () + "Test that USB devices (Jabra) are correctly paired." + (let ((output (test-load-fixture "pactl-output-normal.txt"))) + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (_cmd) output))) + (let* ((result (cj/recording-group-devices-by-hardware)) + (jabra (assoc "Jabra SPEAK 510 USB" result))) + (should jabra) + (should (string-match-p "Jabra" (cadr jabra))) + (should (string-match-p "Jabra" (cddr jabra))))))) + +(ert-deftest test-video-audio-recording-group-devices-by-hardware-normal-bluetooth-paired () + "Test that Bluetooth devices are correctly paired. +CRITICAL: Tests MAC address normalization (colons in input, underscores in output)." + (let ((output (test-load-fixture "pactl-output-normal.txt"))) + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (_cmd) output))) + (let* ((result (cj/recording-group-devices-by-hardware)) + (bluetooth (assoc "Bluetooth Headset" result))) + (should bluetooth) + ;; Input has colons: bluez_input.00:1B:66:C0:91:6D + (should (equal "bluez_input.00:1B:66:C0:91:6D" (cadr bluetooth))) + ;; Output has underscores: bluez_output.00_1B_66_C0_91_6D.1.monitor + ;; But they should still be grouped together (MAC address normalized) + (should (equal "bluez_output.00_1B_66_C0_91_6D.1.monitor" (cddr bluetooth))))))) + +;;; Boundary Cases + +(ert-deftest test-video-audio-recording-group-devices-by-hardware-boundary-empty-returns-empty () + "Test that empty pactl output returns empty list." + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (_cmd) ""))) + (let ((result (cj/recording-group-devices-by-hardware))) + (should (listp result)) + (should (null result))))) + +(ert-deftest test-video-audio-recording-group-devices-by-hardware-boundary-only-inputs-returns-empty () + "Test that only input devices (no monitors) returns empty list. +Devices must have BOTH mic and monitor to be included." + (let ((output (test-load-fixture "pactl-output-inputs-only.txt"))) + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (_cmd) output))) + (let ((result (cj/recording-group-devices-by-hardware))) + (should (listp result)) + (should (null result)))))) + +(ert-deftest test-video-audio-recording-group-devices-by-hardware-boundary-only-monitors-returns-empty () + "Test that only monitor devices (no inputs) returns empty list." + (let ((output (test-load-fixture "pactl-output-monitors-only.txt"))) + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (_cmd) output))) + (let ((result (cj/recording-group-devices-by-hardware))) + (should (listp result)) + (should (null result)))))) + +(ert-deftest test-video-audio-recording-group-devices-by-hardware-boundary-single-complete-device () + "Test that single device with both mic and monitor is returned." + (let ((output "50\talsa_input.pci-0000_00_1f.3.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n49\talsa_output.pci-0000_00_1f.3.analog-stereo.monitor\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n")) + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (_cmd) output))) + (let ((result (cj/recording-group-devices-by-hardware))) + (should (= 1 (length result))) + (should (equal "Built-in Laptop Audio" (caar result))))))) + +(ert-deftest test-video-audio-recording-group-devices-by-hardware-boundary-mixed-complete-incomplete () + "Test that only devices with BOTH mic and monitor are included. +Incomplete devices (only mic or only monitor) are filtered out." + (let ((output (concat + ;; Complete device (built-in) + "50\talsa_input.pci-0000_00_1f.3.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n" + "49\talsa_output.pci-0000_00_1f.3.analog-stereo.monitor\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n" + ;; Incomplete: USB mic with no monitor + "100\talsa_input.usb-device.mono-fallback\tPipeWire\ts16le 1ch 16000Hz\tSUSPENDED\n" + ;; Incomplete: Bluetooth monitor with no mic + "81\tbluez_output.AA_BB_CC_DD_EE_FF.1.monitor\tPipeWire\ts24le 2ch 48000Hz\tRUNNING\n"))) + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (_cmd) output))) + (let ((result (cj/recording-group-devices-by-hardware))) + ;; Only the complete built-in device should be returned + (should (= 1 (length result))) + (should (equal "Built-in Laptop Audio" (caar result))))))) + +;;; Error Cases + +(ert-deftest test-video-audio-recording-group-devices-by-hardware-error-malformed-output-returns-empty () + "Test that malformed pactl output returns empty list." + (let ((output (test-load-fixture "pactl-output-malformed.txt"))) + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (_cmd) output))) + (let ((result (cj/recording-group-devices-by-hardware))) + (should (listp result)) + (should (null result)))))) + +(ert-deftest test-video-audio-recording-group-devices-by-hardware-error-unknown-device-type () + "Test that unknown device types get generic 'USB Audio Device' name." + (let ((output (concat + "100\talsa_input.usb-unknown_device-00.analog-stereo\tPipeWire\ts16le 2ch 16000Hz\tSUSPENDED\n" + "99\talsa_output.usb-unknown_device-00.analog-stereo.monitor\tPipeWire\ts16le 2ch 48000Hz\tSUSPENDED\n"))) + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (_cmd) output))) + (let ((result (cj/recording-group-devices-by-hardware))) + (should (= 1 (length result))) + ;; Should get generic USB name (not matching Jabra pattern) + (should (equal "USB Audio Device" (caar result))))))) + +(ert-deftest test-video-audio-recording-group-devices-by-hardware-error-bluetooth-mac-case-variations () + "Test that Bluetooth MAC addresses work with different formatting. +Tests the normalization logic handles various MAC address formats." + (let ((output (concat + ;; Input with colons (typical) + "79\tbluez_input.AA:BB:CC:DD:EE:FF\tPipeWire\tfloat32le 1ch 48000Hz\tSUSPENDED\n" + ;; Output with underscores (typical) + "81\tbluez_output.AA_BB_CC_DD_EE_FF.1.monitor\tPipeWire\ts24le 2ch 48000Hz\tRUNNING\n"))) + (cl-letf (((symbol-function 'shell-command-to-string) + (lambda (_cmd) output))) + (let ((result (cj/recording-group-devices-by-hardware))) + (should (= 1 (length result))) + (should (equal "Bluetooth Headset" (caar result))) + ;; Verify both devices paired despite different MAC formats + (let ((device (car result))) + (should (string-match-p "AA:BB:CC" (cadr device))) + (should (string-match-p "AA_BB_CC" (cddr device)))))))) + +(provide 'test-video-audio-recording-group-devices-by-hardware) +;;; test-video-audio-recording-group-devices-by-hardware.el ends here |
