Skip to content

Update test_winsound from v3.14.3#7184

Merged
youknowone merged 2 commits intoRustPython:mainfrom
youknowone:winsound
Feb 17, 2026
Merged

Update test_winsound from v3.14.3#7184
youknowone merged 2 commits intoRustPython:mainfrom
youknowone:winsound

Conversation

@youknowone
Copy link
Member

@youknowone youknowone commented Feb 17, 2026

Summary by CodeRabbit

  • New Features
    • Added new winsound module for Windows, providing access to Windows sound utilities including PlaySound, Beep, and MessageBeep functions with associated configuration options for sound playback control and system alerts.

CPython Developers and others added 2 commits February 17, 2026 21:33
Add PlaySound, Beep, MessageBeep functions and all SND_*/MB_*
constants. Uses Windows APIs via winmm.dll, kernel32, user32.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 17, 2026

📝 Walkthrough

Walkthrough

These changes introduce a new winsound module providing Windows-specific sound utilities (PlaySound, Beep, MessageBeep) by adding Windows-sys dependencies, registering the module with the VM's stdlib, and implementing FFI bindings to kernel32 and winmm APIs.

Changes

Cohort / File(s) Summary
Windows Dependency
crates/vm/Cargo.toml
Added Win32_Media_Audio feature to Windows-sys dependencies for audio API access.
Module Integration
crates/vm/src/stdlib/mod.rs
Declared and registered the new winsound module in the stdlib initialization pipeline.
Winsound Implementation
crates/vm/src/stdlib/winsound.rs
New module exposing PlaySound, Beep, and MessageBeep functions with FFI bindings, argument parsing, comprehensive error handling, type conversions for file paths/bytes, and Windows API constants.

Sequence Diagram(s)

sequenceDiagram
    actor Python as Python Code
    participant VM as VM Runtime
    participant WS as Winsound Module
    participant FFI as Win32 APIs

    Python->>VM: call PlaySound(sound, flags)
    VM->>WS: PlaySound(args, vm)
    WS->>WS: Parse arguments & validate input
    alt sound is None
        WS->>FFI: PlaySoundW(nullptr, flags)
    else sound is bytes/memory
        WS->>WS: Create buffer from bytes
        WS->>FFI: PlaySoundW(buffer_ptr, flags | SND_MEMORY)
    else sound is str/Path
        WS->>WS: Normalize path to wide string
        WS->>FFI: PlaySoundW(path_ptr, flags)
    end
    FFI-->>WS: Result (success or error)
    WS->>WS: Convert Windows error to Python exception
    WS-->>VM: PyResult
    VM-->>Python: return or raise
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 The silent VM now dances with sound! ♪
PlaySound, Beep, MessageBeep—magic unbound,
Binding Win32 with FFI so tight,
Windows speaks sweetly through code every night!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'Update test_winsound from v3.14.3' is misleading and does not accurately reflect the changeset, which implements a new winsound module with PlaySound, Beep, and MessageBeep functions, not a test update. Update the title to accurately describe the main change, such as 'Add winsound module with PlaySound, Beep, and MessageBeep support' or 'Implement Windows audio functionality via winsound module'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

📦 Library Dependencies

The following Lib/ modules were modified. Here are their dependencies:

[x] test: cpython/Lib/test/test_winsound.py

dependencies:

dependent tests: (no tests depend on winsound)

Legend:

  • [+] path exists in CPython
  • [x] up-to-date, [ ] outdated

@youknowone youknowone marked this pull request as ready for review February 17, 2026 14:00
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
crates/vm/src/stdlib/winsound.rs (3)

131-138: vm.is_none(&fspath) check on the method object appears to be dead code.

