fix: use shutil.which so play() and stream() work on Windows - #163
fix: use shutil.which so play() and stream() work on Windows#163Dhevenddra wants to merge 2 commits into
Conversation
_is_installed shelled out to `which`, which does not exist on Windows. The resulting FileNotFoundError is not a CalledProcessError, so it escaped the handler and propagated. On Windows both play() and stream() therefore died with "[WinError 2] The system cannot find the file specified" instead of running, or of raising the DependencyError whose message documents how to install the missing tool on Windows. shutil.which is the standard-library equivalent, works on every supported platform, and avoids spawning a process just to test for one. The same helper was duplicated in both modules, so both are updated. The existing tests mocked subprocess.run, so they described `which` succeeding or exiting non-zero, neither of which is what Windows does. They now patch shutil.which, and cover the case where no `which` binary exists at all.
📝 WalkthroughWalkthroughThe PR replaces subprocess-based ChangesCommand Availability Check Migration
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/unit/test_utils.py`:
- Around line 52-53: Replace the two assertions checking mock_run.call_count and
the "ffplay" string presence with a single assert_called_once_with invocation on
mock_run. This will verify that the mock was called exactly once with the
expected ffplay command and subprocess options, eliminating both the risk of
multiple calls and false positives from "ffplay" appearing elsewhere in the
arguments.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: edeebe02-8b6b-437c-acd0-80a668c0c24e
📒 Files selected for processing (3)
src/fishaudio/utils/play.pysrc/fishaudio/utils/stream.pytests/unit/test_utils.py
| assert mock_run.call_count >= 1 | ||
| assert "ffplay" in str(mock_run.call_args_list) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Assert the exact ffplay invocation.
Lines 52-53 allow extra calls and can pass when another argument contains "ffplay". Use assert_called_once_with to verify the command and subprocess options.
Proposed assertion
- assert mock_run.call_count >= 1
- assert "ffplay" in str(mock_run.call_args_list)
+ mock_run.assert_called_once_with(
+ ["ffplay", "-autoexit", "-", "-nodisp"],
+ input=b"fake audio",
+ capture_output=True,
+ check=True,
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| assert mock_run.call_count >= 1 | |
| assert "ffplay" in str(mock_run.call_args_list) | |
| mock_run.assert_called_once_with( | |
| ["ffplay", "-autoexit", "-", "-nodisp"], | |
| input=b"fake audio", | |
| capture_output=True, | |
| check=True, | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/unit/test_utils.py` around lines 52 - 53, Replace the two assertions
checking mock_run.call_count and the "ffplay" string presence with a single
assert_called_once_with invocation on mock_run. This will verify that the mock
was called exactly once with the expected ffplay command and subprocess options,
eliminating both the risk of multiple calls and false positives from "ffplay"
appearing elsewhere in the arguments.
Checking call_count and a substring of the repr let an unrelated call through and would match 'ffplay' appearing anywhere in the arguments. Pin the call instead.
|
Good call, that assertion was loose in both directions. Replaced it with |
Problem
_is_installed()inutils/play.pyandutils/stream.pychecks for a binary by shelling out towhich:Windows has no
which. Trying to run it raisesFileNotFoundError, which is not aCalledProcessError, so it escapes the handler and propagates to the caller.The result is that
play()andstream()both fail on Windows regardless of whether ffplay or mpv is installed:Reproduced on Windows 11 with Python 3.9.25, against
main. Both helpers raise, and bothplay(b"audio")andstream(iter([b"audio"]))surface theWinErrorrather than theDependencyErrorthey are supposed to raise.That error message is the part that stings: it is the one telling Windows users where to get the tool.
The same helper is duplicated verbatim in both modules, so both are affected.
Fix
Use
shutil.which(), the standard-library equivalent. It works on every supported platform and does not spawn a process just to test for one.This also removes a latent failure on Linux and macOS: minimal container images often ship without
which, and those would have hit the same unhandledFileNotFoundError.Tests
The four existing tests mocked
subprocess.runto simulatewhichsucceeding or exiting non-zero. Neither is what Windows does, which is why CI stayed green: the workflow only runsubuntu-latest, wherewhichexists. They now patchshutil.whichinstead.Added
TestIsInstalledcovering both modules:subprocess.runis patched to fail the test if called)whichbinary available and the tool absent,play()andstream()raiseDependencyErroras documentedVerified the tests actually catch this: with the two
_is_installedbodies reverted and the tests left alone, 9 tests fail, including all 6 new ones, with the originalFileNotFoundError.poe lint,poe format,poe typecheckall pass.pytest tests/unit/is 163 passed, up from 157.Summary by CodeRabbit
Bug Fixes
Tests