get_method_or_type_error returns the bound method (or errors). The returned fspath is the method itself, not its return value — it will never be None. The actual call happens later on line 138. This guard can be removed without behavioral change.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/stdlib/winsound.rs` around lines 131 - 138, Remove the
redundant vm.is_none(&fspath) guard: get_method_or_type_error returns a bound
method (the fspath variable) which will never be None, so the
vm.is_none(&fspath) check and its Err branch should be deleted; leave the
subsequent call to fspath.call((), vm)? intact so the actual return value is
produced and validated. Target the block around get_method_or_type_error /
vm.is_none / fspath.call in winsound.rs and remove only the is_none check and
its error return.

83-168: Inconsistent error reporting: PlaySound and Beep discard the Windows error code.

MessageBeep (line 202) correctly uses std::io::Error::last_os_error().into_pyexception(vm) to propagate the Windows error, but PlaySound (lines 90, 107, 166) and Beep (line 187) raise a generic RuntimeError with a hardcoded string. CPython raises RuntimeError with the actual Windows error message for all three. Consider using last_os_error consistently so callers get actionable diagnostics.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/stdlib/winsound.rs` around lines 83 - 168, PlaySound and Beep
currently return a generic vm.new_runtime_error on PlaySoundW/Beep failures;
replace those error returns with the OS error conversion used by MessageBeep
(i.e. use std::io::Error::last_os_error().into_pyexception(vm)) so the real
Windows error is propagated. Specifically, update the three failure branches in
PlaySound that check PlaySoundW (the null sound path, the SND_MEMORY buffer
path, and the final path using wide.as_ptr()) to return
std::io::Error::last_os_error().into_pyexception(vm) instead of
vm.new_runtime_error(...), and do the same for the Beep failure branch that
currently returns a generic runtime error. Ensure you call into_pyexception(vm)
the same way MessageBeep does so the PyException contains the platform error
message.

6-16: Use windows-sys imports instead of manual FFI bindings.

Win32_Media_Audio, Win32_System_Diagnostics_Debug, and Win32_UI_WindowsAndMessaging are already enabled in Cargo.toml, but the code declares its own extern blocks instead of importing from windows-sys. The rest of the codebase (errno.rs, winreg.rs, time.rs, nt.rs, etc.) consistently uses use windows_sys::Win32::... imports. Replace the manual FFI with:

  • use windows_sys::Win32::Media::Audio::PlaySoundW;
  • use windows_sys::Win32::System::Diagnostics::Debug::Beep;
  • use windows_sys::Win32::UI::WindowsAndMessaging::MessageBeep;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/stdlib/winsound.rs` around lines 6 - 16, Replace the manual FFI
extern blocks in the win32 module with imports from windows-sys: remove the
unsafe extern "system" declarations and instead import PlaySoundW, Beep, and
MessageBeep via use windows_sys::Win32::Media::Audio::PlaySoundW;, use
windows_sys::Win32::System::Diagnostics::Debug::Beep;, and use
windows_sys::Win32::UI::WindowsAndMessaging::MessageBeep; so the code uses the
existing Cargo-enabled Win32_* feature crates rather than declaring its own
bindings (references: PlaySoundW, Beep, MessageBeep, and the win32 module).
crates/vm/Cargo.toml (1)

122-122: Win32_Media_Audio feature is currently unused.

The winsound module uses manual #[link(name = "winmm")] extern blocks rather than importing from windows-sys. This feature will compile but contributes dead code to the dependency. See the related comment on winsound.rs — either switch to windows-sys re-exports or remove this feature.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/Cargo.toml` at line 122, The Cargo feature "Win32_Media_Audio" is
unused because the winsound module uses manual extern #[link(name = "winmm")]
blocks instead of windows-sys re-exports; either remove the unused
"Win32_Media_Audio" feature from Cargo.toml or refactor the winsound module
(winsound.rs / winsound) to import the relevant APIs from windows-sys (enable
the feature and replace manual extern blocks with windows-sys bindings), and
then update feature lists accordingly so Win32_Media_Audio is either referenced
by the winsound code or deleted to avoid dead dependency code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/vm/src/stdlib/winsound.rs`:
- Around line 179-189: The Beep implementation casts BeepArgs.duration (i32) to
u32, allowing negative durations to wrap to huge values; update fn Beep (and
validate BeepArgs.duration) to check duration is >= 0 before calling
super::win32::Beep and return vm.new_value_error("duration must be
non-negative") (or similar) when invalid; alternatively, if you want CPython
compatibility, add a comment referencing that choice and leave behavior
unchanged. Ensure the check occurs alongside the existing frequency validation
and refer to the Beep, BeepArgs, VirtualMachine, and win32::Beep symbols when
making the change.

---

Nitpick comments:
In `@crates/vm/Cargo.toml`:
- Line 122: The Cargo feature "Win32_Media_Audio" is unused because the winsound
module uses manual extern #[link(name = "winmm")] blocks instead of windows-sys
re-exports; either remove the unused "Win32_Media_Audio" feature from Cargo.toml
or refactor the winsound module (winsound.rs / winsound) to import the relevant
APIs from windows-sys (enable the feature and replace manual extern blocks with
windows-sys bindings), and then update feature lists accordingly so
Win32_Media_Audio is either referenced by the winsound code or deleted to avoid
dead dependency code.

In `@crates/vm/src/stdlib/winsound.rs`:
- Around line 131-138: Remove the redundant vm.is_none(&fspath) guard:
get_method_or_type_error returns a bound method (the fspath variable) which will
never be None, so the vm.is_none(&fspath) check and its Err branch should be
deleted; leave the subsequent call to fspath.call((), vm)? intact so the actual
return value is produced and validated. Target the block around
get_method_or_type_error / vm.is_none / fspath.call in winsound.rs and remove
only the is_none check and its error return.
- Around line 83-168: PlaySound and Beep currently return a generic
vm.new_runtime_error on PlaySoundW/Beep failures; replace those error returns
with the OS error conversion used by MessageBeep (i.e. use
std::io::Error::last_os_error().into_pyexception(vm)) so the real Windows error
is propagated. Specifically, update the three failure branches in PlaySound that
check PlaySoundW (the null sound path, the SND_MEMORY buffer path, and the final
path using wide.as_ptr()) to return
std::io::Error::last_os_error().into_pyexception(vm) instead of
vm.new_runtime_error(...), and do the same for the Beep failure branch that
currently returns a generic runtime error. Ensure you call into_pyexception(vm)
the same way MessageBeep does so the PyException contains the platform error
message.
- Around line 6-16: Replace the manual FFI extern blocks in the win32 module
with imports from windows-sys: remove the unsafe extern "system" declarations
and instead import PlaySoundW, Beep, and MessageBeep via use
windows_sys::Win32::Media::Audio::PlaySoundW;, use
windows_sys::Win32::System::Diagnostics::Debug::Beep;, and use
windows_sys::Win32::UI::WindowsAndMessaging::MessageBeep; so the code uses the
existing Cargo-enabled Win32_* feature crates rather than declaring its own
bindings (references: PlaySoundW, Beep, MessageBeep, and the win32 module).

Comment on lines +179 to +189
#[pyfunction]
fn Beep(args: BeepArgs, vm: &VirtualMachine) -> PyResult<()> {
if !(37..=32767).contains(&args.frequency) {
return Err(vm.new_value_error("frequency must be in 37 thru 32767".to_owned()));
}

let ok = unsafe { super::win32::Beep(args.frequency as u32, args.duration as u32) };
if ok == 0 {
return Err(vm.new_runtime_error("Failed to beep".to_owned()));
}
Ok(())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's check the actual code at the specified file and location
cat -n crates/vm/src/stdlib/winsound.rs | sed -n '170,195p'

Repository: RustPython/RustPython

Length of output: 963


🏁 Script executed:

# Also, let's find the BeepArgs struct definition
rg "struct BeepArgs" -A 5 crates/vm/src/stdlib/winsound.rs

Repository: RustPython/RustPython

Length of output: 183


🌐 Web query:

CPython winsound Beep negative duration parameter behavior

💡 Result:

In CPython, winsound.Beep(frequency, duration) does not validate that duration is non‑negative—it parses it as a C int and passes it straight through to the underlying Windows call (Beep(freq, dur) on NT-class Windows, or Sleep(dur) in the legacy path). The CPython code only range-checks frequency, not duration. [1]

On Windows, the API signature is BOOL Beep(DWORD dwFreq, DWORD dwDuration), i.e. the duration is an unsigned 32-bit DWORD. [2] That means a negative Python duration (e.g. -1) will be converted to a large unsigned value (two’s-complement wraparound), so it can turn into an extremely long beep (e.g. -14294967295 ms ≈ 49.7 days) unless the Windows call fails (in which case CPython raises RuntimeError). [1] [2]

Sources:
[1] CPython winsound.c implementation (shows no duration check; calls Beep(freq, dur) / Sleep(dur)) https://sources.debian.org/src/python2.5/2.5-5/PC/winsound.c/
[2] Microsoft Beep API signature (dwDuration is DWORD) https://learn.microsoft.com/en-us/windows/win32/api/utilapiset/nf-utilapiset-beep


Add validation for non-negative duration to prevent silent wraparound of negative values to large unsigned integers.

The duration parameter is declared as i32 (line 176) but cast to u32 (line 185) without validation. A negative value like -1 silently wraps to 4294967295 milliseconds (~49.7 days). Note that CPython's winsound.Beep exhibits the same behavior—it does not validate duration and passes negative values directly to the Windows API. If strict compatibility with CPython is desired, this behavior is expected; otherwise, add a validation check:

Proposed fix
     fn Beep(args: BeepArgs, vm: &VirtualMachine) -> PyResult<()> {
         if !(37..=32767).contains(&args.frequency) {
             return Err(vm.new_value_error("frequency must be in 37 thru 32767".to_owned()));
         }
+        if args.duration < 0 {
+            return Err(vm.new_value_error("duration must be >= 0".to_owned()));
+        }
 
         let ok = unsafe { super::win32::Beep(args.frequency as u32, args.duration as u32) };
📝 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.

Suggested change
#[pyfunction]
fn Beep(args: BeepArgs, vm: &VirtualMachine) -> PyResult<()> {
if !(37..=32767).contains(&args.frequency) {
return Err(vm.new_value_error("frequency must be in 37 thru 32767".to_owned()));
}
let ok = unsafe { super::win32::Beep(args.frequency as u32, args.duration as u32) };
if ok == 0 {
return Err(vm.new_runtime_error("Failed to beep".to_owned()));
}
Ok(())
#[pyfunction]
fn Beep(args: BeepArgs, vm: &VirtualMachine) -> PyResult<()> {
if !(37..=32767).contains(&args.frequency) {
return Err(vm.new_value_error("frequency must be in 37 thru 32767".to_owned()));
}
if args.duration < 0 {
return Err(vm.new_value_error("duration must be >= 0".to_owned()));
}
let ok = unsafe { super::win32::Beep(args.frequency as u32, args.duration as u32) };
if ok == 0 {
return Err(vm.new_runtime_error("Failed to beep".to_owned()));
}
Ok(())
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/stdlib/winsound.rs` around lines 179 - 189, The Beep
implementation casts BeepArgs.duration (i32) to u32, allowing negative durations
to wrap to huge values; update fn Beep (and validate BeepArgs.duration) to check
duration is >= 0 before calling super::win32::Beep and return
vm.new_value_error("duration must be non-negative") (or similar) when invalid;
alternatively, if you want CPython compatibility, add a comment referencing that
choice and leave behavior unchanged. Ensure the check occurs alongside the
existing frequency validation and refer to the Beep, BeepArgs, VirtualMachine,
and win32::Beep symbols when making the change.

@youknowone youknowone merged commit ccccaaf into RustPython:main Feb 17, 2026
13 of 14 checks passed
@youknowone youknowone deleted the winsound branch February 17, 2026 15:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant