From 9760249a750c272733369ff886aac22ffbe25d31 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 23 Dec 2025 15:24:07 +0900 Subject: [PATCH 01/10] fix multiarch --- Lib/test/test_sysconfig.py | 1 - crates/vm/src/stdlib/sys.rs | 20 +++++++++++++------- crates/vm/src/stdlib/sysconfigdata.rs | 11 +++++++---- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index 965780668c..82c11bdf7e 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -598,7 +598,6 @@ def test_android_ext_suffix(self): self.assertTrue(suffix.endswith(f"-{expected_triplet}.so"), f"{machine=}, {suffix=}") - @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(sys.platform == 'darwin', 'OS X-specific test') def test_osx_ext_suffix(self): suffix = sysconfig.get_config_var('EXT_SUFFIX') diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index cfe6f9f5e6..0e46ec18a0 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -1,6 +1,8 @@ use crate::{Py, PyResult, VirtualMachine, builtins::PyModule, convert::ToPyObject}; -pub(crate) use sys::{__module_def, DOC, MAXSIZE, MULTIARCH, UnraisableHookArgsData}; +pub(crate) use sys::{ + __module_def, DOC, MAXSIZE, RUST_MULTIARCH, UnraisableHookArgsData, multiarch, +}; #[pymodule] mod sys { @@ -37,10 +39,14 @@ mod sys { System::LibraryLoader::{GetModuleFileNameW, GetModuleHandleW}, }; - // not the same as CPython (e.g. rust's x86_x64-unknown-linux-gnu is just x86_64-linux-gnu) - // but hopefully that's just an implementation detail? TODO: copy CPython's multiarch exactly, - // https://github.com/python/cpython/blob/3.8/configure.ac#L725 - pub(crate) const MULTIARCH: &str = env!("RUSTPYTHON_TARGET_TRIPLE"); + // Rust target triple (e.g., "x86_64-unknown-linux-gnu") + pub(crate) const RUST_MULTIARCH: &str = env!("RUSTPYTHON_TARGET_TRIPLE"); + + /// Convert Rust target triple to CPython-style multiarch + /// e.g., "x86_64-unknown-linux-gnu" -> "x86_64-linux-gnu" + pub(crate) fn multiarch() -> String { + RUST_MULTIARCH.replace("-unknown", "") + } #[pyattr(name = "_rustpython_debugbuild")] const RUSTPYTHON_DEBUGBUILD: bool = cfg!(debug_assertions); @@ -189,7 +195,7 @@ mod sys { py_namespace!(vm, { "name" => ctx.new_str(NAME), "cache_tag" => ctx.new_str(cache_tag), - "_multiarch" => ctx.new_str(MULTIARCH.to_owned()), + "_multiarch" => ctx.new_str(multiarch()), "version" => version_info(vm), "hexversion" => ctx.new_int(version::VERSION_HEX), }) @@ -1249,6 +1255,6 @@ pub(crate) fn sysconfigdata_name() -> String { "_sysconfigdata_{}_{}_{}", sys::ABIFLAGS, sys::PLATFORM, - sys::MULTIARCH + sys::multiarch() ) } diff --git a/crates/vm/src/stdlib/sysconfigdata.rs b/crates/vm/src/stdlib/sysconfigdata.rs index ee40b693aa..5e954f7fe7 100644 --- a/crates/vm/src/stdlib/sysconfigdata.rs +++ b/crates/vm/src/stdlib/sysconfigdata.rs @@ -4,20 +4,23 @@ pub(crate) use _sysconfigdata::make_module; #[pymodule] pub(crate) mod _sysconfigdata { - use crate::{VirtualMachine, builtins::PyDictRef, convert::ToPyObject, stdlib::sys::MULTIARCH}; + use crate::stdlib::sys::{RUST_MULTIARCH, multiarch}; + use crate::{VirtualMachine, builtins::PyDictRef, convert::ToPyObject}; #[pyattr] fn build_time_vars(vm: &VirtualMachine) -> PyDictRef { let vars = vm.ctx.new_dict(); + let multiarch = multiarch(); macro_rules! sysvars { ($($key:literal => $value:expr),*$(,)?) => {{ $(vars.set_item($key, $value.to_pyobject(vm), vm).unwrap();)* }}; } sysvars! { - // fake shared module extension - "EXT_SUFFIX" => format!(".rustpython-{MULTIARCH}"), - "MULTIARCH" => MULTIARCH, + // Extension module suffix in CPython-compatible format + "EXT_SUFFIX" => format!(".rustpython313-{multiarch}.so"), + "MULTIARCH" => multiarch.clone(), + "RUST_MULTIARCH" => RUST_MULTIARCH, // enough for tests to stop expecting urandom() to fail after restricting file resources "HAVE_GETRANDOM" => 1, // Compiler configuration for native extension builds From b44229f7ca4f7718f405c4a0f397c2a70c9178ec Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Tue, 23 Dec 2025 21:57:05 +0900 Subject: [PATCH 02/10] debug whats_left --- whats_left.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/whats_left.py b/whats_left.py index 91e46bef7e..7d02a4cea4 100755 --- a/whats_left.py +++ b/whats_left.py @@ -361,7 +361,7 @@ def method_incompatibility_reason(typ, method_name, real_method_value): if platform.python_implementation() == "CPython": if not_implementeds: - sys.exit("ERROR: CPython should have all the methods") + sys.exit(f"ERROR: CPython should have all the methods but missing: {not_implementeds}") mod_names = [ name.decode() @@ -455,6 +455,7 @@ def remove_one_indent(s): ) # The last line should be json output, the rest of the lines can contain noise # because importing certain modules can print stuff to stdout/stderr +print(result.stderr, file=sys.stderr) result = json.loads(result.stdout.splitlines()[-1]) if args.json: From fc0a34a5a53730a0f1b59c432e568fea9f84b8f3 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 20 Dec 2025 14:31:33 +0900 Subject: [PATCH 03/10] Remove unused ctypes/field.rs --- crates/vm/src/stdlib/ctypes/field.rs | 306 --------------------------- 1 file changed, 306 deletions(-) delete mode 100644 crates/vm/src/stdlib/ctypes/field.rs diff --git a/crates/vm/src/stdlib/ctypes/field.rs b/crates/vm/src/stdlib/ctypes/field.rs deleted file mode 100644 index ea57d68065..0000000000 --- a/crates/vm/src/stdlib/ctypes/field.rs +++ /dev/null @@ -1,306 +0,0 @@ -use crate::builtins::PyType; -use crate::function::PySetterValue; -use crate::types::{GetDescriptor, Representable}; -use crate::{AsObject, Py, PyObject, PyObjectRef, PyResult, VirtualMachine}; -use num_traits::ToPrimitive; - -use super::structure::PyCStructure; -use super::union::PyCUnion; - -#[pyclass(name = "PyCFieldType", base = PyType, module = "_ctypes")] -#[derive(Debug)] -pub struct PyCFieldType { - pub _base: PyType, - #[allow(dead_code)] - pub(super) inner: PyCField, -} - -#[pyclass] -impl PyCFieldType {} - -#[pyclass(name = "CField", module = "_ctypes")] -#[derive(Debug, PyPayload)] -pub struct PyCField { - pub(super) byte_offset: usize, - pub(super) byte_size: usize, - #[allow(unused)] - pub(super) index: usize, - /// The ctypes type for this field (can be any ctypes type including arrays) - pub(super) proto: PyObjectRef, - pub(super) anonymous: bool, - pub(super) bitfield_size: bool, - pub(super) bit_offset: u8, - pub(super) name: String, -} - -impl PyCField { - pub fn new( - name: String, - proto: PyObjectRef, - byte_offset: usize, - byte_size: usize, - index: usize, - ) -> Self { - Self { - name, - proto, - byte_offset, - byte_size, - index, - anonymous: false, - bitfield_size: false, - bit_offset: 0, - } - } -} - -impl Representable for PyCField { - fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { - // Get type name from the proto object - let tp_name = if let Some(name_attr) = vm - .ctx - .interned_str("__name__") - .and_then(|s| zelf.proto.get_attr(s, vm).ok()) - { - name_attr.str(vm)?.to_string() - } else { - zelf.proto.class().name().to_string() - }; - - if zelf.bitfield_size { - Ok(format!( - "<{} type={}, ofs={byte_offset}, bit_size={bitfield_size}, bit_offset={bit_offset}", - zelf.name, - tp_name, - byte_offset = zelf.byte_offset, - bitfield_size = zelf.bitfield_size, - bit_offset = zelf.bit_offset - )) - } else { - Ok(format!( - "<{} type={tp_name}, ofs={}, size={}", - zelf.name, zelf.byte_offset, zelf.byte_size - )) - } - } -} - -impl GetDescriptor for PyCField { - fn descr_get( - zelf: PyObjectRef, - obj: Option, - _cls: Option, - vm: &VirtualMachine, - ) -> PyResult { - let zelf = zelf - .downcast::() - .map_err(|_| vm.new_type_error("expected CField".to_owned()))?; - - // If obj is None, return the descriptor itself (class attribute access) - let obj = match obj { - Some(obj) if !vm.is_none(&obj) => obj, - _ => return Ok(zelf.into()), - }; - - // Instance attribute access - read value from the structure/union's buffer - if let Some(structure) = obj.downcast_ref::() { - let cdata = structure.cdata.read(); - let offset = zelf.byte_offset; - let size = zelf.byte_size; - - if offset + size <= cdata.buffer.len() { - let bytes = &cdata.buffer[offset..offset + size]; - return PyCField::bytes_to_value(bytes, size, vm); - } - } else if let Some(union) = obj.downcast_ref::() { - let cdata = union.cdata.read(); - let offset = zelf.byte_offset; - let size = zelf.byte_size; - - if offset + size <= cdata.buffer.len() { - let bytes = &cdata.buffer[offset..offset + size]; - return PyCField::bytes_to_value(bytes, size, vm); - } - } - - // Fallback: return 0 for uninitialized or unsupported types - Ok(vm.ctx.new_int(0).into()) - } -} - -impl PyCField { - /// Convert bytes to a Python value based on size - fn bytes_to_value(bytes: &[u8], size: usize, vm: &VirtualMachine) -> PyResult { - match size { - 1 => Ok(vm.ctx.new_int(bytes[0] as i8).into()), - 2 => { - let val = i16::from_ne_bytes([bytes[0], bytes[1]]); - Ok(vm.ctx.new_int(val).into()) - } - 4 => { - let val = i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); - Ok(vm.ctx.new_int(val).into()) - } - 8 => { - let val = i64::from_ne_bytes([ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], - ]); - Ok(vm.ctx.new_int(val).into()) - } - _ => Ok(vm.ctx.new_int(0).into()), - } - } - - /// Convert a Python value to bytes - fn value_to_bytes(value: &PyObject, size: usize, vm: &VirtualMachine) -> PyResult> { - if let Ok(int_val) = value.try_int(vm) { - let i = int_val.as_bigint(); - match size { - 1 => { - let val = i.to_i8().unwrap_or(0); - Ok(val.to_ne_bytes().to_vec()) - } - 2 => { - let val = i.to_i16().unwrap_or(0); - Ok(val.to_ne_bytes().to_vec()) - } - 4 => { - let val = i.to_i32().unwrap_or(0); - Ok(val.to_ne_bytes().to_vec()) - } - 8 => { - let val = i.to_i64().unwrap_or(0); - Ok(val.to_ne_bytes().to_vec()) - } - _ => Ok(vec![0u8; size]), - } - } else { - Ok(vec![0u8; size]) - } - } -} - -#[pyclass( - flags(DISALLOW_INSTANTIATION, IMMUTABLETYPE), - with(Representable, GetDescriptor) -)] -impl PyCField { - #[pyslot] - fn descr_set( - zelf: &crate::PyObject, - obj: PyObjectRef, - value: PySetterValue, - vm: &VirtualMachine, - ) -> PyResult<()> { - let zelf = zelf - .downcast_ref::() - .ok_or_else(|| vm.new_type_error("expected CField".to_owned()))?; - - // Get the structure/union instance - use downcast_ref() to access the struct data - if let Some(structure) = obj.downcast_ref::() { - match value { - PySetterValue::Assign(value) => { - let offset = zelf.byte_offset; - let size = zelf.byte_size; - let bytes = PyCField::value_to_bytes(&value, size, vm)?; - - let mut cdata = structure.cdata.write(); - if offset + size <= cdata.buffer.len() { - cdata.buffer[offset..offset + size].copy_from_slice(&bytes); - } - Ok(()) - } - PySetterValue::Delete => { - Err(vm.new_type_error("cannot delete structure field".to_owned())) - } - } - } else if let Some(union) = obj.downcast_ref::() { - match value { - PySetterValue::Assign(value) => { - let offset = zelf.byte_offset; - let size = zelf.byte_size; - let bytes = PyCField::value_to_bytes(&value, size, vm)?; - - let mut cdata = union.cdata.write(); - if offset + size <= cdata.buffer.len() { - cdata.buffer[offset..offset + size].copy_from_slice(&bytes); - } - Ok(()) - } - PySetterValue::Delete => { - Err(vm.new_type_error("cannot delete union field".to_owned())) - } - } - } else { - Err(vm.new_type_error(format!( - "descriptor works only on Structure or Union instances, got {}", - obj.class().name() - ))) - } - } - - #[pymethod] - fn __set__( - zelf: PyObjectRef, - obj: PyObjectRef, - value: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult<()> { - Self::descr_set(&zelf, obj, PySetterValue::Assign(value), vm) - } - - #[pymethod] - fn __delete__(zelf: PyObjectRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - Self::descr_set(&zelf, obj, PySetterValue::Delete, vm) - } - - #[pygetset] - fn size(&self) -> usize { - self.byte_size - } - - #[pygetset] - fn bit_size(&self) -> bool { - self.bitfield_size - } - - #[pygetset] - fn is_bitfield(&self) -> bool { - self.bitfield_size - } - - #[pygetset] - fn is_anonymous(&self) -> bool { - self.anonymous - } - - #[pygetset] - fn name(&self) -> String { - self.name.clone() - } - - #[pygetset(name = "type")] - fn type_(&self) -> PyObjectRef { - self.proto.clone() - } - - #[pygetset] - fn offset(&self) -> usize { - self.byte_offset - } - - #[pygetset] - fn byte_offset(&self) -> usize { - self.byte_offset - } - - #[pygetset] - fn byte_size(&self) -> usize { - self.byte_size - } - - #[pygetset] - fn bit_offset(&self) -> u8 { - self.bit_offset - } -} From 286a5b5b8f8759808ed5a46f0256965bddcaf45d Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 23 Dec 2025 23:28:58 +0900 Subject: [PATCH 04/10] msc_info in get_version --- Lib/platform.py | 2 +- crates/vm/src/version.rs | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index 58b66078e1..c64c6d2c6f 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -1080,7 +1080,7 @@ def _sys_version(sys_version=None): match.groups() # XXX: RUSTPYTHON support - if "rustc" in sys_version: + if "RustPython" in sys_version: name = "RustPython" else: name = 'CPython' diff --git a/crates/vm/src/version.rs b/crates/vm/src/version.rs index 9d472e8be0..deb3dccd53 100644 --- a/crates/vm/src/version.rs +++ b/crates/vm/src/version.rs @@ -15,12 +15,29 @@ pub const VERSION_HEX: usize = (MAJOR << 24) | (MINOR << 16) | (MICRO << 8) | (RELEASELEVEL_N << 4) | SERIAL; pub fn get_version() -> String { + // Windows: include MSC v. for compatibility with ctypes.util.find_library + // MSC v.1929 = VS 2019, version 14+ makes find_msvcrt() return None + #[cfg(windows)] + let msc_info = { + let arch = if cfg!(target_pointer_width = "64") { + "64 bit (AMD64)" + } else { + "32 bit (Intel)" + }; + // Include both RustPython identifier and MSC v. for compatibility + format!(" MSC v.1929 {arch}",) + }; + + #[cfg(not(windows))] + let msc_info = String::new(); + format!( - "{:.80} ({:.80}) \n[RustPython {} with {:.80}]", // \n is PyPy convention + "{:.80} ({:.80}) \n[RustPython {} with {:.80}{}]", // \n is PyPy convention get_version_number(), get_build_info(), env!("CARGO_PKG_VERSION"), COMPILER, + msc_info, ) } From 4bf0bac52d0270c0efcbc248a3888789624a2645 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 23 Dec 2025 23:29:15 +0900 Subject: [PATCH 05/10] debuggable whats_left --- whats_left.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/whats_left.py b/whats_left.py index 7d02a4cea4..c5b0be6ead 100755 --- a/whats_left.py +++ b/whats_left.py @@ -361,7 +361,9 @@ def method_incompatibility_reason(typ, method_name, real_method_value): if platform.python_implementation() == "CPython": if not_implementeds: - sys.exit(f"ERROR: CPython should have all the methods but missing: {not_implementeds}") + sys.exit( + f"ERROR: CPython should have all the methods but missing: {not_implementeds}" + ) mod_names = [ name.decode() From 79abbf0b29d5183b0885e45ea9991bd8d046d702 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 23 Dec 2025 23:36:38 +0900 Subject: [PATCH 06/10] fix ctypes --- crates/vm/src/stdlib/ctypes.rs | 102 ++- crates/vm/src/stdlib/ctypes/array.rs | 44 +- crates/vm/src/stdlib/ctypes/base.rs | 76 ++- crates/vm/src/stdlib/ctypes/function.rs | 776 ++++++++++++++--------- crates/vm/src/stdlib/ctypes/library.rs | 108 +++- crates/vm/src/stdlib/ctypes/pointer.rs | 147 ++++- crates/vm/src/stdlib/ctypes/simple.rs | 187 ++++-- crates/vm/src/stdlib/ctypes/structure.rs | 6 - 8 files changed, 973 insertions(+), 473 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 3fdb2df610..9b92223043 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -95,6 +95,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { pointer::PyCPointerType::make_class(ctx); structure::PyCStructType::make_class(ctx); union::PyCUnionType::make_class(ctx); + function::PyCFuncPtrType::make_class(ctx); extend_module!(vm, &module, { "_CData" => PyCData::make_class(ctx), "_SimpleCData" => PyCSimple::make_class(ctx), @@ -385,12 +386,8 @@ pub(crate) mod _ctypes { #[pyattr] const RTLD_GLOBAL: i32 = 0; - #[cfg(target_os = "windows")] - #[pyattr] - const SIZEOF_TIME_T: usize = 8; - #[cfg(not(target_os = "windows"))] #[pyattr] - const SIZEOF_TIME_T: usize = 4; + const SIZEOF_TIME_T: usize = std::mem::size_of::(); #[pyattr] const CTYPES_MAX_ARGCOUNT: usize = 1024; @@ -578,30 +575,42 @@ pub(crate) mod _ctypes { #[pyfunction(name = "dlopen")] fn load_library_unix( name: Option, - _load_flags: OptionalArg, + load_flags: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - // TODO: audit functions first - // TODO: load_flags + // Default mode: RTLD_NOW | RTLD_LOCAL, always force RTLD_NOW + let mode = load_flags.unwrap_or(libc::RTLD_NOW | libc::RTLD_LOCAL) | libc::RTLD_NOW; + match name { Some(name) => { let cache = library::libcache(); let mut cache_write = cache.write(); let os_str = name.as_os_str(vm)?; - let (id, _) = cache_write.get_or_insert_lib(&*os_str, vm).map_err(|e| { - // Include filename in error message for better diagnostics - let name_str = os_str.to_string_lossy(); - vm.new_os_error(format!("{}: {}", name_str, e)) - })?; + let (id, _) = cache_write + .get_or_insert_lib_with_mode(&*os_str, mode, vm) + .map_err(|e| { + let name_str = os_str.to_string_lossy(); + vm.new_os_error(format!("{}: {}", name_str, e)) + })?; Ok(id) } None => { - // If None, call libc::dlopen(null, mode) to get the current process handle - let handle = unsafe { libc::dlopen(std::ptr::null(), libc::RTLD_NOW) }; + // dlopen(NULL, mode) to get the current process handle (for pythonapi) + let handle = unsafe { libc::dlopen(std::ptr::null(), mode) }; if handle.is_null() { - return Err(vm.new_os_error("dlopen() error")); + let err = unsafe { libc::dlerror() }; + let msg = if err.is_null() { + "dlopen() error".to_string() + } else { + unsafe { std::ffi::CStr::from_ptr(err).to_string_lossy().into_owned() } + }; + return Err(vm.new_os_error(msg)); } - Ok(handle as usize) + // Add to library cache so symbol lookup works + let cache = library::libcache(); + let mut cache_write = cache.write(); + let id = cache_write.insert_raw_handle(handle); + Ok(id) } } } @@ -614,6 +623,48 @@ pub(crate) mod _ctypes { Ok(()) } + #[cfg(not(windows))] + #[pyfunction] + fn dlclose(handle: usize, _vm: &VirtualMachine) -> PyResult<()> { + // Remove from cache, which triggers SharedLibrary drop. + // libloading::Library calls dlclose automatically on Drop. + let cache = library::libcache(); + let mut cache_write = cache.write(); + cache_write.drop_lib(handle); + Ok(()) + } + + #[cfg(not(windows))] + #[pyfunction] + fn dlsym( + handle: usize, + name: crate::builtins::PyStrRef, + vm: &VirtualMachine, + ) -> PyResult { + let symbol_name = std::ffi::CString::new(name.as_str()) + .map_err(|_| vm.new_value_error("symbol name contains null byte"))?; + + // Clear previous error + unsafe { libc::dlerror() }; + + let ptr = unsafe { libc::dlsym(handle as *mut libc::c_void, symbol_name.as_ptr()) }; + + // Check for error via dlerror first + let err = unsafe { libc::dlerror() }; + if !err.is_null() { + let msg = unsafe { std::ffi::CStr::from_ptr(err).to_string_lossy().into_owned() }; + return Err(vm.new_os_error(msg)); + } + + // Treat NULL symbol address as error + // This handles cases like GNU IFUNCs that resolve to NULL + if ptr.is_null() { + return Err(vm.new_os_error(format!("symbol '{}' not found", name.as_str()))); + } + + Ok(ptr as usize) + } + #[pyfunction(name = "POINTER")] fn create_pointer_type(cls: PyObjectRef, vm: &VirtualMachine) -> PyResult { use crate::builtins::PyStr; @@ -905,25 +956,24 @@ pub(crate) mod _ctypes { #[pyfunction] fn get_errno() -> i32 { - errno::errno().0 + super::function::get_errno_value() } #[pyfunction] - fn set_errno(value: i32) { - errno::set_errno(errno::Errno(value)); + fn set_errno(value: i32) -> i32 { + super::function::set_errno_value(value) } #[cfg(windows)] #[pyfunction] fn get_last_error() -> PyResult { - Ok(unsafe { windows_sys::Win32::Foundation::GetLastError() }) + Ok(super::function::get_last_error_value()) } #[cfg(windows)] #[pyfunction] - fn set_last_error(value: u32) -> PyResult<()> { - unsafe { windows_sys::Win32::Foundation::SetLastError(value) }; - Ok(()) + fn set_last_error(value: u32) -> u32 { + super::function::set_last_error_value(value) } #[pyattr] @@ -1084,9 +1134,9 @@ pub(crate) mod _ctypes { ffi_args.push(Arg::new(val)); } - let cif = Cif::new(arg_types, Type::isize()); + let cif = Cif::new(arg_types, Type::c_int()); let code_ptr = CodePtr::from_ptr(func_addr as *const _); - let result: isize = unsafe { cif.call(code_ptr, &ffi_args) }; + let result: libc::c_int = unsafe { cif.call(code_ptr, &ffi_args) }; Ok(vm.ctx.new_int(result).into()) } diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 60e6516bfe..208b3e3f4d 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -1,9 +1,12 @@ use super::StgInfo; use super::base::{CDATA_BUFFER_METHODS, PyCData}; +use super::type_info; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, atomic_func, - builtins::{PyBytes, PyInt, PyList, PySlice, PyStr, PyType, PyTypeRef}, + builtins::{ + PyBytes, PyInt, PyList, PySlice, PyStr, PyType, PyTypeRef, genericalias::PyGenericAlias, + }, class::StaticType, function::{ArgBytesLike, FuncArgs, PySetterValue}, protocol::{BufferDescriptor, PyBuffer, PyNumberMethods, PySequenceMethods}, @@ -11,6 +14,19 @@ use crate::{ }; use num_traits::{Signed, ToPrimitive}; +/// Get itemsize from a PEP 3118 format string +/// Extracts the type code (last char after endianness prefix) and returns its size +fn get_size_from_format(fmt: &str) -> usize { + // Format is like "q", etc. - strip endianness prefix and get type code + let code = fmt + .trim_start_matches(['<', '>', '@', '=', '!', '&']) + .chars() + .next() + .map(|c| c.to_string()); + code.map(|c| type_info(&c).map(|t| t.size).unwrap_or(1)) + .unwrap_or(1) +} + /// Creates array type for (element_type, length) /// Uses _array_type_cache to ensure identical calls return the same type object pub(super) fn array_type_from_ctype( @@ -444,6 +460,11 @@ impl AsSequence for PyCArray { with(Constructor, AsSequence, AsBuffer) )] impl PyCArray { + #[pyclassmethod] + fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::from_args(cls, args, vm) + } + fn int_to_bytes(i: &malachite_bigint::BigInt, size: usize) -> Vec { // Try unsigned first (handles values like 0xFFFFFFFF that overflow signed) // then fall back to signed (handles negative values) @@ -1056,19 +1077,30 @@ impl AsBuffer for PyCArray { .expect("PyCArray type must have StgInfo"); let format = stg_info.format.clone(); let shape = stg_info.shape.clone(); - let element_size = stg_info.element_size; let desc = if let Some(fmt) = format && !shape.is_empty() { + // itemsize is the size of the base element type (item_info->size) + // For empty arrays, we still need the element size, not 0 + let total_elements: usize = shape.iter().product(); + let has_zero_dim = shape.contains(&0); + let itemsize = if total_elements > 0 && buffer_len > 0 { + buffer_len / total_elements + } else { + // For empty arrays, get itemsize from format type code + get_size_from_format(&fmt) + }; + // Build dim_desc from shape (C-contiguous: row-major order) // stride[i] = product(shape[i+1:]) * itemsize + // For empty arrays (any dimension is 0), all strides are 0 let mut dim_desc = Vec::with_capacity(shape.len()); - let mut stride = element_size as isize; + let mut stride = itemsize as isize; - // Calculate strides from innermost to outermost dimension for &dim_size in shape.iter().rev() { - dim_desc.push((dim_size, stride, 0)); + let current_stride = if has_zero_dim { 0 } else { stride }; + dim_desc.push((dim_size, current_stride, 0)); stride *= dim_size as isize; } dim_desc.reverse(); @@ -1076,7 +1108,7 @@ impl AsBuffer for PyCArray { BufferDescriptor { len: buffer_len, readonly: false, - itemsize: element_size, + itemsize, format: std::borrow::Cow::Owned(fmt), dim_desc, } diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 38c371346e..44793a2156 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -300,15 +300,27 @@ pub(super) fn get_field_format( big_endian: bool, vm: &VirtualMachine, ) -> String { + let endian_prefix = if big_endian { ">" } else { "<" }; + // 1. Check StgInfo for format if let Some(type_obj) = field_type.downcast_ref::() && let Some(stg_info) = type_obj.stg_info_opt() && let Some(fmt) = &stg_info.format { - // Handle endian prefix for simple types - if fmt.len() == 1 { - let endian_prefix = if big_endian { ">" } else { "<" }; - return format!("{}{}", endian_prefix, fmt); + // For structures (T{...}), arrays ((n)...), and pointers (&...), return as-is + // These complex types have their own endianness markers inside + if fmt.starts_with('T') + || fmt.starts_with('(') + || fmt.starts_with('&') + || fmt.starts_with("X{") + { + return fmt.clone(); + } + + // For simple types, replace existing endian prefix with the correct one + let base_fmt = fmt.trim_start_matches(['<', '>', '@', '=', '!']); + if !base_fmt.is_empty() { + return format!("{}{}", endian_prefix, base_fmt); } return fmt.clone(); } @@ -318,8 +330,7 @@ pub(super) fn get_field_format( && let Some(type_str) = type_attr.downcast_ref::() { let s = type_str.as_str(); - if s.len() == 1 { - let endian_prefix = if big_endian { ">" } else { "<" }; + if !s.is_empty() { return format!("{}{}", endian_prefix, s); } return s.to_string(); @@ -1168,29 +1179,30 @@ impl PyCData { .ok_or_else(|| vm.new_value_error("Invalid library handle"))? }; - // Get symbol address using platform-specific API - let symbol_name = std::ffi::CString::new(name.as_str()) - .map_err(|_| vm.new_value_error("Invalid symbol name"))?; - - #[cfg(windows)] - let ptr: *const u8 = unsafe { - match windows_sys::Win32::System::LibraryLoader::GetProcAddress( - handle as windows_sys::Win32::Foundation::HMODULE, - symbol_name.as_ptr() as *const u8, - ) { - Some(p) => p as *const u8, - None => std::ptr::null(), + // Look up the library in the cache and use lib.get() for symbol lookup + let library_cache = super::library::libcache().read(); + let library = library_cache + .get_lib(handle) + .ok_or_else(|| vm.new_value_error("Library not found"))?; + let inner_lib = library.lib.lock(); + + let symbol_name_with_nul = format!("{}\0", name.as_str()); + let ptr: *const u8 = if let Some(lib) = &*inner_lib { + unsafe { + lib.get::<*const u8>(symbol_name_with_nul.as_bytes()) + .map(|sym| *sym) + .map_err(|_| { + vm.new_value_error(format!("symbol '{}' not found", name.as_str())) + })? } + } else { + return Err(vm.new_value_error("Library closed")); }; - #[cfg(not(windows))] - let ptr: *const u8 = - unsafe { libc::dlsym(handle as *mut libc::c_void, symbol_name.as_ptr()) as *const u8 }; - + // dlsym can return NULL for symbols that resolve to NULL (e.g., GNU IFUNC) + // Treat NULL addresses as errors if ptr.is_null() { - return Err( - vm.new_value_error(format!("symbol '{}' not found in library", name.as_str())) - ); + return Err(vm.new_value_error(format!("symbol '{}' not found", name.as_str()))); } // PyCData_AtAddress @@ -1593,7 +1605,7 @@ impl PyCField { /// PyCField_set #[pyslot] fn descr_set( - zelf: &crate::PyObject, + zelf: &PyObject, obj: PyObjectRef, value: PySetterValue, vm: &VirtualMachine, @@ -1804,7 +1816,7 @@ pub enum FfiArgValue { F64(f64), Pointer(usize), /// Pointer with owned data. The PyObjectRef keeps the pointed data alive. - OwnedPointer(usize, #[allow(dead_code)] crate::PyObjectRef), + OwnedPointer(usize, #[allow(dead_code)] PyObjectRef), } impl FfiArgValue { @@ -2145,6 +2157,16 @@ pub(super) fn read_ptr_from_buffer(buffer: &[u8]) -> usize { } } +/// Check if a type is a "simple instance" (direct subclass of a simple type) +/// Returns TRUE for c_int, c_void_p, etc. (simple types with _type_ attribute) +/// Returns FALSE for Structure, Array, POINTER(T), etc. +pub(super) fn is_simple_instance(typ: &Py) -> bool { + // _ctypes_simple_instance + // Check if the type's metaclass is PyCSimpleType + let metaclass = typ.class(); + metaclass.fast_issubclass(super::simple::PyCSimpleType::static_type()) +} + /// Set or initialize StgInfo on a type pub(super) fn set_or_init_stginfo(type_ref: &PyType, stg_info: StgInfo) { if type_ref.init_type_data(stg_info.clone()).is_err() diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index 9bddb0ef0e..04ff238ebc 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -1,16 +1,19 @@ // spell-checker:disable use super::{ - _ctypes::CArgObject, PyCArray, PyCData, PyCPointer, PyCStructure, base::FfiArgValue, - simple::PyCSimple, type_info, + _ctypes::CArgObject, + PyCArray, PyCData, PyCPointer, PyCStructure, StgInfo, + base::{CDATA_BUFFER_METHODS, FfiArgValue, ParamFunc, StgInfoFlags}, + simple::PyCSimple, + type_info, }; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyBytes, PyDict, PyNone, PyStr, PyTuple, PyType, PyTypeRef}, class::StaticType, - convert::ToPyObject, function::FuncArgs, - types::{Callable, Constructor, Representable}, + protocol::{BufferDescriptor, PyBuffer}, + types::{AsBuffer, Callable, Constructor, Initializer, Representable}, vm::thread::with_current_vm, }; use libffi::{ @@ -18,9 +21,10 @@ use libffi::{ middle::{Arg, Cif, Closure, CodePtr, Type}, }; use libloading::Symbol; -use num_traits::ToPrimitive; +use num_traits::{Signed, ToPrimitive}; use rustpython_common::lock::PyRwLock; -use std::ffi::{self, c_void}; +use std::borrow::Cow; +use std::ffi::c_void; use std::fmt::Debug; // Internal function addresses for special ctypes functions @@ -28,6 +32,90 @@ pub(super) const INTERNAL_CAST_ADDR: usize = 1; pub(super) const INTERNAL_STRING_AT_ADDR: usize = 2; pub(super) const INTERNAL_WSTRING_AT_ADDR: usize = 3; +// Thread-local errno storage for ctypes +std::thread_local! { + /// Thread-local storage for ctypes errno + /// This is separate from the system errno - ctypes swaps them during FFI calls + /// when use_errno=True is specified. + static CTYPES_LOCAL_ERRNO: std::cell::Cell = const { std::cell::Cell::new(0) }; +} + +/// Get ctypes thread-local errno value +pub(super) fn get_errno_value() -> i32 { + CTYPES_LOCAL_ERRNO.with(|e| e.get()) +} + +/// Set ctypes thread-local errno value, returns old value +pub(super) fn set_errno_value(value: i32) -> i32 { + CTYPES_LOCAL_ERRNO.with(|e| { + let old = e.get(); + e.set(value); + old + }) +} + +/// Save and restore errno around FFI call (called when use_errno=True) +/// Before: restore thread-local errno to system +/// After: save system errno to thread-local +#[cfg(not(windows))] +fn swap_errno(f: F) -> R +where + F: FnOnce() -> R, +{ + // Before call: restore thread-local errno to system + let saved = CTYPES_LOCAL_ERRNO.with(|e| e.get()); + errno::set_errno(errno::Errno(saved)); + + // Call the function + let result = f(); + + // After call: save system errno to thread-local + let new_error = errno::errno().0; + CTYPES_LOCAL_ERRNO.with(|e| e.set(new_error)); + + result +} + +#[cfg(windows)] +std::thread_local! { + /// Thread-local storage for ctypes last_error (Windows only) + static CTYPES_LOCAL_LAST_ERROR: std::cell::Cell = const { std::cell::Cell::new(0) }; +} + +#[cfg(windows)] +pub(super) fn get_last_error_value() -> u32 { + CTYPES_LOCAL_LAST_ERROR.with(|e| e.get()) +} + +#[cfg(windows)] +pub(super) fn set_last_error_value(value: u32) -> u32 { + CTYPES_LOCAL_LAST_ERROR.with(|e| { + let old = e.get(); + e.set(value); + old + }) +} + +/// Save and restore last_error around FFI call (called when use_last_error=True) +#[cfg(windows)] +fn save_and_restore_last_error(f: F) -> R +where + F: FnOnce() -> R, +{ + // Before call: restore thread-local last_error to Windows + let saved = CTYPES_LOCAL_LAST_ERROR.with(|e| e.get()); + unsafe { windows_sys::Win32::Foundation::SetLastError(saved) }; + + // Call the function + let result = f(); + + // After call: save Windows last_error to thread-local + let new_error = unsafe { windows_sys::Win32::Foundation::GetLastError() }; + CTYPES_LOCAL_LAST_ERROR.with(|e| e.set(new_error)); + + result +} + type FP = unsafe extern "C" fn(); /// Get FFI type for a ctypes type code @@ -131,11 +219,19 @@ fn convert_to_pointer(value: &PyObject, vm: &VirtualMachine) -> PyResult direct value + // 7. Integer -> direct value (PyLong_AsVoidPtr behavior) if let Ok(int_val) = value.try_int(vm) { - return Ok(FfiArgValue::Pointer( - int_val.as_bigint().to_usize().unwrap_or(0), - )); + let bigint = int_val.as_bigint(); + // Negative values: use signed conversion (allows -1 as 0xFFFF...) + if bigint.is_negative() { + if let Some(signed_val) = bigint.to_isize() { + return Ok(FfiArgValue::Pointer(signed_val as usize)); + } + } else if let Some(unsigned_val) = bigint.to_usize() { + return Ok(FfiArgValue::Pointer(unsigned_val)); + } + // Value out of range - raise OverflowError + return Err(vm.new_overflow_error("int too large to convert to pointer".to_string())); } // 8. Check _as_parameter_ attribute ( recursive ConvParam) @@ -150,46 +246,86 @@ fn convert_to_pointer(value: &PyObject, vm: &VirtualMachine) -> PyResult PyResult<(Type, FfiArgValue)> { +/// Returns an Argument with FFI type, value, and optional keep object +fn conv_param(value: &PyObject, vm: &VirtualMachine) -> PyResult { // 1. CArgObject (from byref() or paramfunc) -> use stored type and value if let Some(carg) = value.downcast_ref::() { let ffi_type = ffi_type_from_tag(carg.tag); - return Ok((ffi_type, carg.value.clone())); + return Ok(Argument { + ffi_type, + keep: None, + value: carg.value.clone(), + }); } // 2. None -> NULL pointer if value.is(&vm.ctx.none) { - return Ok((Type::pointer(), FfiArgValue::Pointer(0))); + return Ok(Argument { + ffi_type: Type::pointer(), + keep: None, + value: FfiArgValue::Pointer(0), + }); } // 3. ctypes objects -> use paramfunc if let Ok(carg) = super::base::call_paramfunc(value, vm) { let ffi_type = ffi_type_from_tag(carg.tag); - return Ok((ffi_type, carg.value.clone())); + return Ok(Argument { + ffi_type, + keep: None, + value: carg.value.clone(), + }); } - // 4. Python str -> pointer (use internal UTF-8 buffer) + // 4. Python str -> wide string pointer (like PyUnicode_AsWideCharString) if let Some(s) = value.downcast_ref::() { - let addr = s.as_str().as_ptr() as usize; - return Ok((Type::pointer(), FfiArgValue::Pointer(addr))); + // Convert to null-terminated UTF-16 (wide string) + let wide: Vec = s + .as_str() + .encode_utf16() + .chain(std::iter::once(0)) + .collect(); + let wide_bytes: Vec = wide.iter().flat_map(|&x| x.to_ne_bytes()).collect(); + let keep = vm.ctx.new_bytes(wide_bytes); + let addr = keep.as_bytes().as_ptr() as usize; + return Ok(Argument { + ffi_type: Type::pointer(), + keep: Some(keep.into()), + value: FfiArgValue::Pointer(addr), + }); } - // 9. Python bytes -> pointer to buffer + // 9. Python bytes -> null-terminated buffer pointer + // Need to ensure null termination like c_char_p if let Some(bytes) = value.downcast_ref::() { - let addr = bytes.as_bytes().as_ptr() as usize; - return Ok((Type::pointer(), FfiArgValue::Pointer(addr))); + let mut buffer = bytes.as_bytes().to_vec(); + buffer.push(0); // Add null terminator + let keep = vm.ctx.new_bytes(buffer); + let addr = keep.as_bytes().as_ptr() as usize; + return Ok(Argument { + ffi_type: Type::pointer(), + keep: Some(keep.into()), + value: FfiArgValue::Pointer(addr), + }); } // 10. Python int -> i32 (default integer type) if let Ok(int_val) = value.try_int(vm) { let val = int_val.as_bigint().to_i32().unwrap_or(0); - return Ok((Type::i32(), FfiArgValue::I32(val))); + return Ok(Argument { + ffi_type: Type::i32(), + keep: None, + value: FfiArgValue::I32(val), + }); } // 11. Python float -> f64 if let Ok(float_val) = value.try_float(vm) { - return Ok((Type::f64(), FfiArgValue::F64(float_val.to_f64()))); + return Ok(Argument { + ffi_type: Type::f64(), + keep: None, + value: FfiArgValue::F64(float_val.to_f64()), + }); } // 12. Check _as_parameter_ attribute @@ -245,7 +381,7 @@ impl ArgumentType for PyTypeRef { } fn convert_object(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { - // Call from_param first to convert the value (like CPython's callproc.c:1235) + // Call from_param first to convert the value // converter = PyTuple_GET_ITEM(argtypes, i); // v = PyObject_CallOneArg(converter, arg); let from_param = self @@ -264,11 +400,10 @@ impl ArgumentType for PyTypeRef { return Ok(FfiArgValue::Pointer(0)); } - // For pointer types (POINTER(T)), we need to pass the ADDRESS of the value's buffer + // For pointer types (POINTER(T)), we need to pass the pointer VALUE stored in buffer if self.fast_issubclass(PyCPointer::static_type()) { - if let Some(cdata) = converted.downcast_ref::() { - let addr = cdata.buffer.read().as_ptr() as usize; - return Ok(FfiArgValue::Pointer(addr)); + if let Some(pointer) = converted.downcast_ref::() { + return Ok(FfiArgValue::Pointer(pointer.get_ptr_value())); } return convert_to_pointer(&converted, vm); } @@ -305,12 +440,6 @@ impl ArgumentType for PyTypeRef { trait ReturnType { fn to_ffi_type(&self, vm: &VirtualMachine) -> Option; - #[allow(clippy::wrong_self_convention)] - fn from_ffi_type( - &self, - value: *mut ffi::c_void, - vm: &VirtualMachine, - ) -> PyResult>; } impl ReturnType for PyTypeRef { @@ -343,130 +472,56 @@ impl ReturnType for PyTypeRef { // Fallback to class name get_ffi_type(self.name().to_string().as_str()) } - - fn from_ffi_type( - &self, - value: *mut ffi::c_void, - vm: &VirtualMachine, - ) -> PyResult> { - // Get the type code from _type_ attribute (use get_attr to traverse MRO) - let type_code = self - .as_object() - .get_attr(vm.ctx.intern_str("_type_"), vm) - .ok() - .and_then(|t| t.downcast_ref::().map(|s| s.to_string())); - - let result = match type_code.as_deref() { - Some("b") => vm - .ctx - .new_int(unsafe { *(value as *const i8) } as i32) - .into(), - Some("B") => vm - .ctx - .new_int(unsafe { *(value as *const u8) } as i32) - .into(), - Some("c") => vm - .ctx - .new_bytes(vec![unsafe { *(value as *const u8) }]) - .into(), - Some("h") => vm - .ctx - .new_int(unsafe { *(value as *const i16) } as i32) - .into(), - Some("H") => vm - .ctx - .new_int(unsafe { *(value as *const u16) } as i32) - .into(), - Some("i") => vm.ctx.new_int(unsafe { *(value as *const i32) }).into(), - Some("I") => vm.ctx.new_int(unsafe { *(value as *const u32) }).into(), - Some("l") => vm - .ctx - .new_int(unsafe { *(value as *const libc::c_long) }) - .into(), - Some("L") => vm - .ctx - .new_int(unsafe { *(value as *const libc::c_ulong) }) - .into(), - Some("q") => vm - .ctx - .new_int(unsafe { *(value as *const libc::c_longlong) }) - .into(), - Some("Q") => vm - .ctx - .new_int(unsafe { *(value as *const libc::c_ulonglong) }) - .into(), - Some("f") => vm - .ctx - .new_float(unsafe { *(value as *const f32) } as f64) - .into(), - Some("d") => vm.ctx.new_float(unsafe { *(value as *const f64) }).into(), - Some("P") | Some("z") | Some("Z") => { - vm.ctx.new_int(unsafe { *(value as *const usize) }).into() - } - Some("?") => vm - .ctx - .new_bool(unsafe { *(value as *const u8) } != 0) - .into(), - None => { - // No _type_ attribute - check for Structure/Array types - // GetResult: PyCData_FromBaseObj creates instance from memory - if let Some(stg_info) = self.stg_info_opt() { - let size = stg_info.size; - // Create instance of the ctypes type - let instance = self.as_object().call((), vm)?; - - // Copy return value memory into instance buffer - // Use a block to properly scope the borrow - { - let src = unsafe { std::slice::from_raw_parts(value as *const u8, size) }; - if let Some(cdata) = instance.downcast_ref::() { - let mut buffer = cdata.buffer.write(); - if buffer.len() >= size { - buffer.to_mut()[..size].copy_from_slice(src); - } - } else if let Some(structure) = instance.downcast_ref::() { - let mut buffer = structure.0.buffer.write(); - if buffer.len() >= size { - buffer.to_mut()[..size].copy_from_slice(src); - } - } else if let Some(array) = instance.downcast_ref::() { - let mut buffer = array.0.buffer.write(); - if buffer.len() >= size { - buffer.to_mut()[..size].copy_from_slice(src); - } - } - } - return Ok(Some(instance)); - } - // Not a ctypes type - call type with int result - return self - .as_object() - .call((unsafe { *(value as *const i32) },), vm) - .map(Some); - } - _ => return Err(vm.new_type_error("Unsupported return type")), - }; - Ok(Some(result)) - } } impl ReturnType for PyNone { fn to_ffi_type(&self, _vm: &VirtualMachine) -> Option { get_ffi_type("void") } +} + +// PyCFuncPtrType - Metaclass for function pointer types +// PyCFuncPtrType_init + +#[pyclass(name = "PyCFuncPtrType", base = PyType, module = "_ctypes")] +#[derive(Debug)] +#[repr(transparent)] +pub(super) struct PyCFuncPtrType(PyType); - fn from_ffi_type( - &self, - _value: *mut ffi::c_void, - _vm: &VirtualMachine, - ) -> PyResult> { - Ok(None) +impl Initializer for PyCFuncPtrType { + type Args = FuncArgs; + + fn init(zelf: PyRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { + let obj: PyObjectRef = zelf.clone().into(); + let new_type: PyTypeRef = obj + .downcast() + .map_err(|_| vm.new_type_error("expected type"))?; + + new_type.check_not_initialized(vm)?; + + let ptr_size = std::mem::size_of::(); + let mut stg_info = StgInfo::new(ptr_size, ptr_size); + stg_info.format = Some("X{}".to_string()); + stg_info.length = 1; + stg_info.flags |= StgInfoFlags::TYPEFLAG_ISPOINTER; + stg_info.paramfunc = ParamFunc::Pointer; // CFuncPtr is passed as a pointer + + let _ = new_type.init_type_data(stg_info); + Ok(()) } } +#[pyclass(flags(IMMUTABLETYPE), with(Initializer))] +impl PyCFuncPtrType {} + /// PyCFuncPtr - Function pointer instance /// Saved in _base.buffer -#[pyclass(module = "_ctypes", name = "CFuncPtr", base = PyCData)] +#[pyclass( + module = "_ctypes", + name = "CFuncPtr", + base = PyCData, + metaclass = "PyCFuncPtrType" +)] #[repr(C)] pub(super) struct PyCFuncPtr { pub _base: PyCData, @@ -892,7 +947,13 @@ impl Constructor for PyCFuncPtr { .map_err(|err| err.to_string()) .map_err(|err| vm.new_attribute_error(err))? }; - *pointer as usize + let addr = *pointer as usize; + // dlsym can return NULL for symbols that resolve to NULL (e.g., GNU IFUNC) + // Treat NULL addresses as errors + if addr == 0 { + return Err(vm.new_attribute_error(format!("function '{}' not found", name))); + } + addr } else { 0 }; @@ -921,12 +982,17 @@ impl Constructor for PyCFuncPtr { // Get argument types and result type from the class let class_argtypes = cls.get_attr(vm.ctx.intern_str("_argtypes_")); let class_restype = cls.get_attr(vm.ctx.intern_str("_restype_")); + let class_flags = cls + .get_attr(vm.ctx.intern_str("_flags_")) + .and_then(|f| f.try_to_value::(vm).ok()) + .unwrap_or(0); // Create the thunk (C-callable wrapper for the Python function) let thunk = PyCThunk::new( first_arg.clone(), class_argtypes.clone(), class_restype.clone(), + class_flags, vm, )?; let code_ptr = thunk.code_ptr(); @@ -1060,13 +1126,16 @@ fn extract_call_info(zelf: &Py, vm: &VirtualMachine) -> PyResult().ok()) - .and_then(|t| t.as_object().get_attr(vm.ctx.intern_str("_type_"), vm).ok()) - .and_then(|t| t.downcast_ref::().map(|s| s.to_string())) - .is_some_and(|tc| matches!(tc.as_str(), "P" | "z" | "Z")); + .and_then(|t| { + t.stg_info_opt() + .map(|info| info.flags.contains(StgInfoFlags::TYPEFLAG_ISPOINTER)) + }) + .unwrap_or(false); Ok(CallInfo { explicit_arg_types, @@ -1178,13 +1247,18 @@ fn resolve_com_method( Ok((Some(CodePtr(fptr as *mut _)), true)) } -/// Prepared arguments for FFI call -struct PreparedArgs { - ffi_arg_types: Vec, - ffi_values: Vec, - out_buffers: Vec<(usize, PyObjectRef)>, +/// Single argument for FFI call +// struct argument +struct Argument { + ffi_type: Type, + value: FfiArgValue, + #[allow(dead_code)] + keep: Option, // Object to keep alive during call } +/// Out buffers for paramflags OUT parameters +type OutBuffers = Vec<(usize, PyObjectRef)>; + /// Get buffer address from a ctypes object fn get_buffer_addr(obj: &PyObjectRef) -> Option { obj.downcast_ref::() @@ -1213,18 +1287,16 @@ fn create_out_buffer(arg_type: &PyTypeRef, vm: &VirtualMachine) -> PyResult PyResult { - let results: Vec<(Type, FfiArgValue)> = args +fn build_callargs_no_argtypes( + args: &FuncArgs, + vm: &VirtualMachine, +) -> PyResult<(Vec, OutBuffers)> { + let arguments: Vec = args .args .iter() .map(|arg| conv_param(arg, vm)) .collect::>>()?; - let (ffi_arg_types, ffi_values) = results.into_iter().unzip(); - Ok(PreparedArgs { - ffi_arg_types, - ffi_values, - out_buffers: Vec::new(), - }) + Ok((arguments, Vec::new())) } /// Build callargs for regular function with argtypes (no paramflags) @@ -1232,12 +1304,8 @@ fn build_callargs_simple( args: &FuncArgs, arg_types: &[PyTypeRef], vm: &VirtualMachine, -) -> PyResult { - let ffi_arg_types = arg_types - .iter() - .map(|t| ArgumentType::to_ffi_type(t, vm)) - .collect::>>()?; - let ffi_values = args +) -> PyResult<(Vec, OutBuffers)> { + let arguments: Vec = args .args .iter() .enumerate() @@ -1245,14 +1313,16 @@ fn build_callargs_simple( let arg_type = arg_types .get(n) .ok_or_else(|| vm.new_type_error("argument amount mismatch"))?; - arg_type.convert_object(arg.clone(), vm) + let ffi_type = ArgumentType::to_ffi_type(arg_type, vm)?; + let value = arg_type.convert_object(arg.clone(), vm)?; + Ok(Argument { + ffi_type, + keep: None, + value, + }) }) - .collect::, _>>()?; - Ok(PreparedArgs { - ffi_arg_types, - ffi_values, - out_buffers: Vec::new(), - }) + .collect::>>()?; + Ok((arguments, Vec::new())) } /// Build callargs with paramflags (handles IN/OUT parameters) @@ -1262,27 +1332,21 @@ fn build_callargs_with_paramflags( paramflags: &ParsedParamFlags, skip_first_arg: bool, // true for COM methods vm: &VirtualMachine, -) -> PyResult { - let mut ffi_arg_types = Vec::new(); - let mut ffi_values = Vec::new(); +) -> PyResult<(Vec, OutBuffers)> { + let mut arguments = Vec::new(); let mut out_buffers = Vec::new(); // For COM methods, first arg is self (pointer) let mut caller_arg_idx = if skip_first_arg { - ffi_arg_types.push(Type::pointer()); if !args.args.is_empty() { - ffi_values.push(conv_param(&args.args[0], vm)?.1); + let arg = conv_param(&args.args[0], vm)?; + arguments.push(arg); } 1usize } else { 0usize }; - // Add FFI types for all argtypes - for arg_type in arg_types { - ffi_arg_types.push(ArgumentType::to_ffi_type(arg_type, vm)?); - } - // Process parameters based on paramflags for (param_idx, (direction, _name, default)) in paramflags.iter().enumerate() { let arg_type = arg_types @@ -1292,13 +1356,19 @@ fn build_callargs_with_paramflags( let is_out = (*direction & 2) != 0; // OUT flag let is_in = (*direction & 1) != 0 || *direction == 0; // IN flag or default + let ffi_type = ArgumentType::to_ffi_type(arg_type, vm)?; + if is_out && !is_in { // Pure OUT parameter: create buffer, don't consume caller arg let buffer = create_out_buffer(arg_type, vm)?; let addr = get_buffer_addr(&buffer).ok_or_else(|| { vm.new_type_error("Cannot create OUT buffer for this type".to_string()) })?; - ffi_values.push(FfiArgValue::Pointer(addr)); + arguments.push(Argument { + ffi_type, + keep: None, + value: FfiArgValue::Pointer(addr), + }); out_buffers.push((param_idx, buffer)); } else { // IN or IN|OUT: get from caller args or default @@ -1315,15 +1385,16 @@ fn build_callargs_with_paramflags( // IN|OUT: track for return out_buffers.push((param_idx, arg.clone())); } - ffi_values.push(arg_type.convert_object(arg, vm)?); + let value = arg_type.convert_object(arg, vm)?; + arguments.push(Argument { + ffi_type, + keep: None, + value, + }); } } - Ok(PreparedArgs { - ffi_arg_types, - ffi_values, - out_buffers, - }) + Ok((arguments, out_buffers)) } /// Build call arguments (main dispatcher) @@ -1333,7 +1404,7 @@ fn build_callargs( paramflags: Option<&ParsedParamFlags>, is_com_method: bool, vm: &VirtualMachine, -) -> PyResult { +) -> PyResult<(Vec, OutBuffers)> { let Some(ref arg_types) = call_info.explicit_arg_types else { // No argtypes: use ConvParam return build_callargs_no_argtypes(args, vm); @@ -1344,28 +1415,23 @@ fn build_callargs( build_callargs_with_paramflags(args, arg_types, pflags, is_com_method, vm) } else if is_com_method { // COM method without paramflags - let mut ffi_types = vec![Type::pointer()]; - ffi_types.extend( - arg_types - .iter() - .map(|t| ArgumentType::to_ffi_type(t, vm)) - .collect::>>()?, - ); - let mut ffi_vals = Vec::new(); + let mut arguments = Vec::new(); if !args.args.is_empty() { - ffi_vals.push(conv_param(&args.args[0], vm)?.1); + arguments.push(conv_param(&args.args[0], vm)?); } for (n, arg) in args.args.iter().skip(1).enumerate() { let arg_type = arg_types .get(n) .ok_or_else(|| vm.new_type_error("argument amount mismatch"))?; - ffi_vals.push(arg_type.convert_object(arg.clone(), vm)?); + let ffi_type = ArgumentType::to_ffi_type(arg_type, vm)?; + let value = arg_type.convert_object(arg.clone(), vm)?; + arguments.push(Argument { + ffi_type, + keep: None, + value, + }); } - Ok(PreparedArgs { - ffi_arg_types: ffi_types, - ffi_values: ffi_vals, - out_buffers: Vec::new(), - }) + Ok((arguments, Vec::new())) } else { // Regular function build_callargs_simple(args, arg_types, vm) @@ -1380,12 +1446,10 @@ enum RawResult { } /// Execute FFI call -fn ctypes_callproc(code_ptr: CodePtr, prepared: &PreparedArgs, call_info: &CallInfo) -> RawResult { - let cif = Cif::new( - prepared.ffi_arg_types.clone(), - call_info.ffi_return_type.clone(), - ); - let ffi_args: Vec = prepared.ffi_values.iter().map(|v| v.as_arg()).collect(); +fn ctypes_callproc(code_ptr: CodePtr, arguments: &[Argument], call_info: &CallInfo) -> RawResult { + let ffi_arg_types: Vec = arguments.iter().map(|a| a.ffi_type.clone()).collect(); + let cif = Cif::new(ffi_arg_types, call_info.ffi_return_type.clone()); + let ffi_args: Vec = arguments.iter().map(|a| a.value.as_arg()).collect(); if call_info.restype_is_none { unsafe { cif.call::<()>(code_ptr, &ffi_args) }; @@ -1438,73 +1502,118 @@ fn check_hresult(hresult: i32, zelf: &Py, vm: &VirtualMachine) -> Py } /// Convert raw FFI result to Python object +// = GetResult fn convert_raw_result( raw_result: &mut RawResult, call_info: &CallInfo, vm: &VirtualMachine, ) -> Option { - match raw_result { - RawResult::Void => None, + // Get result as bytes for type conversion + let (result_bytes, result_size) = match raw_result { + RawResult::Void => return None, RawResult::Pointer(ptr) => { - // Get type code from restype to determine conversion method - let type_code = call_info - .restype_obj - .as_ref() - .and_then(|t| t.clone().downcast::().ok()) - .and_then(|t| t.as_object().get_attr(vm.ctx.intern_str("_type_"), vm).ok()) - .and_then(|t| t.downcast_ref::().map(|s| s.to_string())); - - match type_code.as_deref() { - Some("z") => { - // c_char_p: NULL -> None, otherwise read C string -> bytes - if *ptr == 0 { - Some(vm.ctx.none()) - } else { - let cstr = unsafe { std::ffi::CStr::from_ptr(*ptr as _) }; - Some(vm.ctx.new_bytes(cstr.to_bytes().to_vec()).into()) - } - } - Some("Z") => { - // c_wchar_p: NULL -> None, otherwise read wide string -> str - if *ptr == 0 { - Some(vm.ctx.none()) - } else { - let wstr_ptr = *ptr as *const libc::wchar_t; - let mut len = 0; - unsafe { - while *wstr_ptr.add(len) != 0 { - len += 1; - } - } - let slice = unsafe { std::slice::from_raw_parts(wstr_ptr, len) }; - let s: String = slice - .iter() - .filter_map(|&c| char::from_u32(c as u32)) - .collect(); - Some(vm.ctx.new_str(s).into()) - } - } - _ => { - // c_void_p ("P") and other pointer types: NULL -> None, otherwise int - if *ptr == 0 { - Some(vm.ctx.none()) - } else { - Some(vm.ctx.new_int(*ptr).into()) - } - } - } + let bytes = ptr.to_ne_bytes(); + (bytes.to_vec(), std::mem::size_of::()) } - RawResult::Value(val) => call_info - .restype_obj - .as_ref() - .and_then(|f| f.clone().downcast::().ok()) - .map(|f| { - f.from_ffi_type(val as *mut _ as *mut c_void, vm) - .ok() - .flatten() - }) - .unwrap_or_else(|| Some(vm.ctx.new_int(*val as usize).as_object().to_pyobject(vm))), + RawResult::Value(val) => { + let bytes = val.to_ne_bytes(); + (bytes.to_vec(), std::mem::size_of::()) + } + }; + + // 1. No restype → return as int + let restype = match &call_info.restype_obj { + None => { + // Default: return as int + let val = match raw_result { + RawResult::Pointer(p) => *p as isize, + RawResult::Value(v) => *v as isize, + RawResult::Void => return None, + }; + return Some(vm.ctx.new_int(val).into()); + } + Some(r) => r, + }; + + // 2. restype is None → return None + if restype.is(&vm.ctx.none()) { + return None; + } + + // 3. Get restype as PyType + let restype_type = match restype.clone().downcast::() { + Ok(t) => t, + Err(_) => { + // Not a type, call it with int result + let val = match raw_result { + RawResult::Pointer(p) => *p as isize, + RawResult::Value(v) => *v as isize, + RawResult::Void => return None, + }; + return restype.call((val,), vm).ok(); + } + }; + + // 4. Get StgInfo + let stg_info = restype_type.stg_info_opt(); + + // No StgInfo → call restype with int + if stg_info.is_none() { + let val = match raw_result { + RawResult::Pointer(p) => *p as isize, + RawResult::Value(v) => *v as isize, + RawResult::Void => return None, + }; + return restype_type.as_object().call((val,), vm).ok(); + } + + let info = stg_info.unwrap(); + + // 5. Simple type with getfunc → use bytes_to_pyobject (info->getfunc) + // is_simple_instance returns TRUE for c_int, c_void_p, etc. + if super::base::is_simple_instance(&restype_type) { + return super::base::bytes_to_pyobject(&restype_type, &result_bytes, vm).ok(); + } + + // 6. Complex type → create ctypes instance (PyCData_FromBaseObj) + // This handles POINTER(T), Structure, Array, etc. + + // Special handling for POINTER(T) types - set pointer value directly + if info.flags.contains(StgInfoFlags::TYPEFLAG_ISPOINTER) + && info.proto.is_some() + && let RawResult::Pointer(ptr) = raw_result + && let Ok(instance) = restype_type.as_object().call((), vm) + { + if let Some(pointer) = instance.downcast_ref::() { + pointer.set_ptr_value(*ptr); + } + return Some(instance); } + + // Create instance and copy result data + pycdata_from_ffi_result(&restype_type, &result_bytes, result_size, vm).ok() +} + +/// Create a ctypes instance from FFI result (PyCData_FromBaseObj equivalent) +fn pycdata_from_ffi_result( + typ: &PyTypeRef, + result_bytes: &[u8], + size: usize, + vm: &VirtualMachine, +) -> PyResult { + // Create instance + let instance = PyType::call(typ, ().into(), vm)?; + + // Copy result data into instance buffer + if let Some(cdata) = instance.downcast_ref::() { + let mut buffer = cdata.buffer.write(); + let copy_size = size.min(buffer.len()).min(result_bytes.len()); + if copy_size > 0 { + buffer.to_mut()[..copy_size].copy_from_slice(&result_bytes[..copy_size]); + } + } + + Ok(instance) } /// Extract values from OUT buffers @@ -1522,7 +1631,7 @@ fn extract_out_values( fn build_result( mut raw_result: RawResult, call_info: &CallInfo, - prepared: PreparedArgs, + out_buffers: OutBuffers, zelf: &Py, args: &FuncArgs, vm: &VirtualMachine, @@ -1552,11 +1661,11 @@ fn build_result( } // Handle OUT parameter return values - if prepared.out_buffers.is_empty() { + if out_buffers.is_empty() { return result.map(Ok).unwrap_or_else(|| Ok(vm.ctx.none())); } - let out_values = extract_out_values(prepared.out_buffers, vm); + let out_values = extract_out_values(out_buffers, vm); Ok(match <[PyObjectRef; 1]>::try_from(out_values) { Ok([single]) => single, Err(v) => PyTuple::new_ref(v, &vm.ctx).into(), @@ -1584,23 +1693,43 @@ impl Callable for PyCFuncPtr { let paramflags = parse_paramflags(zelf, vm)?; // 5. Build call arguments - let prepared = build_callargs(&args, &call_info, paramflags.as_ref(), is_com_method, vm)?; + let (arguments, out_buffers) = + build_callargs(&args, &call_info, paramflags.as_ref(), is_com_method, vm)?; // 6. Get code pointer let code_ptr = match func_ptr.or_else(|| zelf.get_code_ptr()) { Some(cp) => cp, None => { debug_assert!(false, "NULL function pointer"); - // In release mode, this will crash like CPython + // In release mode, this will crash CodePtr(std::ptr::null_mut()) } }; - // 7. Call the function - let raw_result = ctypes_callproc(code_ptr, &prepared, &call_info); + // 7. Get flags to check for use_last_error/use_errno + let flags = PyCFuncPtr::_flags_(zelf, vm); - // 8. Build result - build_result(raw_result, &call_info, prepared, zelf, &args, vm) + // 8. Call the function (with use_last_error/use_errno handling) + #[cfg(not(windows))] + let raw_result = { + if flags & super::base::StgInfoFlags::FUNCFLAG_USE_ERRNO.bits() != 0 { + swap_errno(|| ctypes_callproc(code_ptr, &arguments, &call_info)) + } else { + ctypes_callproc(code_ptr, &arguments, &call_info) + } + }; + + #[cfg(windows)] + let raw_result = { + if flags & super::base::StgInfoFlags::FUNCFLAG_USE_LASTERROR.bits() != 0 { + save_and_restore_last_error(|| ctypes_callproc(code_ptr, &arguments, &call_info)) + } else { + ctypes_callproc(code_ptr, &arguments, &call_info) + } + }; + + // 9. Build result + build_result(raw_result, &call_info, out_buffers, zelf, &args, vm) } } @@ -1614,7 +1743,36 @@ impl Representable for PyCFuncPtr { } } -#[pyclass(flags(BASETYPE), with(Callable, Constructor, Representable))] +// PyCData_NewGetBuffer +impl AsBuffer for PyCFuncPtr { + fn as_buffer(zelf: &Py, _vm: &VirtualMachine) -> PyResult { + // CFuncPtr types may not have StgInfo if PyCFuncPtrType metaclass is not used + // Use default values for function pointers: format="X{}", size=sizeof(pointer) + let (format, itemsize) = if let Some(stg_info) = zelf.class().stg_info_opt() { + ( + stg_info + .format + .clone() + .map(Cow::Owned) + .unwrap_or(Cow::Borrowed("X{}")), + stg_info.size, + ) + } else { + (Cow::Borrowed("X{}"), std::mem::size_of::()) + }; + let desc = BufferDescriptor { + len: itemsize, + readonly: false, + itemsize, + format, + dim_desc: vec![], + }; + let buf = PyBuffer::new(zelf.to_owned().into(), desc, &CDATA_BUFFER_METHODS); + Ok(buf) + } +} + +#[pyclass(flags(BASETYPE), with(Callable, Constructor, Representable, AsBuffer))] impl PyCFuncPtr { // restype getter/setter #[pygetset] @@ -1685,7 +1843,6 @@ impl PyCFuncPtr { } // Fallback to StgInfo for native types - use super::base::StgInfoFlags; zelf.class() .stg_info_opt() .map(|stg| stg.flags.bits()) @@ -1708,7 +1865,9 @@ struct ThunkUserData { /// Argument types for conversion arg_types: Vec, /// Result type for conversion (None means void) - res_type: Option, + pub res_type: Option, + /// Function flags (FUNCFLAG_USE_ERRNO, etc.) + pub flags: u32, } /// Check if ty is a subclass of a simple type (like MyInt(c_int)). @@ -1758,11 +1917,23 @@ fn ffi_to_python(ty: &Py, ptr: *const c_void, vm: &VirtualMachine) -> Py len += 1; } let slice = std::slice::from_raw_parts(wstr_ptr, len); - let s: String = slice - .iter() - .filter_map(|&c| char::from_u32(c as u32)) - .collect(); - vm.ctx.new_str(s).into() + // Windows: wchar_t = u16 (UTF-16) -> use Wtf8Buf::from_wide + // Unix: wchar_t = i32 (UTF-32) -> convert via char::from_u32 + #[cfg(windows)] + { + use rustpython_common::wtf8::Wtf8Buf; + let wide: Vec = slice.to_vec(); + let wtf8 = Wtf8Buf::from_wide(&wide); + vm.ctx.new_str(wtf8).into() + } + #[cfg(not(windows))] + { + let s: String = slice + .iter() + .filter_map(|&c| char::from_u32(c as u32)) + .collect(); + vm.ctx.new_str(s).into() + } } } Some("P") => vm.ctx.new_int(*(ptr as *const usize)).into(), @@ -1865,6 +2036,16 @@ unsafe extern "C" fn thunk_callback( userdata: &ThunkUserData, ) { with_current_vm(|vm| { + // Swap errno before call if FUNCFLAG_USE_ERRNO is set + let use_errno = userdata.flags & StgInfoFlags::FUNCFLAG_USE_ERRNO.bits() != 0; + let saved_errno = if use_errno { + let current = rustpython_common::os::get_errno(); + // TODO: swap with ctypes stored errno (thread-local) + Some(current) + } else { + None + }; + let py_args: Vec = userdata .arg_types .iter() @@ -1877,6 +2058,15 @@ unsafe extern "C" fn thunk_callback( let py_result = userdata.callable.call(py_args, vm); + // Swap errno back after call + if use_errno { + let _current = rustpython_common::os::get_errno(); + // TODO: store current errno to ctypes storage + if let Some(saved) = saved_errno { + rustpython_common::os::set_errno(saved); + } + } + // Call unraisable hook if exception occurred if let Err(exc) = &py_result { let repr = userdata @@ -1935,6 +2125,7 @@ impl PyCThunk { callable: PyObjectRef, arg_types: Option, res_type: Option, + flags: u32, vm: &VirtualMachine, ) -> PyResult { let arg_type_vec: Vec = match arg_types { @@ -1979,6 +2170,7 @@ impl PyCThunk { callable: callable.clone(), arg_types: arg_type_vec, res_type: res_type_ref, + flags, }); let userdata_ptr = Box::into_raw(userdata); let userdata_ref: &'static ThunkUserData = unsafe { &*userdata_ptr }; diff --git a/crates/vm/src/stdlib/ctypes/library.rs b/crates/vm/src/stdlib/ctypes/library.rs index ec8ca91af0..7512ce29d8 100644 --- a/crates/vm/src/stdlib/ctypes/library.rs +++ b/crates/vm/src/stdlib/ctypes/library.rs @@ -2,12 +2,14 @@ use crate::VirtualMachine; use libloading::Library; use rustpython_common::lock::{PyMutex, PyRwLock}; use std::collections::HashMap; -use std::ffi::{OsStr, c_void}; +use std::ffi::OsStr; use std::fmt; -use std::ptr::null; -pub(super) struct SharedLibrary { - pub(super) lib: PyMutex>, +#[cfg(unix)] +use libloading::os::unix::Library as UnixLibrary; + +pub struct SharedLibrary { + pub(crate) lib: PyMutex>, } impl fmt::Debug for SharedLibrary { @@ -17,18 +19,44 @@ impl fmt::Debug for SharedLibrary { } impl SharedLibrary { - fn new(name: impl AsRef) -> Result { + #[cfg(windows)] + pub fn new(name: impl AsRef) -> Result { Ok(SharedLibrary { lib: PyMutex::new(unsafe { Some(Library::new(name.as_ref())?) }), }) } - fn get_pointer(&self) -> usize { + #[cfg(unix)] + pub fn new_with_mode( + name: impl AsRef, + mode: i32, + ) -> Result { + Ok(SharedLibrary { + lib: PyMutex::new(Some(unsafe { + UnixLibrary::open(Some(name.as_ref()), mode)?.into() + })), + }) + } + + /// Create a SharedLibrary from a raw dlopen handle (for pythonapi / dlopen(NULL)) + #[cfg(unix)] + pub fn from_raw_handle(handle: *mut libc::c_void) -> SharedLibrary { + SharedLibrary { + lib: PyMutex::new(Some(unsafe { UnixLibrary::from_raw(handle).into() })), + } + } + + /// Get the underlying OS handle (HMODULE on Windows, dlopen handle on Unix) + pub fn get_pointer(&self) -> usize { let lib_lock = self.lib.lock(); if let Some(l) = &*lib_lock { - l as *const Library as usize + // libloading::Library internally stores the OS handle directly + // On Windows: HMODULE (*mut c_void) + // On Unix: *mut c_void from dlopen + // We use transmute_copy to read the handle without consuming the Library + unsafe { std::mem::transmute_copy::(l) } } else { - null::() as usize + 0 } } @@ -36,16 +64,6 @@ impl SharedLibrary { let lib_lock = self.lib.lock(); lib_lock.is_none() } - - fn close(&self) { - *self.lib.lock() = None; - } -} - -impl Drop for SharedLibrary { - fn drop(&mut self) { - self.close(); - } } pub(super) struct ExternalLibs { @@ -63,6 +81,7 @@ impl ExternalLibs { self.libraries.get(&key) } + #[cfg(windows)] pub fn get_or_insert_lib( &mut self, library_path: impl AsRef, @@ -71,20 +90,53 @@ impl ExternalLibs { let new_lib = SharedLibrary::new(library_path)?; let key = new_lib.get_pointer(); - match self.libraries.get(&key) { - Some(l) => { - if l.is_closed() { - self.libraries.insert(key, new_lib); - } - } - _ => { - self.libraries.insert(key, new_lib); - } - }; + // Check if library already exists and is not closed + let should_use_cached = self.libraries.get(&key).is_some_and(|l| !l.is_closed()); + + if should_use_cached { + // new_lib will be dropped, calling FreeLibrary (decrements refcount) + // But library stays loaded because cached version maintains refcount + drop(new_lib); + return Ok((key, self.libraries.get(&key).expect("just checked"))); + } + self.libraries.insert(key, new_lib); Ok((key, self.libraries.get(&key).expect("just inserted"))) } + #[cfg(unix)] + pub fn get_or_insert_lib_with_mode( + &mut self, + library_path: impl AsRef, + mode: i32, + _vm: &VirtualMachine, + ) -> Result<(usize, &SharedLibrary), libloading::Error> { + let new_lib = SharedLibrary::new_with_mode(library_path, mode)?; + let key = new_lib.get_pointer(); + + // Check if library already exists and is not closed + let should_use_cached = self.libraries.get(&key).is_some_and(|l| !l.is_closed()); + + if should_use_cached { + // new_lib will be dropped, calling dlclose (decrements refcount) + // But library stays loaded because cached version maintains refcount + drop(new_lib); + return Ok((key, self.libraries.get(&key).expect("just checked"))); + } + + self.libraries.insert(key, new_lib); + Ok((key, self.libraries.get(&key).expect("just inserted"))) + } + + /// Insert a raw dlopen handle into the cache (for pythonapi / dlopen(NULL)) + #[cfg(unix)] + pub fn insert_raw_handle(&mut self, handle: *mut libc::c_void) -> usize { + let shared_lib = SharedLibrary::from_raw_handle(handle); + let key = handle as usize; + self.libraries.insert(key, shared_lib); + key + } + pub fn drop_lib(&mut self, key: usize) { self.libraries.remove(&key); } diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index 3ee39af3a7..ae97b741b3 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -1,6 +1,7 @@ +use super::base::CDATA_BUFFER_METHODS; use super::{PyCArray, PyCData, PyCSimple, PyCStructure, StgInfo, StgInfoFlags}; -use crate::protocol::PyNumberMethods; -use crate::types::{AsNumber, Constructor, Initializer}; +use crate::protocol::{BufferDescriptor, PyBuffer, PyNumberMethods}; +use crate::types::{AsBuffer, AsNumber, Constructor, Initializer}; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyBytes, PyInt, PyList, PySlice, PyStr, PyType, PyTypeRef}, @@ -8,6 +9,7 @@ use crate::{ function::{FuncArgs, OptionalArg}, }; use num_traits::ToPrimitive; +use std::borrow::Cow; #[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")] #[derive(Debug)] @@ -42,11 +44,19 @@ impl Initializer for PyCPointerType { stg_info.length = 1; stg_info.flags |= StgInfoFlags::TYPEFLAG_ISPOINTER; - // Set format string: "&" - if let Some(ref proto) = stg_info.proto { - let item_info = proto.stg_info_opt().expect("proto has StgInfo"); + // Set format string: "&" or "&(shape)" for arrays + if let Some(ref proto) = stg_info.proto + && let Some(item_info) = proto.stg_info_opt() + { let current_format = item_info.format.as_deref().unwrap_or("B"); - stg_info.format = Some(format!("&{}", current_format)); + // Include shape for array types in the pointer format + let shape_str = if !item_info.shape.is_empty() { + let dims: Vec = item_info.shape.iter().map(|d| d.to_string()).collect(); + format!("({})", dims.join(",")) + } else { + String::new() + }; + stg_info.format = Some(format!("&{}{}", shape_str, current_format)); } let _ = new_type.init_type_data(stg_info); @@ -69,6 +79,15 @@ impl PyCPointerType { return Ok(value); } + // 1.5 CArgObject (from byref()) - check if underlying obj is instance of _type_ + if let Some(carg) = value.downcast_ref::() + && let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) + && let Ok(type_ref) = type_attr.downcast::() + && carg.obj.is_instance(type_ref.as_object(), vm)? + { + return Ok(value); + } + // 2. If already an instance of the requested type, return it if value.is_instance(cls.as_object(), vm)? { return Ok(value); @@ -149,10 +168,17 @@ impl PyCPointerType { if let Some(mut stg_info) = zelf.get_type_data_mut::() { stg_info.proto = Some(typ_type.clone()); - // Update format string: "&" + // Update format string: "&" or "&(shape)" for arrays let item_info = typ_type.stg_info_opt().expect("proto has StgInfo"); let current_format = item_info.format.as_deref().unwrap_or("B"); - stg_info.format = Some(format!("&{}", current_format)); + // Include shape for array types in the pointer format + let shape_str = if !item_info.shape.is_empty() { + let dims: Vec = item_info.shape.iter().map(|d| d.to_string()).collect(); + format!("({})", dims.join(",")) + } else { + String::new() + }; + stg_info.format = Some(format!("&{}{}", shape_str, current_format)); } // 4. Set _type_ attribute on the pointer type @@ -233,7 +259,10 @@ impl Initializer for PyCPointer { } } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, Initializer))] +#[pyclass( + flags(BASETYPE, IMMUTABLETYPE), + with(Constructor, Initializer, AsBuffer) +)] impl PyCPointer { /// Get the pointer value stored in buffer as usize pub fn get_ptr_value(&self) -> usize { @@ -605,20 +634,47 @@ impl PyCPointer { unsafe { let ptr = addr as *const u8; match type_code { + // Single-byte types don't need read_unaligned Some("c") => Ok(vm.ctx.new_bytes(vec![*ptr]).into()), - Some("b") => Ok(vm.ctx.new_int(*(ptr as *const i8) as i32).into()), + Some("b") => Ok(vm.ctx.new_int(*ptr as i8 as i32).into()), Some("B") => Ok(vm.ctx.new_int(*ptr as i32).into()), - Some("h") => Ok(vm.ctx.new_int(*(ptr as *const i16) as i32).into()), - Some("H") => Ok(vm.ctx.new_int(*(ptr as *const u16) as i32).into()), - Some("i") | Some("l") => Ok(vm.ctx.new_int(*(ptr as *const i32)).into()), - Some("I") | Some("L") => Ok(vm.ctx.new_int(*(ptr as *const u32)).into()), - Some("q") => Ok(vm.ctx.new_int(*(ptr as *const i64)).into()), - Some("Q") => Ok(vm.ctx.new_int(*(ptr as *const u64)).into()), - Some("f") => Ok(vm.ctx.new_float(*(ptr as *const f32) as f64).into()), - Some("d") | Some("g") => Ok(vm.ctx.new_float(*(ptr as *const f64)).into()), - Some("P") | Some("z") | Some("Z") => { - Ok(vm.ctx.new_int(*(ptr as *const usize)).into()) - } + // Multi-byte types need read_unaligned for safety on strict-alignment architectures + Some("h") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const i16) as i32) + .into()), + Some("H") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const u16) as i32) + .into()), + Some("i") | Some("l") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const i32)) + .into()), + Some("I") | Some("L") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const u32)) + .into()), + Some("q") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const i64)) + .into()), + Some("Q") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const u64)) + .into()), + Some("f") => Ok(vm + .ctx + .new_float(std::ptr::read_unaligned(ptr as *const f32) as f64) + .into()), + Some("d") | Some("g") => Ok(vm + .ctx + .new_float(std::ptr::read_unaligned(ptr as *const f64)) + .into()), + Some("P") | Some("z") | Some("Z") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const usize)) + .into()), _ => { // Default: read as bytes let bytes = std::slice::from_raw_parts(ptr, size).to_vec(); @@ -652,27 +708,37 @@ impl PyCPointer { "bytes/string or integer address expected".to_owned(), )); }; - *(ptr as *mut usize) = ptr_val; + std::ptr::write_unaligned(ptr as *mut usize, ptr_val); return Ok(()); } _ => {} } // Try to get value as integer + // Use write_unaligned for safety on strict-alignment architectures if let Ok(int_val) = value.try_int(vm) { let i = int_val.as_bigint(); match size { 1 => { - *ptr = i.to_u8().unwrap_or(0); + *ptr = i.to_u8().expect("int too large"); } 2 => { - *(ptr as *mut i16) = i.to_i16().unwrap_or(0); + std::ptr::write_unaligned( + ptr as *mut i16, + i.to_i16().expect("int too large"), + ); } 4 => { - *(ptr as *mut i32) = i.to_i32().unwrap_or(0); + std::ptr::write_unaligned( + ptr as *mut i32, + i.to_i32().expect("int too large"), + ); } 8 => { - *(ptr as *mut i64) = i.to_i64().unwrap_or(0); + std::ptr::write_unaligned( + ptr as *mut i64, + i.to_i64().expect("int too large"), + ); } _ => { let bytes = i.to_signed_bytes_le(); @@ -688,10 +754,10 @@ impl PyCPointer { let f = float_val.to_f64(); match size { 4 => { - *(ptr as *mut f32) = f as f32; + std::ptr::write_unaligned(ptr as *mut f32, f as f32); } 8 => { - *(ptr as *mut f64) = f; + std::ptr::write_unaligned(ptr as *mut f64, f); } _ => {} } @@ -712,3 +778,28 @@ impl PyCPointer { } } } + +impl AsBuffer for PyCPointer { + fn as_buffer(zelf: &Py, _vm: &VirtualMachine) -> PyResult { + let stg_info = zelf + .class() + .stg_info_opt() + .expect("PyCPointer type must have StgInfo"); + let format = stg_info + .format + .clone() + .map(Cow::Owned) + .unwrap_or(Cow::Borrowed("&B")); + let itemsize = stg_info.size; + // Pointer types are scalars with ndim=0, shape=() + let desc = BufferDescriptor { + len: itemsize, + readonly: false, + itemsize, + format, + dim_desc: vec![], + }; + let buf = PyBuffer::new(zelf.to_owned().into(), desc, &CDATA_BUFFER_METHODS); + Ok(buf) + } +} diff --git a/crates/vm/src/stdlib/ctypes/simple.rs b/crates/vm/src/stdlib/ctypes/simple.rs index 1c0ec250d7..803b38d6e0 100644 --- a/crates/vm/src/stdlib/ctypes/simple.rs +++ b/crates/vm/src/stdlib/ctypes/simple.rs @@ -14,12 +14,47 @@ use crate::protocol::{BufferDescriptor, PyBuffer, PyNumberMethods}; use crate::types::{AsBuffer, AsNumber, Constructor, Initializer, Representable}; use crate::{AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine}; use num_traits::ToPrimitive; +use std::borrow::Cow; use std::fmt::Debug; /// Valid type codes for ctypes simple types // spell-checker: disable-next-line pub(super) const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQPXOv?g"; +/// Convert ctypes type code to PEP 3118 format code. +/// Some ctypes codes need to be mapped to standard-size codes based on platform. +/// _ctypes_alloc_format_string_for_type +fn ctypes_code_to_pep3118(code: char) -> char { + match code { + // c_int: map based on sizeof(int) + 'i' if std::mem::size_of::() == 2 => 'h', + 'i' if std::mem::size_of::() == 4 => 'i', + 'i' if std::mem::size_of::() == 8 => 'q', + 'I' if std::mem::size_of::() == 2 => 'H', + 'I' if std::mem::size_of::() == 4 => 'I', + 'I' if std::mem::size_of::() == 8 => 'Q', + // c_long: map based on sizeof(long) + 'l' if std::mem::size_of::() == 4 => 'l', + 'l' if std::mem::size_of::() == 8 => 'q', + 'L' if std::mem::size_of::() == 4 => 'L', + 'L' if std::mem::size_of::() == 8 => 'Q', + // c_bool: map based on sizeof(bool) - typically 1 byte on all platforms + '?' if std::mem::size_of::() == 1 => '?', + '?' if std::mem::size_of::() == 2 => 'H', + '?' if std::mem::size_of::() == 4 => 'L', + '?' if std::mem::size_of::() == 8 => 'Q', + // Default: use the same code + _ => code, + } +} + +/// _ctypes_alloc_format_string_for_type +fn alloc_format_string_for_type(code: char, big_endian: bool) -> String { + let prefix = if big_endian { ">" } else { "<" }; + let pep_code = ctypes_code_to_pep3118(code); + format!("{}{}", prefix, pep_code) +} + /// Create a new simple type instance from a class fn new_simple_type( cls: Either<&PyObject, &Py>, @@ -165,6 +200,20 @@ fn set_primitive(_type_: &str, value: &PyObject, vm: &VirtualMachine) -> PyResul } // O_set: py_object accepts any Python object "O" => Ok(value.to_owned()), + // X_set: BSTR - same as Z (c_wchar_p), accepts None, int, or str + "X" => { + if value.is(&vm.ctx.none) + || value.downcast_ref_if_exact::(vm).is_some() + || value.downcast_ref_if_exact::(vm).is_some() + { + Ok(value.to_owned()) + } else { + Err(vm.new_type_error(format!( + "unicode string or integer address expected instead of {} instance", + value.class().name() + ))) + } + } _ => { // "P" if value.downcast_ref_if_exact::(vm).is_some() @@ -313,7 +362,7 @@ impl PyCSimpleType { .to_pyobject(vm)); } // 2. Array/Pointer with c_wchar element type - if is_cwchar_array_or_pointer(&value, vm) { + if is_cwchar_array_or_pointer(&value, vm)? { return Ok(value); } // 3. CArgObject (byref(c_wchar(...))) @@ -521,13 +570,10 @@ impl Initializer for PyCSimpleType { let mut stg_info = StgInfo::new(size, align); // Set format for PEP 3118 buffer protocol - // Format is endian prefix + type code (e.g., "" - }; - stg_info.format = Some(format!("{}{}", endian_prefix, type_str)); + stg_info.format = Some(alloc_format_string_for_type( + type_str.chars().next().unwrap_or('?'), + cfg!(target_endian = "big"), + )); stg_info.paramfunc = super::base::ParamFunc::Simple; // Set TYPEFLAG_ISPOINTER for pointer types: z (c_char_p), Z (c_wchar_p), @@ -619,13 +665,14 @@ fn create_swapped_types( swapped_type.set_attr("_swappedbytes_", vm.ctx.none(), vm)?; // Update swapped type's StgInfo format to use opposite endian prefix - // Native uses '<' on little-endian, '>' on big-endian - // Swapped uses the opposite if let Ok(swapped_type_ref) = swapped_type.clone().downcast::() && let Some(mut sw_stg) = swapped_type_ref.get_type_data_mut::() { - let swapped_prefix = if is_little_endian { ">" } else { "<" }; - sw_stg.format = Some(format!("{}{}", swapped_prefix, type_str)); + // Swapped: little-endian system uses big-endian prefix and vice versa + sw_stg.format = Some(alloc_format_string_for_type( + type_str.chars().next().unwrap_or('?'), + is_little_endian, + )); } // Set attributes based on system byte order @@ -734,63 +781,56 @@ fn value_to_bytes_endian( } "b" => { // c_byte - signed char (1 byte) - // PyLong_AsLongMask pattern: wrapping for overflow values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().unwrap_or(0) as i8; + let v = int_val.as_bigint().to_i128().expect("int too large") as i8; return vec![v as u8]; } vec![0] } "B" => { // c_ubyte - unsigned char (1 byte) - // PyLong_AsUnsignedLongMask: wrapping for negative values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().map(|n| n as u8).unwrap_or(0); + let v = int_val.as_bigint().to_i128().expect("int too large") as u8; return vec![v]; } vec![0] } "h" => { // c_short (2 bytes) - // PyLong_AsLongMask pattern: wrapping for overflow values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().unwrap_or(0) as i16; + let v = int_val.as_bigint().to_i128().expect("int too large") as i16; return to_bytes!(v); } vec![0; 2] } "H" => { // c_ushort (2 bytes) - // PyLong_AsUnsignedLongMask: wrapping for negative values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().map(|n| n as u16).unwrap_or(0); + let v = int_val.as_bigint().to_i128().expect("int too large") as u16; return to_bytes!(v); } vec![0; 2] } "i" => { // c_int (4 bytes) - // PyLong_AsLongMask pattern: wrapping for overflow values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().unwrap_or(0) as i32; + let v = int_val.as_bigint().to_i128().expect("int too large") as i32; return to_bytes!(v); } vec![0; 4] } "I" => { // c_uint (4 bytes) - // PyLong_AsUnsignedLongMask: wrapping for negative values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().map(|n| n as u32).unwrap_or(0); + let v = int_val.as_bigint().to_i128().expect("int too large") as u32; return to_bytes!(v); } vec![0; 4] } "l" => { // c_long (platform dependent) - // PyLong_AsLongMask pattern: wrapping for overflow values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().unwrap_or(0) as libc::c_long; + let v = int_val.as_bigint().to_i128().expect("int too large") as libc::c_long; return to_bytes!(v); } const SIZE: usize = std::mem::size_of::(); @@ -798,13 +838,8 @@ fn value_to_bytes_endian( } "L" => { // c_ulong (platform dependent) - // PyLong_AsUnsignedLongMask: wrapping for negative values if let Ok(int_val) = value.try_index(vm) { - let v = int_val - .as_bigint() - .to_i128() - .map(|n| n as libc::c_ulong) - .unwrap_or(0); + let v = int_val.as_bigint().to_i128().expect("int too large") as libc::c_ulong; return to_bytes!(v); } const SIZE: usize = std::mem::size_of::(); @@ -812,18 +847,16 @@ fn value_to_bytes_endian( } "q" => { // c_longlong (8 bytes) - // PyLong_AsLongMask pattern: wrapping for overflow values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().unwrap_or(0) as i64; + let v = int_val.as_bigint().to_i128().expect("int too large") as i64; return to_bytes!(v); } vec![0; 8] } "Q" => { // c_ulonglong (8 bytes) - // PyLong_AsUnsignedLongLongMask: wrapping for negative values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().map(|n| n as u64).unwrap_or(0); + let v = int_val.as_bigint().to_i128().expect("int too large") as u64; return to_bytes!(v); } vec![0; 8] @@ -899,7 +932,10 @@ fn value_to_bytes_endian( "P" => { // c_void_p - pointer type (platform pointer size) if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_usize().unwrap_or(0); + let v = int_val + .as_bigint() + .to_usize() + .expect("int too large for pointer"); return to_bytes!(v); } vec![0; std::mem::size_of::()] @@ -908,7 +944,10 @@ fn value_to_bytes_endian( // c_char_p - pointer to char (stores pointer value from int) // PyBytes case is handled in slot_new/set_value with make_z_buffer() if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_usize().unwrap_or(0); + let v = int_val + .as_bigint() + .to_usize() + .expect("int too large for pointer"); return to_bytes!(v); } vec![0; std::mem::size_of::()] @@ -917,7 +956,10 @@ fn value_to_bytes_endian( // c_wchar_p - pointer to wchar_t (stores pointer value from int) // PyStr case is handled in slot_new/set_value with make_wchar_buffer() if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_usize().unwrap_or(0); + let v = int_val + .as_bigint() + .to_usize() + .expect("int too large for pointer"); return to_bytes!(v); } vec![0; std::mem::size_of::()] @@ -939,7 +981,7 @@ fn is_cchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> bool { if let Some(arr) = value.downcast_ref::() && let Some(info) = arr.class().stg_info_opt() && let Some(ref elem_type) = info.element_type - && let Some(elem_code) = elem_type.class().type_code(vm) + && let Some(elem_code) = elem_type.type_code(vm) { return elem_code == "c"; } @@ -947,7 +989,7 @@ fn is_cchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> bool { if let Some(ptr) = value.downcast_ref::() && let Some(info) = ptr.class().stg_info_opt() && let Some(ref proto) = info.proto - && let Some(proto_code) = proto.class().type_code(vm) + && let Some(proto_code) = proto.type_code(vm) { return proto_code == "c"; } @@ -955,25 +997,25 @@ fn is_cchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> bool { } /// Check if value is a c_wchar array or pointer(c_wchar) -fn is_cwchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> bool { +fn is_cwchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> PyResult { // Check Array with c_wchar element type if let Some(arr) = value.downcast_ref::() { - let info = arr.class().stg_info_opt().expect("array has StgInfo"); + let info = arr.class().stg_info(vm)?; let elem_type = info.element_type.as_ref().expect("array has element_type"); - if let Some(elem_code) = elem_type.class().type_code(vm) { - return elem_code == "u"; + if let Some(elem_code) = elem_type.type_code(vm) { + return Ok(elem_code == "u"); } } // Check Pointer to c_wchar if let Some(ptr) = value.downcast_ref::() { - let info = ptr.class().stg_info_opt().expect("pointer has StgInfo"); + let info = ptr.class().stg_info(vm)?; if let Some(ref proto) = info.proto - && let Some(proto_code) = proto.class().type_code(vm) + && let Some(proto_code) = proto.type_code(vm) { - return proto_code == "u"; + return Ok(proto_code == "u"); } } - false + Ok(false) } impl Constructor for PyCSimple { @@ -1121,15 +1163,27 @@ impl PyCSimple { return Ok(vm.ctx.none()); } // Read null-terminated wide string at the address + // Windows: wchar_t = u16 (UTF-16) -> use Wtf8Buf::from_wide for surrogate pairs + // Unix: wchar_t = i32 (UTF-32) -> convert via char::from_u32 unsafe { let w_ptr = ptr as *const libc::wchar_t; let len = libc::wcslen(w_ptr); let wchars = std::slice::from_raw_parts(w_ptr, len); - let s: String = wchars - .iter() - .filter_map(|&c| char::from_u32(c as u32)) - .collect(); - return Ok(vm.ctx.new_str(s).into()); + #[cfg(windows)] + { + use rustpython_common::wtf8::Wtf8Buf; + let wide: Vec = wchars.to_vec(); + let wtf8 = Wtf8Buf::from_wide(&wide); + return Ok(vm.ctx.new_str(wtf8).into()); + } + #[cfg(not(windows))] + { + let s: String = wchars + .iter() + .filter_map(|&c| char::from_u32(c as u32)) + .collect(); + return Ok(vm.ctx.new_str(s).into()); + } } } @@ -1349,12 +1403,25 @@ impl PyCSimple { impl AsBuffer for PyCSimple { fn as_buffer(zelf: &Py, _vm: &VirtualMachine) -> PyResult { - let buffer_len = zelf.0.buffer.read().len(); - let buf = PyBuffer::new( - zelf.to_owned().into(), - BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes - &CDATA_BUFFER_METHODS, - ); + let stg_info = zelf + .class() + .stg_info_opt() + .expect("PyCSimple type must have StgInfo"); + let format = stg_info + .format + .clone() + .map(Cow::Owned) + .unwrap_or(Cow::Borrowed("B")); + let itemsize = stg_info.size; + // Simple types are scalars with ndim=0, shape=() + let desc = BufferDescriptor { + len: itemsize, + readonly: false, + itemsize, + format, + dim_desc: vec![], + }; + let buf = PyBuffer::new(zelf.to_owned().into(), desc, &CDATA_BUFFER_METHODS); Ok(buf) } } diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index 10b8812e42..1ca428669d 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -601,12 +601,6 @@ impl PyCStructure { fn _b0_(&self) -> Option { self.0.base.read().clone() } - - #[pygetset] - fn _fields_(&self, vm: &VirtualMachine) -> PyObjectRef { - // Return the _fields_ from the class, not instance - vm.ctx.none() - } } impl AsBuffer for PyCStructure { From 987216bcb8df3b3ce73056d66bee7dff66f6831c Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 16 Dec 2025 20:38:32 +0900 Subject: [PATCH 07/10] enable ctypes --- Lib/ctypes/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index 3599e13ed2..2ef5e9a4fe 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -36,9 +36,6 @@ FUNCFLAG_USE_ERRNO as _FUNCFLAG_USE_ERRNO, \ FUNCFLAG_USE_LASTERROR as _FUNCFLAG_USE_LASTERROR -# TODO: RUSTPYTHON remove this -from _ctypes import _non_existing_function - # WINOLEAPI -> HRESULT # WINOLEAPI_(type) # From 03d6f4634f86a1b9d12e85159bdd14361921c7ed Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 20 Dec 2025 14:31:01 +0900 Subject: [PATCH 08/10] Upgrade ctypes to Python v3.13.11 --- Lib/ctypes/__init__.py | 94 ++- Lib/ctypes/_endian.py | 8 +- Lib/ctypes/test/__init__.py | 16 - Lib/ctypes/test/__main__.py | 4 - Lib/ctypes/test/test_anon.py | 73 -- Lib/ctypes/test/test_array_in_pointer.py | 64 -- Lib/ctypes/test/test_arrays.py | 238 ------ Lib/ctypes/test/test_as_parameter.py | 231 ------ Lib/ctypes/test/test_bitfields.py | 297 ------- Lib/ctypes/test/test_buffers.py | 73 -- Lib/ctypes/test/test_bytes.py | 66 -- Lib/ctypes/test/test_byteswap.py | 364 --------- Lib/ctypes/test/test_callbacks.py | 333 -------- Lib/ctypes/test/test_cast.py | 99 --- Lib/ctypes/test/test_cfuncs.py | 218 ----- Lib/ctypes/test/test_checkretval.py | 36 - Lib/ctypes/test/test_delattr.py | 21 - Lib/ctypes/test/test_errno.py | 76 -- Lib/ctypes/test/test_find.py | 127 --- Lib/ctypes/test/test_frombuffer.py | 141 ---- Lib/ctypes/test/test_funcptr.py | 132 --- Lib/ctypes/test/test_functions.py | 384 --------- Lib/ctypes/test/test_incomplete.py | 42 - Lib/ctypes/test/test_init.py | 40 - Lib/ctypes/test/test_internals.py | 100 --- Lib/ctypes/test/test_keeprefs.py | 153 ---- Lib/ctypes/test/test_libc.py | 33 - Lib/ctypes/test/test_loading.py | 182 ----- Lib/ctypes/test/test_macholib.py | 110 --- Lib/ctypes/test/test_memfunctions.py | 79 -- Lib/ctypes/test/test_numbers.py | 218 ----- Lib/ctypes/test/test_objects.py | 67 -- Lib/ctypes/test/test_parameters.py | 250 ------ Lib/ctypes/test/test_pep3118.py | 235 ------ Lib/ctypes/test/test_pickling.py | 81 -- Lib/ctypes/test/test_pointers.py | 223 ----- Lib/ctypes/test/test_prototypes.py | 222 ----- Lib/ctypes/test/test_python_api.py | 85 -- Lib/ctypes/test/test_random_things.py | 77 -- Lib/ctypes/test/test_refcounts.py | 116 --- Lib/ctypes/test/test_repr.py | 29 - Lib/ctypes/test/test_returnfuncptrs.py | 66 -- Lib/ctypes/test/test_simplesubclasses.py | 55 -- Lib/ctypes/test/test_sizes.py | 33 - Lib/ctypes/test/test_slicing.py | 167 ---- Lib/ctypes/test/test_stringptr.py | 77 -- Lib/ctypes/test/test_strings.py | 145 ---- Lib/ctypes/test/test_struct_fields.py | 97 --- Lib/ctypes/test/test_structures.py | 812 ------------------- Lib/ctypes/test/test_unaligned_structures.py | 43 - Lib/ctypes/test/test_unicode.py | 64 -- Lib/ctypes/test/test_values.py | 103 --- Lib/ctypes/test/test_varsize_struct.py | 50 -- Lib/ctypes/test/test_win32.py | 136 ---- Lib/ctypes/test/test_wintypes.py | 43 - Lib/ctypes/util.py | 18 +- 56 files changed, 70 insertions(+), 7276 deletions(-) delete mode 100644 Lib/ctypes/test/__init__.py delete mode 100644 Lib/ctypes/test/__main__.py delete mode 100644 Lib/ctypes/test/test_anon.py delete mode 100644 Lib/ctypes/test/test_array_in_pointer.py delete mode 100644 Lib/ctypes/test/test_arrays.py delete mode 100644 Lib/ctypes/test/test_as_parameter.py delete mode 100644 Lib/ctypes/test/test_bitfields.py delete mode 100644 Lib/ctypes/test/test_buffers.py delete mode 100644 Lib/ctypes/test/test_bytes.py delete mode 100644 Lib/ctypes/test/test_byteswap.py delete mode 100644 Lib/ctypes/test/test_callbacks.py delete mode 100644 Lib/ctypes/test/test_cast.py delete mode 100644 Lib/ctypes/test/test_cfuncs.py delete mode 100644 Lib/ctypes/test/test_checkretval.py delete mode 100644 Lib/ctypes/test/test_delattr.py delete mode 100644 Lib/ctypes/test/test_errno.py delete mode 100644 Lib/ctypes/test/test_find.py delete mode 100644 Lib/ctypes/test/test_frombuffer.py delete mode 100644 Lib/ctypes/test/test_funcptr.py delete mode 100644 Lib/ctypes/test/test_functions.py delete mode 100644 Lib/ctypes/test/test_incomplete.py delete mode 100644 Lib/ctypes/test/test_init.py delete mode 100644 Lib/ctypes/test/test_internals.py delete mode 100644 Lib/ctypes/test/test_keeprefs.py delete mode 100644 Lib/ctypes/test/test_libc.py delete mode 100644 Lib/ctypes/test/test_loading.py delete mode 100644 Lib/ctypes/test/test_macholib.py delete mode 100644 Lib/ctypes/test/test_memfunctions.py delete mode 100644 Lib/ctypes/test/test_numbers.py delete mode 100644 Lib/ctypes/test/test_objects.py delete mode 100644 Lib/ctypes/test/test_parameters.py delete mode 100644 Lib/ctypes/test/test_pep3118.py delete mode 100644 Lib/ctypes/test/test_pickling.py delete mode 100644 Lib/ctypes/test/test_pointers.py delete mode 100644 Lib/ctypes/test/test_prototypes.py delete mode 100644 Lib/ctypes/test/test_python_api.py delete mode 100644 Lib/ctypes/test/test_random_things.py delete mode 100644 Lib/ctypes/test/test_refcounts.py delete mode 100644 Lib/ctypes/test/test_repr.py delete mode 100644 Lib/ctypes/test/test_returnfuncptrs.py delete mode 100644 Lib/ctypes/test/test_simplesubclasses.py delete mode 100644 Lib/ctypes/test/test_sizes.py delete mode 100644 Lib/ctypes/test/test_slicing.py delete mode 100644 Lib/ctypes/test/test_stringptr.py delete mode 100644 Lib/ctypes/test/test_strings.py delete mode 100644 Lib/ctypes/test/test_struct_fields.py delete mode 100644 Lib/ctypes/test/test_structures.py delete mode 100644 Lib/ctypes/test/test_unaligned_structures.py delete mode 100644 Lib/ctypes/test/test_unicode.py delete mode 100644 Lib/ctypes/test/test_values.py delete mode 100644 Lib/ctypes/test/test_varsize_struct.py delete mode 100644 Lib/ctypes/test/test_win32.py delete mode 100644 Lib/ctypes/test/test_wintypes.py diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index 2ef5e9a4fe..80651dc64c 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -1,6 +1,8 @@ """create and manipulate C data types in Python""" -import os as _os, sys as _sys +import os as _os +import sys as _sys +import sysconfig as _sysconfig import types as _types __version__ = "1.1.0" @@ -107,7 +109,7 @@ class CFunctionType(_CFuncPtr): return CFunctionType if _os.name == "nt": - from _ctypes import LoadLibrary as _dlopen + from _ctypes import LoadLibrary as _LoadLibrary from _ctypes import FUNCFLAG_STDCALL as _FUNCFLAG_STDCALL _win_functype_cache = {} @@ -302,8 +304,9 @@ def create_unicode_buffer(init, size=None): raise TypeError(init) -# XXX Deprecated def SetPointerType(pointer, cls): + import warnings + warnings._deprecated("ctypes.SetPointerType", remove=(3, 15)) if _pointer_type_cache.get(cls, None) is not None: raise RuntimeError("This type already exists in the cache") if id(pointer) not in _pointer_type_cache: @@ -312,7 +315,6 @@ def SetPointerType(pointer, cls): _pointer_type_cache[cls] = pointer del _pointer_type_cache[id(pointer)] -# XXX Deprecated def ARRAY(typ, len): return typ * len @@ -344,52 +346,59 @@ def __init__(self, name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=None): + class _FuncPtr(_CFuncPtr): + _flags_ = self._func_flags_ + _restype_ = self._func_restype_ + if use_errno: + _flags_ |= _FUNCFLAG_USE_ERRNO + if use_last_error: + _flags_ |= _FUNCFLAG_USE_LASTERROR + + self._FuncPtr = _FuncPtr if name: name = _os.fspath(name) + self._handle = self._load_library(name, mode, handle, winmode) + + if _os.name == "nt": + def _load_library(self, name, mode, handle, winmode): + if winmode is None: + import nt as _nt + winmode = _nt._LOAD_LIBRARY_SEARCH_DEFAULT_DIRS + # WINAPI LoadLibrary searches for a DLL if the given name + # is not fully qualified with an explicit drive. For POSIX + # compatibility, and because the DLL search path no longer + # contains the working directory, begin by fully resolving + # any name that contains a path separator. + if name is not None and ('/' in name or '\\' in name): + name = _nt._getfullpathname(name) + winmode |= _nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR + self._name = name + if handle is not None: + return handle + return _LoadLibrary(self._name, winmode) + + else: + def _load_library(self, name, mode, handle, winmode): # If the filename that has been provided is an iOS/tvOS/watchOS # .fwork file, dereference the location to the true origin of the # binary. - if name.endswith(".fwork"): + if name and name.endswith(".fwork"): with open(name) as f: name = _os.path.join( _os.path.dirname(_sys.executable), f.read().strip() ) - - self._name = name - flags = self._func_flags_ - if use_errno: - flags |= _FUNCFLAG_USE_ERRNO - if use_last_error: - flags |= _FUNCFLAG_USE_LASTERROR - if _sys.platform.startswith("aix"): - """When the name contains ".a(" and ends with ")", - e.g., "libFOO.a(libFOO.so)" - this is taken to be an - archive(member) syntax for dlopen(), and the mode is adjusted. - Otherwise, name is presented to dlopen() as a file argument. - """ - if name and name.endswith(")") and ".a(" in name: - mode |= ( _os.RTLD_MEMBER | _os.RTLD_NOW ) - if _os.name == "nt": - if winmode is not None: - mode = winmode - else: - import nt - mode = nt._LOAD_LIBRARY_SEARCH_DEFAULT_DIRS - if '/' in name or '\\' in name: - self._name = nt._getfullpathname(self._name) - mode |= nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR - - class _FuncPtr(_CFuncPtr): - _flags_ = flags - _restype_ = self._func_restype_ - self._FuncPtr = _FuncPtr - - if handle is None: - self._handle = _dlopen(self._name, mode) - else: - self._handle = handle + if _sys.platform.startswith("aix"): + """When the name contains ".a(" and ends with ")", + e.g., "libFOO.a(libFOO.so)" - this is taken to be an + archive(member) syntax for dlopen(), and the mode is adjusted. + Otherwise, name is presented to dlopen() as a file argument. + """ + if name and name.endswith(")") and ".a(" in name: + mode |= _os.RTLD_MEMBER | _os.RTLD_NOW + self._name = name + return _dlopen(name, mode) def __repr__(self): return "<%s '%s', handle %x at %#x>" % \ @@ -477,10 +486,9 @@ def LoadLibrary(self, name): if _os.name == "nt": pythonapi = PyDLL("python dll", None, _sys.dllhandle) -elif _sys.platform == "android": - pythonapi = PyDLL("libpython%d.%d.so" % _sys.version_info[:2]) -elif _sys.platform == "cygwin": - pythonapi = PyDLL("libpython%d.%d.dll" % _sys.version_info[:2]) +elif _sys.platform in ["android", "cygwin"]: + # These are Unix-like platforms which use a dynamically-linked libpython. + pythonapi = PyDLL(_sysconfig.get_config_var("LDLIBRARY")) else: pythonapi = PyDLL(None) diff --git a/Lib/ctypes/_endian.py b/Lib/ctypes/_endian.py index 34dee64b1a..6382dd22b8 100644 --- a/Lib/ctypes/_endian.py +++ b/Lib/ctypes/_endian.py @@ -1,5 +1,5 @@ import sys -from ctypes import * +from ctypes import Array, Structure, Union _array_type = type(Array) @@ -15,8 +15,8 @@ def _other_endian(typ): # if typ is array if isinstance(typ, _array_type): return _other_endian(typ._type_) * typ._length_ - # if typ is structure - if issubclass(typ, Structure): + # if typ is structure or union + if issubclass(typ, (Structure, Union)): return typ raise TypeError("This type does not support other endian: %s" % typ) @@ -37,7 +37,7 @@ class _swapped_union_meta(_swapped_meta, type(Union)): pass ################################################################ # Note: The Structure metaclass checks for the *presence* (not the -# value!) of a _swapped_bytes_ attribute to determine the bit order in +# value!) of a _swappedbytes_ attribute to determine the bit order in # structures containing bit fields. if sys.byteorder == "little": diff --git a/Lib/ctypes/test/__init__.py b/Lib/ctypes/test/__init__.py deleted file mode 100644 index 6e496fa5a5..0000000000 --- a/Lib/ctypes/test/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -import os -import unittest -from test import support -from test.support import import_helper - - -# skip tests if _ctypes was not built -ctypes = import_helper.import_module('ctypes') -ctypes_symbols = dir(ctypes) - -def need_symbol(name): - return unittest.skipUnless(name in ctypes_symbols, - '{!r} is required'.format(name)) - -def load_tests(*args): - return support.load_package_tests(os.path.dirname(__file__), *args) diff --git a/Lib/ctypes/test/__main__.py b/Lib/ctypes/test/__main__.py deleted file mode 100644 index 362a9ec8cf..0000000000 --- a/Lib/ctypes/test/__main__.py +++ /dev/null @@ -1,4 +0,0 @@ -from ctypes.test import load_tests -import unittest - -unittest.main() diff --git a/Lib/ctypes/test/test_anon.py b/Lib/ctypes/test/test_anon.py deleted file mode 100644 index d378392ebe..0000000000 --- a/Lib/ctypes/test/test_anon.py +++ /dev/null @@ -1,73 +0,0 @@ -import unittest -import test.support -from ctypes import * - -class AnonTest(unittest.TestCase): - - def test_anon(self): - class ANON(Union): - _fields_ = [("a", c_int), - ("b", c_int)] - - class Y(Structure): - _fields_ = [("x", c_int), - ("_", ANON), - ("y", c_int)] - _anonymous_ = ["_"] - - self.assertEqual(Y.a.offset, sizeof(c_int)) - self.assertEqual(Y.b.offset, sizeof(c_int)) - - self.assertEqual(ANON.a.offset, 0) - self.assertEqual(ANON.b.offset, 0) - - def test_anon_nonseq(self): - # TypeError: _anonymous_ must be a sequence - self.assertRaises(TypeError, - lambda: type(Structure)("Name", - (Structure,), - {"_fields_": [], "_anonymous_": 42})) - - def test_anon_nonmember(self): - # AttributeError: type object 'Name' has no attribute 'x' - self.assertRaises(AttributeError, - lambda: type(Structure)("Name", - (Structure,), - {"_fields_": [], - "_anonymous_": ["x"]})) - - @test.support.cpython_only - def test_issue31490(self): - # There shouldn't be an assertion failure in case the class has an - # attribute whose name is specified in _anonymous_ but not in _fields_. - - # AttributeError: 'x' is specified in _anonymous_ but not in _fields_ - with self.assertRaises(AttributeError): - class Name(Structure): - _fields_ = [] - _anonymous_ = ["x"] - x = 42 - - def test_nested(self): - class ANON_S(Structure): - _fields_ = [("a", c_int)] - - class ANON_U(Union): - _fields_ = [("_", ANON_S), - ("b", c_int)] - _anonymous_ = ["_"] - - class Y(Structure): - _fields_ = [("x", c_int), - ("_", ANON_U), - ("y", c_int)] - _anonymous_ = ["_"] - - self.assertEqual(Y.x.offset, 0) - self.assertEqual(Y.a.offset, sizeof(c_int)) - self.assertEqual(Y.b.offset, sizeof(c_int)) - self.assertEqual(Y._.offset, sizeof(c_int)) - self.assertEqual(Y.y.offset, sizeof(c_int) * 2) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_array_in_pointer.py b/Lib/ctypes/test/test_array_in_pointer.py deleted file mode 100644 index ca1edcf621..0000000000 --- a/Lib/ctypes/test/test_array_in_pointer.py +++ /dev/null @@ -1,64 +0,0 @@ -import unittest -from ctypes import * -from binascii import hexlify -import re - -def dump(obj): - # helper function to dump memory contents in hex, with a hyphen - # between the bytes. - h = hexlify(memoryview(obj)).decode() - return re.sub(r"(..)", r"\1-", h)[:-1] - - -class Value(Structure): - _fields_ = [("val", c_byte)] - -class Container(Structure): - _fields_ = [("pvalues", POINTER(Value))] - -class Test(unittest.TestCase): - def test(self): - # create an array of 4 values - val_array = (Value * 4)() - - # create a container, which holds a pointer to the pvalues array. - c = Container() - c.pvalues = val_array - - # memory contains 4 NUL bytes now, that's correct - self.assertEqual("00-00-00-00", dump(val_array)) - - # set the values of the array through the pointer: - for i in range(4): - c.pvalues[i].val = i + 1 - - values = [c.pvalues[i].val for i in range(4)] - - # These are the expected results: here s the bug! - self.assertEqual( - (values, dump(val_array)), - ([1, 2, 3, 4], "01-02-03-04") - ) - - def test_2(self): - - val_array = (Value * 4)() - - # memory contains 4 NUL bytes now, that's correct - self.assertEqual("00-00-00-00", dump(val_array)) - - ptr = cast(val_array, POINTER(Value)) - # set the values of the array through the pointer: - for i in range(4): - ptr[i].val = i + 1 - - values = [ptr[i].val for i in range(4)] - - # These are the expected results: here s the bug! - self.assertEqual( - (values, dump(val_array)), - ([1, 2, 3, 4], "01-02-03-04") - ) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_arrays.py b/Lib/ctypes/test/test_arrays.py deleted file mode 100644 index 14603b7049..0000000000 --- a/Lib/ctypes/test/test_arrays.py +++ /dev/null @@ -1,238 +0,0 @@ -import unittest -from test.support import bigmemtest, _2G -import sys -from ctypes import * - -from ctypes.test import need_symbol - -formats = "bBhHiIlLqQfd" - -formats = c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint, \ - c_long, c_ulonglong, c_float, c_double, c_longdouble - -class ArrayTestCase(unittest.TestCase): - def test_simple(self): - # create classes holding simple numeric types, and check - # various properties. - - init = list(range(15, 25)) - - for fmt in formats: - alen = len(init) - int_array = ARRAY(fmt, alen) - - ia = int_array(*init) - # length of instance ok? - self.assertEqual(len(ia), alen) - - # slot values ok? - values = [ia[i] for i in range(alen)] - self.assertEqual(values, init) - - # out-of-bounds accesses should be caught - with self.assertRaises(IndexError): ia[alen] - with self.assertRaises(IndexError): ia[-alen-1] - - # change the items - from operator import setitem - new_values = list(range(42, 42+alen)) - [setitem(ia, n, new_values[n]) for n in range(alen)] - values = [ia[i] for i in range(alen)] - self.assertEqual(values, new_values) - - # are the items initialized to 0? - ia = int_array() - values = [ia[i] for i in range(alen)] - self.assertEqual(values, [0] * alen) - - # Too many initializers should be caught - self.assertRaises(IndexError, int_array, *range(alen*2)) - - CharArray = ARRAY(c_char, 3) - - ca = CharArray(b"a", b"b", b"c") - - # Should this work? It doesn't: - # CharArray("abc") - self.assertRaises(TypeError, CharArray, "abc") - - self.assertEqual(ca[0], b"a") - self.assertEqual(ca[1], b"b") - self.assertEqual(ca[2], b"c") - self.assertEqual(ca[-3], b"a") - self.assertEqual(ca[-2], b"b") - self.assertEqual(ca[-1], b"c") - - self.assertEqual(len(ca), 3) - - # cannot delete items - from operator import delitem - self.assertRaises(TypeError, delitem, ca, 0) - - def test_step_overflow(self): - a = (c_int * 5)() - a[3::sys.maxsize] = (1,) - self.assertListEqual(a[3::sys.maxsize], [1]) - a = (c_char * 5)() - a[3::sys.maxsize] = b"A" - self.assertEqual(a[3::sys.maxsize], b"A") - a = (c_wchar * 5)() - a[3::sys.maxsize] = u"X" - self.assertEqual(a[3::sys.maxsize], u"X") - - def test_numeric_arrays(self): - - alen = 5 - - numarray = ARRAY(c_int, alen) - - na = numarray() - values = [na[i] for i in range(alen)] - self.assertEqual(values, [0] * alen) - - na = numarray(*[c_int()] * alen) - values = [na[i] for i in range(alen)] - self.assertEqual(values, [0]*alen) - - na = numarray(1, 2, 3, 4, 5) - values = [i for i in na] - self.assertEqual(values, [1, 2, 3, 4, 5]) - - na = numarray(*map(c_int, (1, 2, 3, 4, 5))) - values = [i for i in na] - self.assertEqual(values, [1, 2, 3, 4, 5]) - - def test_classcache(self): - self.assertIsNot(ARRAY(c_int, 3), ARRAY(c_int, 4)) - self.assertIs(ARRAY(c_int, 3), ARRAY(c_int, 3)) - - def test_from_address(self): - # Failed with 0.9.8, reported by JUrner - p = create_string_buffer(b"foo") - sz = (c_char * 3).from_address(addressof(p)) - self.assertEqual(sz[:], b"foo") - self.assertEqual(sz[::], b"foo") - self.assertEqual(sz[::-1], b"oof") - self.assertEqual(sz[::3], b"f") - self.assertEqual(sz[1:4:2], b"o") - self.assertEqual(sz.value, b"foo") - - @need_symbol('create_unicode_buffer') - def test_from_addressW(self): - p = create_unicode_buffer("foo") - sz = (c_wchar * 3).from_address(addressof(p)) - self.assertEqual(sz[:], "foo") - self.assertEqual(sz[::], "foo") - self.assertEqual(sz[::-1], "oof") - self.assertEqual(sz[::3], "f") - self.assertEqual(sz[1:4:2], "o") - self.assertEqual(sz.value, "foo") - - def test_cache(self): - # Array types are cached internally in the _ctypes extension, - # in a WeakValueDictionary. Make sure the array type is - # removed from the cache when the itemtype goes away. This - # test will not fail, but will show a leak in the testsuite. - - # Create a new type: - class my_int(c_int): - pass - # Create a new array type based on it: - t1 = my_int * 1 - t2 = my_int * 1 - self.assertIs(t1, t2) - - def test_subclass(self): - class T(Array): - _type_ = c_int - _length_ = 13 - class U(T): - pass - class V(U): - pass - class W(V): - pass - class X(T): - _type_ = c_short - class Y(T): - _length_ = 187 - - for c in [T, U, V, W]: - self.assertEqual(c._type_, c_int) - self.assertEqual(c._length_, 13) - self.assertEqual(c()._type_, c_int) - self.assertEqual(c()._length_, 13) - - self.assertEqual(X._type_, c_short) - self.assertEqual(X._length_, 13) - self.assertEqual(X()._type_, c_short) - self.assertEqual(X()._length_, 13) - - self.assertEqual(Y._type_, c_int) - self.assertEqual(Y._length_, 187) - self.assertEqual(Y()._type_, c_int) - self.assertEqual(Y()._length_, 187) - - def test_bad_subclass(self): - with self.assertRaises(AttributeError): - class T(Array): - pass - with self.assertRaises(AttributeError): - class T(Array): - _type_ = c_int - with self.assertRaises(AttributeError): - class T(Array): - _length_ = 13 - - def test_bad_length(self): - with self.assertRaises(ValueError): - class T(Array): - _type_ = c_int - _length_ = - sys.maxsize * 2 - with self.assertRaises(ValueError): - class T(Array): - _type_ = c_int - _length_ = -1 - with self.assertRaises(TypeError): - class T(Array): - _type_ = c_int - _length_ = 1.87 - with self.assertRaises(OverflowError): - class T(Array): - _type_ = c_int - _length_ = sys.maxsize * 2 - - def test_zero_length(self): - # _length_ can be zero. - class T(Array): - _type_ = c_int - _length_ = 0 - - def test_empty_element_struct(self): - class EmptyStruct(Structure): - _fields_ = [] - - obj = (EmptyStruct * 2)() # bpo37188: Floating point exception - self.assertEqual(sizeof(obj), 0) - - def test_empty_element_array(self): - class EmptyArray(Array): - _type_ = c_int - _length_ = 0 - - obj = (EmptyArray * 2)() # bpo37188: Floating point exception - self.assertEqual(sizeof(obj), 0) - - def test_bpo36504_signed_int_overflow(self): - # The overflow check in PyCArrayType_new() could cause signed integer - # overflow. - with self.assertRaises(OverflowError): - c_char * sys.maxsize * 2 - - @unittest.skipUnless(sys.maxsize > 2**32, 'requires 64bit platform') - @bigmemtest(size=_2G, memuse=1, dry_run=False) - def test_large_array(self, size): - c_char * size - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_as_parameter.py b/Lib/ctypes/test/test_as_parameter.py deleted file mode 100644 index 9c39179d2a..0000000000 --- a/Lib/ctypes/test/test_as_parameter.py +++ /dev/null @@ -1,231 +0,0 @@ -import unittest -from ctypes import * -from ctypes.test import need_symbol -import _ctypes_test - -dll = CDLL(_ctypes_test.__file__) - -try: - CALLBACK_FUNCTYPE = WINFUNCTYPE -except NameError: - # fake to enable this test on Linux - CALLBACK_FUNCTYPE = CFUNCTYPE - -class POINT(Structure): - _fields_ = [("x", c_int), ("y", c_int)] - -class BasicWrapTestCase(unittest.TestCase): - def wrap(self, param): - return param - - @need_symbol('c_wchar') - def test_wchar_parm(self): - f = dll._testfunc_i_bhilfd - f.argtypes = [c_byte, c_wchar, c_int, c_long, c_float, c_double] - result = f(self.wrap(1), self.wrap("x"), self.wrap(3), self.wrap(4), self.wrap(5.0), self.wrap(6.0)) - self.assertEqual(result, 139) - self.assertIs(type(result), int) - - def test_pointers(self): - f = dll._testfunc_p_p - f.restype = POINTER(c_int) - f.argtypes = [POINTER(c_int)] - - # This only works if the value c_int(42) passed to the - # function is still alive while the pointer (the result) is - # used. - - v = c_int(42) - - self.assertEqual(pointer(v).contents.value, 42) - result = f(self.wrap(pointer(v))) - self.assertEqual(type(result), POINTER(c_int)) - self.assertEqual(result.contents.value, 42) - - # This on works... - result = f(self.wrap(pointer(v))) - self.assertEqual(result.contents.value, v.value) - - p = pointer(c_int(99)) - result = f(self.wrap(p)) - self.assertEqual(result.contents.value, 99) - - def test_shorts(self): - f = dll._testfunc_callback_i_if - - args = [] - expected = [262144, 131072, 65536, 32768, 16384, 8192, 4096, 2048, - 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1] - - def callback(v): - args.append(v) - return v - - CallBack = CFUNCTYPE(c_int, c_int) - - cb = CallBack(callback) - f(self.wrap(2**18), self.wrap(cb)) - self.assertEqual(args, expected) - - ################################################################ - - def test_callbacks(self): - f = dll._testfunc_callback_i_if - f.restype = c_int - f.argtypes = None - - MyCallback = CFUNCTYPE(c_int, c_int) - - def callback(value): - #print "called back with", value - return value - - cb = MyCallback(callback) - - result = f(self.wrap(-10), self.wrap(cb)) - self.assertEqual(result, -18) - - # test with prototype - f.argtypes = [c_int, MyCallback] - cb = MyCallback(callback) - - result = f(self.wrap(-10), self.wrap(cb)) - self.assertEqual(result, -18) - - result = f(self.wrap(-10), self.wrap(cb)) - self.assertEqual(result, -18) - - AnotherCallback = CALLBACK_FUNCTYPE(c_int, c_int, c_int, c_int, c_int) - - # check that the prototype works: we call f with wrong - # argument types - cb = AnotherCallback(callback) - self.assertRaises(ArgumentError, f, self.wrap(-10), self.wrap(cb)) - - def test_callbacks_2(self): - # Can also use simple datatypes as argument type specifiers - # for the callback function. - # In this case the call receives an instance of that type - f = dll._testfunc_callback_i_if - f.restype = c_int - - MyCallback = CFUNCTYPE(c_int, c_int) - - f.argtypes = [c_int, MyCallback] - - def callback(value): - #print "called back with", value - self.assertEqual(type(value), int) - return value - - cb = MyCallback(callback) - result = f(self.wrap(-10), self.wrap(cb)) - self.assertEqual(result, -18) - - @need_symbol('c_longlong') - def test_longlong_callbacks(self): - - f = dll._testfunc_callback_q_qf - f.restype = c_longlong - - MyCallback = CFUNCTYPE(c_longlong, c_longlong) - - f.argtypes = [c_longlong, MyCallback] - - def callback(value): - self.assertIsInstance(value, int) - return value & 0x7FFFFFFF - - cb = MyCallback(callback) - - self.assertEqual(13577625587, int(f(self.wrap(1000000000000), self.wrap(cb)))) - - def test_byval(self): - # without prototype - ptin = POINT(1, 2) - ptout = POINT() - # EXPORT int _testfunc_byval(point in, point *pout) - result = dll._testfunc_byval(ptin, byref(ptout)) - got = result, ptout.x, ptout.y - expected = 3, 1, 2 - self.assertEqual(got, expected) - - # with prototype - ptin = POINT(101, 102) - ptout = POINT() - dll._testfunc_byval.argtypes = (POINT, POINTER(POINT)) - dll._testfunc_byval.restype = c_int - result = dll._testfunc_byval(self.wrap(ptin), byref(ptout)) - got = result, ptout.x, ptout.y - expected = 203, 101, 102 - self.assertEqual(got, expected) - - def test_struct_return_2H(self): - class S2H(Structure): - _fields_ = [("x", c_short), - ("y", c_short)] - dll.ret_2h_func.restype = S2H - dll.ret_2h_func.argtypes = [S2H] - inp = S2H(99, 88) - s2h = dll.ret_2h_func(self.wrap(inp)) - self.assertEqual((s2h.x, s2h.y), (99*2, 88*3)) - - # Test also that the original struct was unmodified (i.e. was passed by - # value) - self.assertEqual((inp.x, inp.y), (99, 88)) - - def test_struct_return_8H(self): - class S8I(Structure): - _fields_ = [("a", c_int), - ("b", c_int), - ("c", c_int), - ("d", c_int), - ("e", c_int), - ("f", c_int), - ("g", c_int), - ("h", c_int)] - dll.ret_8i_func.restype = S8I - dll.ret_8i_func.argtypes = [S8I] - inp = S8I(9, 8, 7, 6, 5, 4, 3, 2) - s8i = dll.ret_8i_func(self.wrap(inp)) - self.assertEqual((s8i.a, s8i.b, s8i.c, s8i.d, s8i.e, s8i.f, s8i.g, s8i.h), - (9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9)) - - def test_recursive_as_param(self): - from ctypes import c_int - - class A(object): - pass - - a = A() - a._as_parameter_ = a - with self.assertRaises(RecursionError): - c_int.from_param(a) - - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class AsParamWrapper(object): - def __init__(self, param): - self._as_parameter_ = param - -class AsParamWrapperTestCase(BasicWrapTestCase): - wrap = AsParamWrapper - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class AsParamPropertyWrapper(object): - def __init__(self, param): - self._param = param - - def getParameter(self): - return self._param - _as_parameter_ = property(getParameter) - -class AsParamPropertyWrapperTestCase(BasicWrapTestCase): - wrap = AsParamPropertyWrapper - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_bitfields.py b/Lib/ctypes/test/test_bitfields.py deleted file mode 100644 index 66acd62e68..0000000000 --- a/Lib/ctypes/test/test_bitfields.py +++ /dev/null @@ -1,297 +0,0 @@ -from ctypes import * -from ctypes.test import need_symbol -from test import support -import unittest -import os - -import _ctypes_test - -class BITS(Structure): - _fields_ = [("A", c_int, 1), - ("B", c_int, 2), - ("C", c_int, 3), - ("D", c_int, 4), - ("E", c_int, 5), - ("F", c_int, 6), - ("G", c_int, 7), - ("H", c_int, 8), - ("I", c_int, 9), - - ("M", c_short, 1), - ("N", c_short, 2), - ("O", c_short, 3), - ("P", c_short, 4), - ("Q", c_short, 5), - ("R", c_short, 6), - ("S", c_short, 7)] - -func = CDLL(_ctypes_test.__file__).unpack_bitfields -func.argtypes = POINTER(BITS), c_char - -##for n in "ABCDEFGHIMNOPQRS": -## print n, hex(getattr(BITS, n).size), getattr(BITS, n).offset - -class C_Test(unittest.TestCase): - - def test_ints(self): - for i in range(512): - for name in "ABCDEFGHI": - b = BITS() - setattr(b, name, i) - self.assertEqual(getattr(b, name), func(byref(b), name.encode('ascii'))) - - # bpo-46913: _ctypes/cfield.c h_get() has an undefined behavior - @support.skip_if_sanitizer(ub=True) - def test_shorts(self): - b = BITS() - name = "M" - if func(byref(b), name.encode('ascii')) == 999: - self.skipTest("Compiler does not support signed short bitfields") - for i in range(256): - for name in "MNOPQRS": - b = BITS() - setattr(b, name, i) - self.assertEqual(getattr(b, name), func(byref(b), name.encode('ascii'))) - -signed_int_types = (c_byte, c_short, c_int, c_long, c_longlong) -unsigned_int_types = (c_ubyte, c_ushort, c_uint, c_ulong, c_ulonglong) -int_types = unsigned_int_types + signed_int_types - -class BitFieldTest(unittest.TestCase): - - def test_longlong(self): - class X(Structure): - _fields_ = [("a", c_longlong, 1), - ("b", c_longlong, 62), - ("c", c_longlong, 1)] - - self.assertEqual(sizeof(X), sizeof(c_longlong)) - x = X() - x.a, x.b, x.c = -1, 7, -1 - self.assertEqual((x.a, x.b, x.c), (-1, 7, -1)) - - def test_ulonglong(self): - class X(Structure): - _fields_ = [("a", c_ulonglong, 1), - ("b", c_ulonglong, 62), - ("c", c_ulonglong, 1)] - - self.assertEqual(sizeof(X), sizeof(c_longlong)) - x = X() - self.assertEqual((x.a, x.b, x.c), (0, 0, 0)) - x.a, x.b, x.c = 7, 7, 7 - self.assertEqual((x.a, x.b, x.c), (1, 7, 1)) - - def test_signed(self): - for c_typ in signed_int_types: - class X(Structure): - _fields_ = [("dummy", c_typ), - ("a", c_typ, 3), - ("b", c_typ, 3), - ("c", c_typ, 1)] - self.assertEqual(sizeof(X), sizeof(c_typ)*2) - - x = X() - self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 0, 0)) - x.a = -1 - self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, -1, 0, 0)) - x.a, x.b = 0, -1 - self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, -1, 0)) - - - def test_unsigned(self): - for c_typ in unsigned_int_types: - class X(Structure): - _fields_ = [("a", c_typ, 3), - ("b", c_typ, 3), - ("c", c_typ, 1)] - self.assertEqual(sizeof(X), sizeof(c_typ)) - - x = X() - self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 0, 0)) - x.a = -1 - self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 7, 0, 0)) - x.a, x.b = 0, -1 - self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 7, 0)) - - - def fail_fields(self, *fields): - return self.get_except(type(Structure), "X", (), - {"_fields_": fields}) - - def test_nonint_types(self): - # bit fields are not allowed on non-integer types. - result = self.fail_fields(("a", c_char_p, 1)) - self.assertEqual(result, (TypeError, 'bit fields not allowed for type c_char_p')) - - result = self.fail_fields(("a", c_void_p, 1)) - self.assertEqual(result, (TypeError, 'bit fields not allowed for type c_void_p')) - - if c_int != c_long: - result = self.fail_fields(("a", POINTER(c_int), 1)) - self.assertEqual(result, (TypeError, 'bit fields not allowed for type LP_c_int')) - - result = self.fail_fields(("a", c_char, 1)) - self.assertEqual(result, (TypeError, 'bit fields not allowed for type c_char')) - - class Dummy(Structure): - _fields_ = [] - - result = self.fail_fields(("a", Dummy, 1)) - self.assertEqual(result, (TypeError, 'bit fields not allowed for type Dummy')) - - @need_symbol('c_wchar') - def test_c_wchar(self): - result = self.fail_fields(("a", c_wchar, 1)) - self.assertEqual(result, - (TypeError, 'bit fields not allowed for type c_wchar')) - - def test_single_bitfield_size(self): - for c_typ in int_types: - result = self.fail_fields(("a", c_typ, -1)) - self.assertEqual(result, (ValueError, 'number of bits invalid for bit field')) - - result = self.fail_fields(("a", c_typ, 0)) - self.assertEqual(result, (ValueError, 'number of bits invalid for bit field')) - - class X(Structure): - _fields_ = [("a", c_typ, 1)] - self.assertEqual(sizeof(X), sizeof(c_typ)) - - class X(Structure): - _fields_ = [("a", c_typ, sizeof(c_typ)*8)] - self.assertEqual(sizeof(X), sizeof(c_typ)) - - result = self.fail_fields(("a", c_typ, sizeof(c_typ)*8 + 1)) - self.assertEqual(result, (ValueError, 'number of bits invalid for bit field')) - - def test_multi_bitfields_size(self): - class X(Structure): - _fields_ = [("a", c_short, 1), - ("b", c_short, 14), - ("c", c_short, 1)] - self.assertEqual(sizeof(X), sizeof(c_short)) - - class X(Structure): - _fields_ = [("a", c_short, 1), - ("a1", c_short), - ("b", c_short, 14), - ("c", c_short, 1)] - self.assertEqual(sizeof(X), sizeof(c_short)*3) - self.assertEqual(X.a.offset, 0) - self.assertEqual(X.a1.offset, sizeof(c_short)) - self.assertEqual(X.b.offset, sizeof(c_short)*2) - self.assertEqual(X.c.offset, sizeof(c_short)*2) - - class X(Structure): - _fields_ = [("a", c_short, 3), - ("b", c_short, 14), - ("c", c_short, 14)] - self.assertEqual(sizeof(X), sizeof(c_short)*3) - self.assertEqual(X.a.offset, sizeof(c_short)*0) - self.assertEqual(X.b.offset, sizeof(c_short)*1) - self.assertEqual(X.c.offset, sizeof(c_short)*2) - - - def get_except(self, func, *args, **kw): - try: - func(*args, **kw) - except Exception as detail: - return detail.__class__, str(detail) - - def test_mixed_1(self): - class X(Structure): - _fields_ = [("a", c_byte, 4), - ("b", c_int, 4)] - if os.name == "nt": - self.assertEqual(sizeof(X), sizeof(c_int)*2) - else: - self.assertEqual(sizeof(X), sizeof(c_int)) - - def test_mixed_2(self): - class X(Structure): - _fields_ = [("a", c_byte, 4), - ("b", c_int, 32)] - self.assertEqual(sizeof(X), alignment(c_int)+sizeof(c_int)) - - def test_mixed_3(self): - class X(Structure): - _fields_ = [("a", c_byte, 4), - ("b", c_ubyte, 4)] - self.assertEqual(sizeof(X), sizeof(c_byte)) - - def test_mixed_4(self): - class X(Structure): - _fields_ = [("a", c_short, 4), - ("b", c_short, 4), - ("c", c_int, 24), - ("d", c_short, 4), - ("e", c_short, 4), - ("f", c_int, 24)] - # MSVC does NOT combine c_short and c_int into one field, GCC - # does (unless GCC is run with '-mms-bitfields' which - # produces code compatible with MSVC). - if os.name == "nt": - self.assertEqual(sizeof(X), sizeof(c_int) * 4) - else: - self.assertEqual(sizeof(X), sizeof(c_int) * 2) - - def test_anon_bitfields(self): - # anonymous bit-fields gave a strange error message - class X(Structure): - _fields_ = [("a", c_byte, 4), - ("b", c_ubyte, 4)] - class Y(Structure): - _anonymous_ = ["_"] - _fields_ = [("_", X)] - - @need_symbol('c_uint32') - def test_uint32(self): - class X(Structure): - _fields_ = [("a", c_uint32, 32)] - x = X() - x.a = 10 - self.assertEqual(x.a, 10) - x.a = 0xFDCBA987 - self.assertEqual(x.a, 0xFDCBA987) - - @need_symbol('c_uint64') - def test_uint64(self): - class X(Structure): - _fields_ = [("a", c_uint64, 64)] - x = X() - x.a = 10 - self.assertEqual(x.a, 10) - x.a = 0xFEDCBA9876543211 - self.assertEqual(x.a, 0xFEDCBA9876543211) - - @need_symbol('c_uint32') - def test_uint32_swap_little_endian(self): - # Issue #23319 - class Little(LittleEndianStructure): - _fields_ = [("a", c_uint32, 24), - ("b", c_uint32, 4), - ("c", c_uint32, 4)] - b = bytearray(4) - x = Little.from_buffer(b) - x.a = 0xabcdef - x.b = 1 - x.c = 2 - self.assertEqual(b, b'\xef\xcd\xab\x21') - - @need_symbol('c_uint32') - def test_uint32_swap_big_endian(self): - # Issue #23319 - class Big(BigEndianStructure): - _fields_ = [("a", c_uint32, 24), - ("b", c_uint32, 4), - ("c", c_uint32, 4)] - b = bytearray(4) - x = Big.from_buffer(b) - x.a = 0xabcdef - x.b = 1 - x.c = 2 - self.assertEqual(b, b'\xab\xcd\xef\x12') - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_buffers.py b/Lib/ctypes/test/test_buffers.py deleted file mode 100644 index 15782be757..0000000000 --- a/Lib/ctypes/test/test_buffers.py +++ /dev/null @@ -1,73 +0,0 @@ -from ctypes import * -from ctypes.test import need_symbol -import unittest - -class StringBufferTestCase(unittest.TestCase): - - def test_buffer(self): - b = create_string_buffer(32) - self.assertEqual(len(b), 32) - self.assertEqual(sizeof(b), 32 * sizeof(c_char)) - self.assertIs(type(b[0]), bytes) - - b = create_string_buffer(b"abc") - self.assertEqual(len(b), 4) # trailing nul char - self.assertEqual(sizeof(b), 4 * sizeof(c_char)) - self.assertIs(type(b[0]), bytes) - self.assertEqual(b[0], b"a") - self.assertEqual(b[:], b"abc\0") - self.assertEqual(b[::], b"abc\0") - self.assertEqual(b[::-1], b"\0cba") - self.assertEqual(b[::2], b"ac") - self.assertEqual(b[::5], b"a") - - self.assertRaises(TypeError, create_string_buffer, "abc") - - def test_buffer_interface(self): - self.assertEqual(len(bytearray(create_string_buffer(0))), 0) - self.assertEqual(len(bytearray(create_string_buffer(1))), 1) - - @need_symbol('c_wchar') - def test_unicode_buffer(self): - b = create_unicode_buffer(32) - self.assertEqual(len(b), 32) - self.assertEqual(sizeof(b), 32 * sizeof(c_wchar)) - self.assertIs(type(b[0]), str) - - b = create_unicode_buffer("abc") - self.assertEqual(len(b), 4) # trailing nul char - self.assertEqual(sizeof(b), 4 * sizeof(c_wchar)) - self.assertIs(type(b[0]), str) - self.assertEqual(b[0], "a") - self.assertEqual(b[:], "abc\0") - self.assertEqual(b[::], "abc\0") - self.assertEqual(b[::-1], "\0cba") - self.assertEqual(b[::2], "ac") - self.assertEqual(b[::5], "a") - - self.assertRaises(TypeError, create_unicode_buffer, b"abc") - - @need_symbol('c_wchar') - def test_unicode_conversion(self): - b = create_unicode_buffer("abc") - self.assertEqual(len(b), 4) # trailing nul char - self.assertEqual(sizeof(b), 4 * sizeof(c_wchar)) - self.assertIs(type(b[0]), str) - self.assertEqual(b[0], "a") - self.assertEqual(b[:], "abc\0") - self.assertEqual(b[::], "abc\0") - self.assertEqual(b[::-1], "\0cba") - self.assertEqual(b[::2], "ac") - self.assertEqual(b[::5], "a") - - @need_symbol('c_wchar') - def test_create_unicode_buffer_non_bmp(self): - expected = 5 if sizeof(c_wchar) == 2 else 3 - for s in '\U00010000\U00100000', '\U00010000\U0010ffff': - b = create_unicode_buffer(s) - self.assertEqual(len(b), expected) - self.assertEqual(b[-1], '\0') - - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_bytes.py b/Lib/ctypes/test/test_bytes.py deleted file mode 100644 index 092ec5af05..0000000000 --- a/Lib/ctypes/test/test_bytes.py +++ /dev/null @@ -1,66 +0,0 @@ -"""Test where byte objects are accepted""" -import unittest -import sys -from ctypes import * - -class BytesTest(unittest.TestCase): - def test_c_char(self): - x = c_char(b"x") - self.assertRaises(TypeError, c_char, "x") - x.value = b"y" - with self.assertRaises(TypeError): - x.value = "y" - c_char.from_param(b"x") - self.assertRaises(TypeError, c_char.from_param, "x") - self.assertIn('xbd', repr(c_char.from_param(b"\xbd"))) - (c_char * 3)(b"a", b"b", b"c") - self.assertRaises(TypeError, c_char * 3, "a", "b", "c") - - def test_c_wchar(self): - x = c_wchar("x") - self.assertRaises(TypeError, c_wchar, b"x") - x.value = "y" - with self.assertRaises(TypeError): - x.value = b"y" - c_wchar.from_param("x") - self.assertRaises(TypeError, c_wchar.from_param, b"x") - (c_wchar * 3)("a", "b", "c") - self.assertRaises(TypeError, c_wchar * 3, b"a", b"b", b"c") - - def test_c_char_p(self): - c_char_p(b"foo bar") - self.assertRaises(TypeError, c_char_p, "foo bar") - - def test_c_wchar_p(self): - c_wchar_p("foo bar") - self.assertRaises(TypeError, c_wchar_p, b"foo bar") - - def test_struct(self): - class X(Structure): - _fields_ = [("a", c_char * 3)] - - x = X(b"abc") - self.assertRaises(TypeError, X, "abc") - self.assertEqual(x.a, b"abc") - self.assertEqual(type(x.a), bytes) - - def test_struct_W(self): - class X(Structure): - _fields_ = [("a", c_wchar * 3)] - - x = X("abc") - self.assertRaises(TypeError, X, b"abc") - self.assertEqual(x.a, "abc") - self.assertEqual(type(x.a), str) - - @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') - def test_BSTR(self): - from _ctypes import _SimpleCData - class BSTR(_SimpleCData): - _type_ = "X" - - BSTR("abc") - - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_byteswap.py b/Lib/ctypes/test/test_byteswap.py deleted file mode 100644 index 7e98559dfb..0000000000 --- a/Lib/ctypes/test/test_byteswap.py +++ /dev/null @@ -1,364 +0,0 @@ -import sys, unittest, struct, math, ctypes -from binascii import hexlify - -from ctypes import * - -def bin(s): - return hexlify(memoryview(s)).decode().upper() - -# Each *simple* type that supports different byte orders has an -# __ctype_be__ attribute that specifies the same type in BIG ENDIAN -# byte order, and a __ctype_le__ attribute that is the same type in -# LITTLE ENDIAN byte order. -# -# For Structures and Unions, these types are created on demand. - -class Test(unittest.TestCase): - @unittest.skip('test disabled') - def test_X(self): - print(sys.byteorder, file=sys.stderr) - for i in range(32): - bits = BITS() - setattr(bits, "i%s" % i, 1) - dump(bits) - - def test_slots(self): - class BigPoint(BigEndianStructure): - __slots__ = () - _fields_ = [("x", c_int), ("y", c_int)] - - class LowPoint(LittleEndianStructure): - __slots__ = () - _fields_ = [("x", c_int), ("y", c_int)] - - big = BigPoint() - little = LowPoint() - big.x = 4 - big.y = 2 - little.x = 2 - little.y = 4 - with self.assertRaises(AttributeError): - big.z = 42 - with self.assertRaises(AttributeError): - little.z = 24 - - def test_endian_short(self): - if sys.byteorder == "little": - self.assertIs(c_short.__ctype_le__, c_short) - self.assertIs(c_short.__ctype_be__.__ctype_le__, c_short) - else: - self.assertIs(c_short.__ctype_be__, c_short) - self.assertIs(c_short.__ctype_le__.__ctype_be__, c_short) - s = c_short.__ctype_be__(0x1234) - self.assertEqual(bin(struct.pack(">h", 0x1234)), "1234") - self.assertEqual(bin(s), "1234") - self.assertEqual(s.value, 0x1234) - - s = c_short.__ctype_le__(0x1234) - self.assertEqual(bin(struct.pack("h", 0x1234)), "1234") - self.assertEqual(bin(s), "1234") - self.assertEqual(s.value, 0x1234) - - s = c_ushort.__ctype_le__(0x1234) - self.assertEqual(bin(struct.pack("i", 0x12345678)), "12345678") - self.assertEqual(bin(s), "12345678") - self.assertEqual(s.value, 0x12345678) - - s = c_int.__ctype_le__(0x12345678) - self.assertEqual(bin(struct.pack("I", 0x12345678)), "12345678") - self.assertEqual(bin(s), "12345678") - self.assertEqual(s.value, 0x12345678) - - s = c_uint.__ctype_le__(0x12345678) - self.assertEqual(bin(struct.pack("q", 0x1234567890ABCDEF)), "1234567890ABCDEF") - self.assertEqual(bin(s), "1234567890ABCDEF") - self.assertEqual(s.value, 0x1234567890ABCDEF) - - s = c_longlong.__ctype_le__(0x1234567890ABCDEF) - self.assertEqual(bin(struct.pack("Q", 0x1234567890ABCDEF)), "1234567890ABCDEF") - self.assertEqual(bin(s), "1234567890ABCDEF") - self.assertEqual(s.value, 0x1234567890ABCDEF) - - s = c_ulonglong.__ctype_le__(0x1234567890ABCDEF) - self.assertEqual(bin(struct.pack("f", math.pi)), bin(s)) - - def test_endian_double(self): - if sys.byteorder == "little": - self.assertIs(c_double.__ctype_le__, c_double) - self.assertIs(c_double.__ctype_be__.__ctype_le__, c_double) - else: - self.assertIs(c_double.__ctype_be__, c_double) - self.assertIs(c_double.__ctype_le__.__ctype_be__, c_double) - s = c_double(math.pi) - self.assertEqual(s.value, math.pi) - self.assertEqual(bin(struct.pack("d", math.pi)), bin(s)) - s = c_double.__ctype_le__(math.pi) - self.assertEqual(s.value, math.pi) - self.assertEqual(bin(struct.pack("d", math.pi)), bin(s)) - - def test_endian_other(self): - self.assertIs(c_byte.__ctype_le__, c_byte) - self.assertIs(c_byte.__ctype_be__, c_byte) - - self.assertIs(c_ubyte.__ctype_le__, c_ubyte) - self.assertIs(c_ubyte.__ctype_be__, c_ubyte) - - self.assertIs(c_char.__ctype_le__, c_char) - self.assertIs(c_char.__ctype_be__, c_char) - - def test_struct_fields_unsupported_byte_order(self): - - fields = [ - ("a", c_ubyte), - ("b", c_byte), - ("c", c_short), - ("d", c_ushort), - ("e", c_int), - ("f", c_uint), - ("g", c_long), - ("h", c_ulong), - ("i", c_longlong), - ("k", c_ulonglong), - ("l", c_float), - ("m", c_double), - ("n", c_char), - ("b1", c_byte, 3), - ("b2", c_byte, 3), - ("b3", c_byte, 2), - ("a", c_int * 3 * 3 * 3) - ] - - # these fields do not support different byte order: - for typ in c_wchar, c_void_p, POINTER(c_int): - with self.assertRaises(TypeError): - class T(BigEndianStructure if sys.byteorder == "little" else LittleEndianStructure): - _fields_ = fields + [("x", typ)] - - - def test_struct_struct(self): - # nested structures with different byteorders - - # create nested structures with given byteorders and set memory to data - - for nested, data in ( - (BigEndianStructure, b'\0\0\0\1\0\0\0\2'), - (LittleEndianStructure, b'\1\0\0\0\2\0\0\0'), - ): - for parent in ( - BigEndianStructure, - LittleEndianStructure, - Structure, - ): - class NestedStructure(nested): - _fields_ = [("x", c_uint32), - ("y", c_uint32)] - - class TestStructure(parent): - _fields_ = [("point", NestedStructure)] - - self.assertEqual(len(data), sizeof(TestStructure)) - ptr = POINTER(TestStructure) - s = cast(data, ptr)[0] - del ctypes._pointer_type_cache[TestStructure] - self.assertEqual(s.point.x, 1) - self.assertEqual(s.point.y, 2) - - def test_struct_field_alignment(self): - # standard packing in struct uses no alignment. - # So, we have to align using pad bytes. - # - # Unaligned accesses will crash Python (on those platforms that - # don't allow it, like sparc solaris). - if sys.byteorder == "little": - base = BigEndianStructure - fmt = ">bxhid" - else: - base = LittleEndianStructure - fmt = " float -> double - import math - self.check_type(c_float, math.e) - self.check_type(c_float, -math.e) - - def test_double(self): - self.check_type(c_double, 3.14) - self.check_type(c_double, -3.14) - - @need_symbol('c_longdouble') - def test_longdouble(self): - self.check_type(c_longdouble, 3.14) - self.check_type(c_longdouble, -3.14) - - def test_char(self): - self.check_type(c_char, b"x") - self.check_type(c_char, b"a") - - # disabled: would now (correctly) raise a RuntimeWarning about - # a memory leak. A callback function cannot return a non-integral - # C type without causing a memory leak. - @unittest.skip('test disabled') - def test_char_p(self): - self.check_type(c_char_p, "abc") - self.check_type(c_char_p, "def") - - def test_pyobject(self): - o = () - from sys import getrefcount as grc - for o in (), [], object(): - initial = grc(o) - # This call leaks a reference to 'o'... - self.check_type(py_object, o) - before = grc(o) - # ...but this call doesn't leak any more. Where is the refcount? - self.check_type(py_object, o) - after = grc(o) - self.assertEqual((after, o), (before, o)) - - def test_unsupported_restype_1(self): - # Only "fundamental" result types are supported for callback - # functions, the type must have a non-NULL stgdict->setfunc. - # POINTER(c_double), for example, is not supported. - - prototype = self.functype.__func__(POINTER(c_double)) - # The type is checked when the prototype is called - self.assertRaises(TypeError, prototype, lambda: None) - - def test_unsupported_restype_2(self): - prototype = self.functype.__func__(object) - self.assertRaises(TypeError, prototype, lambda: None) - - def test_issue_7959(self): - proto = self.functype.__func__(None) - - class X(object): - def func(self): pass - def __init__(self): - self.v = proto(self.func) - - import gc - for i in range(32): - X() - gc.collect() - live = [x for x in gc.get_objects() - if isinstance(x, X)] - self.assertEqual(len(live), 0) - - def test_issue12483(self): - import gc - class Nasty: - def __del__(self): - gc.collect() - CFUNCTYPE(None)(lambda x=Nasty(): None) - - -@need_symbol('WINFUNCTYPE') -class StdcallCallbacks(Callbacks): - try: - functype = WINFUNCTYPE - except NameError: - pass - -################################################################ - -class SampleCallbacksTestCase(unittest.TestCase): - - def test_integrate(self): - # Derived from some then non-working code, posted by David Foster - dll = CDLL(_ctypes_test.__file__) - - # The function prototype called by 'integrate': double func(double); - CALLBACK = CFUNCTYPE(c_double, c_double) - - # The integrate function itself, exposed from the _ctypes_test dll - integrate = dll.integrate - integrate.argtypes = (c_double, c_double, CALLBACK, c_long) - integrate.restype = c_double - - def func(x): - return x**2 - - result = integrate(0.0, 1.0, CALLBACK(func), 10) - diff = abs(result - 1./3.) - - self.assertLess(diff, 0.01, "%s not less than 0.01" % diff) - - def test_issue_8959_a(self): - from ctypes.util import find_library - libc_path = find_library("c") - if not libc_path: - self.skipTest('could not find libc') - libc = CDLL(libc_path) - - @CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int)) - def cmp_func(a, b): - return a[0] - b[0] - - array = (c_int * 5)(5, 1, 99, 7, 33) - - libc.qsort(array, len(array), sizeof(c_int), cmp_func) - self.assertEqual(array[:], [1, 5, 7, 33, 99]) - - @need_symbol('WINFUNCTYPE') - def test_issue_8959_b(self): - from ctypes.wintypes import BOOL, HWND, LPARAM - global windowCount - windowCount = 0 - - @WINFUNCTYPE(BOOL, HWND, LPARAM) - def EnumWindowsCallbackFunc(hwnd, lParam): - global windowCount - windowCount += 1 - return True #Allow windows to keep enumerating - - windll.user32.EnumWindows(EnumWindowsCallbackFunc, 0) - - def test_callback_register_int(self): - # Issue #8275: buggy handling of callback args under Win64 - # NOTE: should be run on release builds as well - dll = CDLL(_ctypes_test.__file__) - CALLBACK = CFUNCTYPE(c_int, c_int, c_int, c_int, c_int, c_int) - # All this function does is call the callback with its args squared - func = dll._testfunc_cbk_reg_int - func.argtypes = (c_int, c_int, c_int, c_int, c_int, CALLBACK) - func.restype = c_int - - def callback(a, b, c, d, e): - return a + b + c + d + e - - result = func(2, 3, 4, 5, 6, CALLBACK(callback)) - self.assertEqual(result, callback(2*2, 3*3, 4*4, 5*5, 6*6)) - - def test_callback_register_double(self): - # Issue #8275: buggy handling of callback args under Win64 - # NOTE: should be run on release builds as well - dll = CDLL(_ctypes_test.__file__) - CALLBACK = CFUNCTYPE(c_double, c_double, c_double, c_double, - c_double, c_double) - # All this function does is call the callback with its args squared - func = dll._testfunc_cbk_reg_double - func.argtypes = (c_double, c_double, c_double, - c_double, c_double, CALLBACK) - func.restype = c_double - - def callback(a, b, c, d, e): - return a + b + c + d + e - - result = func(1.1, 2.2, 3.3, 4.4, 5.5, CALLBACK(callback)) - self.assertEqual(result, - callback(1.1*1.1, 2.2*2.2, 3.3*3.3, 4.4*4.4, 5.5*5.5)) - - def test_callback_large_struct(self): - class Check: pass - - # This should mirror the structure in Modules/_ctypes/_ctypes_test.c - class X(Structure): - _fields_ = [ - ('first', c_ulong), - ('second', c_ulong), - ('third', c_ulong), - ] - - def callback(check, s): - check.first = s.first - check.second = s.second - check.third = s.third - # See issue #29565. - # The structure should be passed by value, so - # any changes to it should not be reflected in - # the value passed - s.first = s.second = s.third = 0x0badf00d - - check = Check() - s = X() - s.first = 0xdeadbeef - s.second = 0xcafebabe - s.third = 0x0bad1dea - - CALLBACK = CFUNCTYPE(None, X) - dll = CDLL(_ctypes_test.__file__) - func = dll._testfunc_cbk_large_struct - func.argtypes = (X, CALLBACK) - func.restype = None - # the function just calls the callback with the passed structure - func(s, CALLBACK(functools.partial(callback, check))) - self.assertEqual(check.first, s.first) - self.assertEqual(check.second, s.second) - self.assertEqual(check.third, s.third) - self.assertEqual(check.first, 0xdeadbeef) - self.assertEqual(check.second, 0xcafebabe) - self.assertEqual(check.third, 0x0bad1dea) - # See issue #29565. - # Ensure that the original struct is unchanged. - self.assertEqual(s.first, check.first) - self.assertEqual(s.second, check.second) - self.assertEqual(s.third, check.third) - - def test_callback_too_many_args(self): - def func(*args): - return len(args) - - # valid call with nargs <= CTYPES_MAX_ARGCOUNT - proto = CFUNCTYPE(c_int, *(c_int,) * CTYPES_MAX_ARGCOUNT) - cb = proto(func) - args1 = (1,) * CTYPES_MAX_ARGCOUNT - self.assertEqual(cb(*args1), CTYPES_MAX_ARGCOUNT) - - # invalid call with nargs > CTYPES_MAX_ARGCOUNT - args2 = (1,) * (CTYPES_MAX_ARGCOUNT + 1) - with self.assertRaises(ArgumentError): - cb(*args2) - - # error when creating the type with too many arguments - with self.assertRaises(ArgumentError): - CFUNCTYPE(c_int, *(c_int,) * (CTYPES_MAX_ARGCOUNT + 1)) - - def test_convert_result_error(self): - def func(): - return ("tuple",) - - proto = CFUNCTYPE(c_int) - ctypes_func = proto(func) - with support.catch_unraisable_exception() as cm: - # don't test the result since it is an uninitialized value - result = ctypes_func() - - self.assertIsInstance(cm.unraisable.exc_value, TypeError) - self.assertEqual(cm.unraisable.err_msg, - "Exception ignored on converting result " - "of ctypes callback function") - self.assertIs(cm.unraisable.object, func) - - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_cast.py b/Lib/ctypes/test/test_cast.py deleted file mode 100644 index 6878f97328..0000000000 --- a/Lib/ctypes/test/test_cast.py +++ /dev/null @@ -1,99 +0,0 @@ -from ctypes import * -from ctypes.test import need_symbol -import unittest -import sys - -class Test(unittest.TestCase): - - def test_array2pointer(self): - array = (c_int * 3)(42, 17, 2) - - # casting an array to a pointer works. - ptr = cast(array, POINTER(c_int)) - self.assertEqual([ptr[i] for i in range(3)], [42, 17, 2]) - - if 2*sizeof(c_short) == sizeof(c_int): - ptr = cast(array, POINTER(c_short)) - if sys.byteorder == "little": - self.assertEqual([ptr[i] for i in range(6)], - [42, 0, 17, 0, 2, 0]) - else: - self.assertEqual([ptr[i] for i in range(6)], - [0, 42, 0, 17, 0, 2]) - - def test_address2pointer(self): - array = (c_int * 3)(42, 17, 2) - - address = addressof(array) - ptr = cast(c_void_p(address), POINTER(c_int)) - self.assertEqual([ptr[i] for i in range(3)], [42, 17, 2]) - - ptr = cast(address, POINTER(c_int)) - self.assertEqual([ptr[i] for i in range(3)], [42, 17, 2]) - - def test_p2a_objects(self): - array = (c_char_p * 5)() - self.assertEqual(array._objects, None) - array[0] = b"foo bar" - self.assertEqual(array._objects, {'0': b"foo bar"}) - - p = cast(array, POINTER(c_char_p)) - # array and p share a common _objects attribute - self.assertIs(p._objects, array._objects) - self.assertEqual(array._objects, {'0': b"foo bar", id(array): array}) - p[0] = b"spam spam" - self.assertEqual(p._objects, {'0': b"spam spam", id(array): array}) - self.assertIs(array._objects, p._objects) - p[1] = b"foo bar" - self.assertEqual(p._objects, {'1': b'foo bar', '0': b"spam spam", id(array): array}) - self.assertIs(array._objects, p._objects) - - def test_other(self): - p = cast((c_int * 4)(1, 2, 3, 4), POINTER(c_int)) - self.assertEqual(p[:4], [1,2, 3, 4]) - self.assertEqual(p[:4:], [1, 2, 3, 4]) - self.assertEqual(p[3:-1:-1], [4, 3, 2, 1]) - self.assertEqual(p[:4:3], [1, 4]) - c_int() - self.assertEqual(p[:4], [1, 2, 3, 4]) - self.assertEqual(p[:4:], [1, 2, 3, 4]) - self.assertEqual(p[3:-1:-1], [4, 3, 2, 1]) - self.assertEqual(p[:4:3], [1, 4]) - p[2] = 96 - self.assertEqual(p[:4], [1, 2, 96, 4]) - self.assertEqual(p[:4:], [1, 2, 96, 4]) - self.assertEqual(p[3:-1:-1], [4, 96, 2, 1]) - self.assertEqual(p[:4:3], [1, 4]) - c_int() - self.assertEqual(p[:4], [1, 2, 96, 4]) - self.assertEqual(p[:4:], [1, 2, 96, 4]) - self.assertEqual(p[3:-1:-1], [4, 96, 2, 1]) - self.assertEqual(p[:4:3], [1, 4]) - - def test_char_p(self): - # This didn't work: bad argument to internal function - s = c_char_p(b"hiho") - self.assertEqual(cast(cast(s, c_void_p), c_char_p).value, - b"hiho") - - @need_symbol('c_wchar_p') - def test_wchar_p(self): - s = c_wchar_p("hiho") - self.assertEqual(cast(cast(s, c_void_p), c_wchar_p).value, - "hiho") - - def test_bad_type_arg(self): - # The type argument must be a ctypes pointer type. - array_type = c_byte * sizeof(c_int) - array = array_type() - self.assertRaises(TypeError, cast, array, None) - self.assertRaises(TypeError, cast, array, array_type) - class Struct(Structure): - _fields_ = [("a", c_int)] - self.assertRaises(TypeError, cast, array, Struct) - class MyUnion(Union): - _fields_ = [("a", c_int)] - self.assertRaises(TypeError, cast, array, MyUnion) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_cfuncs.py b/Lib/ctypes/test/test_cfuncs.py deleted file mode 100644 index 09b06840bf..0000000000 --- a/Lib/ctypes/test/test_cfuncs.py +++ /dev/null @@ -1,218 +0,0 @@ -# A lot of failures in these tests on Mac OS X. -# Byte order related? - -import unittest -from ctypes import * -from ctypes.test import need_symbol - -import _ctypes_test - -class CFunctions(unittest.TestCase): - _dll = CDLL(_ctypes_test.__file__) - - def S(self): - return c_longlong.in_dll(self._dll, "last_tf_arg_s").value - def U(self): - return c_ulonglong.in_dll(self._dll, "last_tf_arg_u").value - - def test_byte(self): - self._dll.tf_b.restype = c_byte - self._dll.tf_b.argtypes = (c_byte,) - self.assertEqual(self._dll.tf_b(-126), -42) - self.assertEqual(self.S(), -126) - - def test_byte_plus(self): - self._dll.tf_bb.restype = c_byte - self._dll.tf_bb.argtypes = (c_byte, c_byte) - self.assertEqual(self._dll.tf_bb(0, -126), -42) - self.assertEqual(self.S(), -126) - - def test_ubyte(self): - self._dll.tf_B.restype = c_ubyte - self._dll.tf_B.argtypes = (c_ubyte,) - self.assertEqual(self._dll.tf_B(255), 85) - self.assertEqual(self.U(), 255) - - def test_ubyte_plus(self): - self._dll.tf_bB.restype = c_ubyte - self._dll.tf_bB.argtypes = (c_byte, c_ubyte) - self.assertEqual(self._dll.tf_bB(0, 255), 85) - self.assertEqual(self.U(), 255) - - def test_short(self): - self._dll.tf_h.restype = c_short - self._dll.tf_h.argtypes = (c_short,) - self.assertEqual(self._dll.tf_h(-32766), -10922) - self.assertEqual(self.S(), -32766) - - def test_short_plus(self): - self._dll.tf_bh.restype = c_short - self._dll.tf_bh.argtypes = (c_byte, c_short) - self.assertEqual(self._dll.tf_bh(0, -32766), -10922) - self.assertEqual(self.S(), -32766) - - def test_ushort(self): - self._dll.tf_H.restype = c_ushort - self._dll.tf_H.argtypes = (c_ushort,) - self.assertEqual(self._dll.tf_H(65535), 21845) - self.assertEqual(self.U(), 65535) - - def test_ushort_plus(self): - self._dll.tf_bH.restype = c_ushort - self._dll.tf_bH.argtypes = (c_byte, c_ushort) - self.assertEqual(self._dll.tf_bH(0, 65535), 21845) - self.assertEqual(self.U(), 65535) - - def test_int(self): - self._dll.tf_i.restype = c_int - self._dll.tf_i.argtypes = (c_int,) - self.assertEqual(self._dll.tf_i(-2147483646), -715827882) - self.assertEqual(self.S(), -2147483646) - - def test_int_plus(self): - self._dll.tf_bi.restype = c_int - self._dll.tf_bi.argtypes = (c_byte, c_int) - self.assertEqual(self._dll.tf_bi(0, -2147483646), -715827882) - self.assertEqual(self.S(), -2147483646) - - def test_uint(self): - self._dll.tf_I.restype = c_uint - self._dll.tf_I.argtypes = (c_uint,) - self.assertEqual(self._dll.tf_I(4294967295), 1431655765) - self.assertEqual(self.U(), 4294967295) - - def test_uint_plus(self): - self._dll.tf_bI.restype = c_uint - self._dll.tf_bI.argtypes = (c_byte, c_uint) - self.assertEqual(self._dll.tf_bI(0, 4294967295), 1431655765) - self.assertEqual(self.U(), 4294967295) - - def test_long(self): - self._dll.tf_l.restype = c_long - self._dll.tf_l.argtypes = (c_long,) - self.assertEqual(self._dll.tf_l(-2147483646), -715827882) - self.assertEqual(self.S(), -2147483646) - - def test_long_plus(self): - self._dll.tf_bl.restype = c_long - self._dll.tf_bl.argtypes = (c_byte, c_long) - self.assertEqual(self._dll.tf_bl(0, -2147483646), -715827882) - self.assertEqual(self.S(), -2147483646) - - def test_ulong(self): - self._dll.tf_L.restype = c_ulong - self._dll.tf_L.argtypes = (c_ulong,) - self.assertEqual(self._dll.tf_L(4294967295), 1431655765) - self.assertEqual(self.U(), 4294967295) - - def test_ulong_plus(self): - self._dll.tf_bL.restype = c_ulong - self._dll.tf_bL.argtypes = (c_char, c_ulong) - self.assertEqual(self._dll.tf_bL(b' ', 4294967295), 1431655765) - self.assertEqual(self.U(), 4294967295) - - @need_symbol('c_longlong') - def test_longlong(self): - self._dll.tf_q.restype = c_longlong - self._dll.tf_q.argtypes = (c_longlong, ) - self.assertEqual(self._dll.tf_q(-9223372036854775806), -3074457345618258602) - self.assertEqual(self.S(), -9223372036854775806) - - @need_symbol('c_longlong') - def test_longlong_plus(self): - self._dll.tf_bq.restype = c_longlong - self._dll.tf_bq.argtypes = (c_byte, c_longlong) - self.assertEqual(self._dll.tf_bq(0, -9223372036854775806), -3074457345618258602) - self.assertEqual(self.S(), -9223372036854775806) - - @need_symbol('c_ulonglong') - def test_ulonglong(self): - self._dll.tf_Q.restype = c_ulonglong - self._dll.tf_Q.argtypes = (c_ulonglong, ) - self.assertEqual(self._dll.tf_Q(18446744073709551615), 6148914691236517205) - self.assertEqual(self.U(), 18446744073709551615) - - @need_symbol('c_ulonglong') - def test_ulonglong_plus(self): - self._dll.tf_bQ.restype = c_ulonglong - self._dll.tf_bQ.argtypes = (c_byte, c_ulonglong) - self.assertEqual(self._dll.tf_bQ(0, 18446744073709551615), 6148914691236517205) - self.assertEqual(self.U(), 18446744073709551615) - - def test_float(self): - self._dll.tf_f.restype = c_float - self._dll.tf_f.argtypes = (c_float,) - self.assertEqual(self._dll.tf_f(-42.), -14.) - self.assertEqual(self.S(), -42) - - def test_float_plus(self): - self._dll.tf_bf.restype = c_float - self._dll.tf_bf.argtypes = (c_byte, c_float) - self.assertEqual(self._dll.tf_bf(0, -42.), -14.) - self.assertEqual(self.S(), -42) - - def test_double(self): - self._dll.tf_d.restype = c_double - self._dll.tf_d.argtypes = (c_double,) - self.assertEqual(self._dll.tf_d(42.), 14.) - self.assertEqual(self.S(), 42) - - def test_double_plus(self): - self._dll.tf_bd.restype = c_double - self._dll.tf_bd.argtypes = (c_byte, c_double) - self.assertEqual(self._dll.tf_bd(0, 42.), 14.) - self.assertEqual(self.S(), 42) - - @need_symbol('c_longdouble') - def test_longdouble(self): - self._dll.tf_D.restype = c_longdouble - self._dll.tf_D.argtypes = (c_longdouble,) - self.assertEqual(self._dll.tf_D(42.), 14.) - self.assertEqual(self.S(), 42) - - @need_symbol('c_longdouble') - def test_longdouble_plus(self): - self._dll.tf_bD.restype = c_longdouble - self._dll.tf_bD.argtypes = (c_byte, c_longdouble) - self.assertEqual(self._dll.tf_bD(0, 42.), 14.) - self.assertEqual(self.S(), 42) - - def test_callwithresult(self): - def process_result(result): - return result * 2 - self._dll.tf_i.restype = process_result - self._dll.tf_i.argtypes = (c_int,) - self.assertEqual(self._dll.tf_i(42), 28) - self.assertEqual(self.S(), 42) - self.assertEqual(self._dll.tf_i(-42), -28) - self.assertEqual(self.S(), -42) - - def test_void(self): - self._dll.tv_i.restype = None - self._dll.tv_i.argtypes = (c_int,) - self.assertEqual(self._dll.tv_i(42), None) - self.assertEqual(self.S(), 42) - self.assertEqual(self._dll.tv_i(-42), None) - self.assertEqual(self.S(), -42) - -# The following repeats the above tests with stdcall functions (where -# they are available) -try: - WinDLL -except NameError: - def stdcall_dll(*_): pass -else: - class stdcall_dll(WinDLL): - def __getattr__(self, name): - if name[:2] == '__' and name[-2:] == '__': - raise AttributeError(name) - func = self._FuncPtr(("s_" + name, self)) - setattr(self, name, func) - return func - -@need_symbol('WinDLL') -class stdcallCFunctions(CFunctions): - _dll = stdcall_dll(_ctypes_test.__file__) - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_checkretval.py b/Lib/ctypes/test/test_checkretval.py deleted file mode 100644 index e9567dc391..0000000000 --- a/Lib/ctypes/test/test_checkretval.py +++ /dev/null @@ -1,36 +0,0 @@ -import unittest - -from ctypes import * -from ctypes.test import need_symbol - -class CHECKED(c_int): - def _check_retval_(value): - # Receives a CHECKED instance. - return str(value.value) - _check_retval_ = staticmethod(_check_retval_) - -class Test(unittest.TestCase): - - def test_checkretval(self): - - import _ctypes_test - dll = CDLL(_ctypes_test.__file__) - self.assertEqual(42, dll._testfunc_p_p(42)) - - dll._testfunc_p_p.restype = CHECKED - self.assertEqual("42", dll._testfunc_p_p(42)) - - dll._testfunc_p_p.restype = None - self.assertEqual(None, dll._testfunc_p_p(42)) - - del dll._testfunc_p_p.restype - self.assertEqual(42, dll._testfunc_p_p(42)) - - @need_symbol('oledll') - def test_oledll(self): - self.assertRaises(OSError, - oledll.oleaut32.CreateTypeLib2, - 0, None, None) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_delattr.py b/Lib/ctypes/test/test_delattr.py deleted file mode 100644 index 0f4d58691b..0000000000 --- a/Lib/ctypes/test/test_delattr.py +++ /dev/null @@ -1,21 +0,0 @@ -import unittest -from ctypes import * - -class X(Structure): - _fields_ = [("foo", c_int)] - -class TestCase(unittest.TestCase): - def test_simple(self): - self.assertRaises(TypeError, - delattr, c_int(42), "value") - - def test_chararray(self): - self.assertRaises(TypeError, - delattr, (c_char * 5)(), "value") - - def test_struct(self): - self.assertRaises(TypeError, - delattr, X(), "foo") - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_errno.py b/Lib/ctypes/test/test_errno.py deleted file mode 100644 index 3685164dde..0000000000 --- a/Lib/ctypes/test/test_errno.py +++ /dev/null @@ -1,76 +0,0 @@ -import unittest, os, errno -import threading - -from ctypes import * -from ctypes.util import find_library - -class Test(unittest.TestCase): - def test_open(self): - libc_name = find_library("c") - if libc_name is None: - raise unittest.SkipTest("Unable to find C library") - libc = CDLL(libc_name, use_errno=True) - if os.name == "nt": - libc_open = libc._open - else: - libc_open = libc.open - - libc_open.argtypes = c_char_p, c_int - - self.assertEqual(libc_open(b"", 0), -1) - self.assertEqual(get_errno(), errno.ENOENT) - - self.assertEqual(set_errno(32), errno.ENOENT) - self.assertEqual(get_errno(), 32) - - def _worker(): - set_errno(0) - - libc = CDLL(libc_name, use_errno=False) - if os.name == "nt": - libc_open = libc._open - else: - libc_open = libc.open - libc_open.argtypes = c_char_p, c_int - self.assertEqual(libc_open(b"", 0), -1) - self.assertEqual(get_errno(), 0) - - t = threading.Thread(target=_worker) - t.start() - t.join() - - self.assertEqual(get_errno(), 32) - set_errno(0) - - @unittest.skipUnless(os.name == "nt", 'Test specific to Windows') - def test_GetLastError(self): - dll = WinDLL("kernel32", use_last_error=True) - GetModuleHandle = dll.GetModuleHandleA - GetModuleHandle.argtypes = [c_wchar_p] - - self.assertEqual(0, GetModuleHandle("foo")) - self.assertEqual(get_last_error(), 126) - - self.assertEqual(set_last_error(32), 126) - self.assertEqual(get_last_error(), 32) - - def _worker(): - set_last_error(0) - - dll = WinDLL("kernel32", use_last_error=False) - GetModuleHandle = dll.GetModuleHandleW - GetModuleHandle.argtypes = [c_wchar_p] - GetModuleHandle("bar") - - self.assertEqual(get_last_error(), 0) - - t = threading.Thread(target=_worker) - t.start() - t.join() - - self.assertEqual(get_last_error(), 32) - - set_last_error(0) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_find.py b/Lib/ctypes/test/test_find.py deleted file mode 100644 index 1ff9d019b1..0000000000 --- a/Lib/ctypes/test/test_find.py +++ /dev/null @@ -1,127 +0,0 @@ -import unittest -import unittest.mock -import os.path -import sys -import test.support -from test.support import os_helper -from ctypes import * -from ctypes.util import find_library - -# On some systems, loading the OpenGL libraries needs the RTLD_GLOBAL mode. -class Test_OpenGL_libs(unittest.TestCase): - @classmethod - def setUpClass(cls): - lib_gl = lib_glu = lib_gle = None - if sys.platform == "win32": - lib_gl = find_library("OpenGL32") - lib_glu = find_library("Glu32") - elif sys.platform == "darwin": - lib_gl = lib_glu = find_library("OpenGL") - else: - lib_gl = find_library("GL") - lib_glu = find_library("GLU") - lib_gle = find_library("gle") - - ## print, for debugging - if test.support.verbose: - print("OpenGL libraries:") - for item in (("GL", lib_gl), - ("GLU", lib_glu), - ("gle", lib_gle)): - print("\t", item) - - cls.gl = cls.glu = cls.gle = None - if lib_gl: - try: - cls.gl = CDLL(lib_gl, mode=RTLD_GLOBAL) - except OSError: - pass - if lib_glu: - try: - cls.glu = CDLL(lib_glu, RTLD_GLOBAL) - except OSError: - pass - if lib_gle: - try: - cls.gle = CDLL(lib_gle) - except OSError: - pass - - @classmethod - def tearDownClass(cls): - cls.gl = cls.glu = cls.gle = None - - def test_gl(self): - if self.gl is None: - self.skipTest('lib_gl not available') - self.gl.glClearIndex - - def test_glu(self): - if self.glu is None: - self.skipTest('lib_glu not available') - self.glu.gluBeginCurve - - def test_gle(self): - if self.gle is None: - self.skipTest('lib_gle not available') - self.gle.gleGetJoinStyle - - def test_shell_injection(self): - result = find_library('; echo Hello shell > ' + os_helper.TESTFN) - self.assertFalse(os.path.lexists(os_helper.TESTFN)) - self.assertIsNone(result) - - -@unittest.skipUnless(sys.platform.startswith('linux'), - 'Test only valid for Linux') -class FindLibraryLinux(unittest.TestCase): - def test_find_on_libpath(self): - import subprocess - import tempfile - - try: - p = subprocess.Popen(['gcc', '--version'], stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL) - out, _ = p.communicate() - except OSError: - raise unittest.SkipTest('gcc, needed for test, not available') - with tempfile.TemporaryDirectory() as d: - # create an empty temporary file - srcname = os.path.join(d, 'dummy.c') - libname = 'py_ctypes_test_dummy' - dstname = os.path.join(d, 'lib%s.so' % libname) - with open(srcname, 'wb') as f: - pass - self.assertTrue(os.path.exists(srcname)) - # compile the file to a shared library - cmd = ['gcc', '-o', dstname, '--shared', - '-Wl,-soname,lib%s.so' % libname, srcname] - out = subprocess.check_output(cmd) - self.assertTrue(os.path.exists(dstname)) - # now check that the .so can't be found (since not in - # LD_LIBRARY_PATH) - self.assertIsNone(find_library(libname)) - # now add the location to LD_LIBRARY_PATH - with os_helper.EnvironmentVarGuard() as env: - KEY = 'LD_LIBRARY_PATH' - if KEY not in env: - v = d - else: - v = '%s:%s' % (env[KEY], d) - env.set(KEY, v) - # now check that the .so can be found (since in - # LD_LIBRARY_PATH) - self.assertEqual(find_library(libname), 'lib%s.so' % libname) - - def test_find_library_with_gcc(self): - with unittest.mock.patch("ctypes.util._findSoname_ldconfig", lambda *args: None): - self.assertNotEqual(find_library('c'), None) - - def test_find_library_with_ld(self): - with unittest.mock.patch("ctypes.util._findSoname_ldconfig", lambda *args: None), \ - unittest.mock.patch("ctypes.util._findLib_gcc", lambda *args: None): - self.assertNotEqual(find_library('c'), None) - - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_frombuffer.py b/Lib/ctypes/test/test_frombuffer.py deleted file mode 100644 index 55c244356b..0000000000 --- a/Lib/ctypes/test/test_frombuffer.py +++ /dev/null @@ -1,141 +0,0 @@ -from ctypes import * -import array -import gc -import unittest - -class X(Structure): - _fields_ = [("c_int", c_int)] - init_called = False - def __init__(self): - self._init_called = True - -class Test(unittest.TestCase): - def test_from_buffer(self): - a = array.array("i", range(16)) - x = (c_int * 16).from_buffer(a) - - y = X.from_buffer(a) - self.assertEqual(y.c_int, a[0]) - self.assertFalse(y.init_called) - - self.assertEqual(x[:], a.tolist()) - - a[0], a[-1] = 200, -200 - self.assertEqual(x[:], a.tolist()) - - self.assertRaises(BufferError, a.append, 100) - self.assertRaises(BufferError, a.pop) - - del x; del y; gc.collect(); gc.collect(); gc.collect() - a.append(100) - a.pop() - x = (c_int * 16).from_buffer(a) - - self.assertIn(a, [obj.obj if isinstance(obj, memoryview) else obj - for obj in x._objects.values()]) - - expected = x[:] - del a; gc.collect(); gc.collect(); gc.collect() - self.assertEqual(x[:], expected) - - with self.assertRaisesRegex(TypeError, "not writable"): - (c_char * 16).from_buffer(b"a" * 16) - with self.assertRaisesRegex(TypeError, "not writable"): - (c_char * 16).from_buffer(memoryview(b"a" * 16)) - with self.assertRaisesRegex(TypeError, "not C contiguous"): - (c_char * 16).from_buffer(memoryview(bytearray(b"a" * 16))[::-1]) - msg = "bytes-like object is required" - with self.assertRaisesRegex(TypeError, msg): - (c_char * 16).from_buffer("a" * 16) - - def test_fortran_contiguous(self): - try: - import _testbuffer - except ImportError as err: - self.skipTest(str(err)) - flags = _testbuffer.ND_WRITABLE | _testbuffer.ND_FORTRAN - array = _testbuffer.ndarray( - [97] * 16, format="B", shape=[4, 4], flags=flags) - with self.assertRaisesRegex(TypeError, "not C contiguous"): - (c_char * 16).from_buffer(array) - array = memoryview(array) - self.assertTrue(array.f_contiguous) - self.assertFalse(array.c_contiguous) - with self.assertRaisesRegex(TypeError, "not C contiguous"): - (c_char * 16).from_buffer(array) - - def test_from_buffer_with_offset(self): - a = array.array("i", range(16)) - x = (c_int * 15).from_buffer(a, sizeof(c_int)) - - self.assertEqual(x[:], a.tolist()[1:]) - with self.assertRaises(ValueError): - c_int.from_buffer(a, -1) - with self.assertRaises(ValueError): - (c_int * 16).from_buffer(a, sizeof(c_int)) - with self.assertRaises(ValueError): - (c_int * 1).from_buffer(a, 16 * sizeof(c_int)) - - def test_from_buffer_memoryview(self): - a = [c_char.from_buffer(memoryview(bytearray(b'a')))] - a.append(a) - del a - gc.collect() # Should not crash - - def test_from_buffer_copy(self): - a = array.array("i", range(16)) - x = (c_int * 16).from_buffer_copy(a) - - y = X.from_buffer_copy(a) - self.assertEqual(y.c_int, a[0]) - self.assertFalse(y.init_called) - - self.assertEqual(x[:], list(range(16))) - - a[0], a[-1] = 200, -200 - self.assertEqual(x[:], list(range(16))) - - a.append(100) - self.assertEqual(x[:], list(range(16))) - - self.assertEqual(x._objects, None) - - del a; gc.collect(); gc.collect(); gc.collect() - self.assertEqual(x[:], list(range(16))) - - x = (c_char * 16).from_buffer_copy(b"a" * 16) - self.assertEqual(x[:], b"a" * 16) - with self.assertRaises(TypeError): - (c_char * 16).from_buffer_copy("a" * 16) - - def test_from_buffer_copy_with_offset(self): - a = array.array("i", range(16)) - x = (c_int * 15).from_buffer_copy(a, sizeof(c_int)) - - self.assertEqual(x[:], a.tolist()[1:]) - with self.assertRaises(ValueError): - c_int.from_buffer_copy(a, -1) - with self.assertRaises(ValueError): - (c_int * 16).from_buffer_copy(a, sizeof(c_int)) - with self.assertRaises(ValueError): - (c_int * 1).from_buffer_copy(a, 16 * sizeof(c_int)) - - def test_abstract(self): - from ctypes import _Pointer, _SimpleCData, _CFuncPtr - - self.assertRaises(TypeError, Array.from_buffer, bytearray(10)) - self.assertRaises(TypeError, Structure.from_buffer, bytearray(10)) - self.assertRaises(TypeError, Union.from_buffer, bytearray(10)) - self.assertRaises(TypeError, _CFuncPtr.from_buffer, bytearray(10)) - self.assertRaises(TypeError, _Pointer.from_buffer, bytearray(10)) - self.assertRaises(TypeError, _SimpleCData.from_buffer, bytearray(10)) - - self.assertRaises(TypeError, Array.from_buffer_copy, b"123") - self.assertRaises(TypeError, Structure.from_buffer_copy, b"123") - self.assertRaises(TypeError, Union.from_buffer_copy, b"123") - self.assertRaises(TypeError, _CFuncPtr.from_buffer_copy, b"123") - self.assertRaises(TypeError, _Pointer.from_buffer_copy, b"123") - self.assertRaises(TypeError, _SimpleCData.from_buffer_copy, b"123") - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_funcptr.py b/Lib/ctypes/test/test_funcptr.py deleted file mode 100644 index e0b9b54e97..0000000000 --- a/Lib/ctypes/test/test_funcptr.py +++ /dev/null @@ -1,132 +0,0 @@ -import unittest -from ctypes import * - -try: - WINFUNCTYPE -except NameError: - # fake to enable this test on Linux - WINFUNCTYPE = CFUNCTYPE - -import _ctypes_test -lib = CDLL(_ctypes_test.__file__) - -class CFuncPtrTestCase(unittest.TestCase): - def test_basic(self): - X = WINFUNCTYPE(c_int, c_int, c_int) - - def func(*args): - return len(args) - - x = X(func) - self.assertEqual(x.restype, c_int) - self.assertEqual(x.argtypes, (c_int, c_int)) - self.assertEqual(sizeof(x), sizeof(c_voidp)) - self.assertEqual(sizeof(X), sizeof(c_voidp)) - - def test_first(self): - StdCallback = WINFUNCTYPE(c_int, c_int, c_int) - CdeclCallback = CFUNCTYPE(c_int, c_int, c_int) - - def func(a, b): - return a + b - - s = StdCallback(func) - c = CdeclCallback(func) - - self.assertEqual(s(1, 2), 3) - self.assertEqual(c(1, 2), 3) - # The following no longer raises a TypeError - it is now - # possible, as in C, to call cdecl functions with more parameters. - #self.assertRaises(TypeError, c, 1, 2, 3) - self.assertEqual(c(1, 2, 3, 4, 5, 6), 3) - if not WINFUNCTYPE is CFUNCTYPE: - self.assertRaises(TypeError, s, 1, 2, 3) - - def test_structures(self): - WNDPROC = WINFUNCTYPE(c_long, c_int, c_int, c_int, c_int) - - def wndproc(hwnd, msg, wParam, lParam): - return hwnd + msg + wParam + lParam - - HINSTANCE = c_int - HICON = c_int - HCURSOR = c_int - LPCTSTR = c_char_p - - class WNDCLASS(Structure): - _fields_ = [("style", c_uint), - ("lpfnWndProc", WNDPROC), - ("cbClsExtra", c_int), - ("cbWndExtra", c_int), - ("hInstance", HINSTANCE), - ("hIcon", HICON), - ("hCursor", HCURSOR), - ("lpszMenuName", LPCTSTR), - ("lpszClassName", LPCTSTR)] - - wndclass = WNDCLASS() - wndclass.lpfnWndProc = WNDPROC(wndproc) - - WNDPROC_2 = WINFUNCTYPE(c_long, c_int, c_int, c_int, c_int) - - # This is no longer true, now that WINFUNCTYPE caches created types internally. - ## # CFuncPtr subclasses are compared by identity, so this raises a TypeError: - ## self.assertRaises(TypeError, setattr, wndclass, - ## "lpfnWndProc", WNDPROC_2(wndproc)) - # instead: - - self.assertIs(WNDPROC, WNDPROC_2) - # 'wndclass.lpfnWndProc' leaks 94 references. Why? - self.assertEqual(wndclass.lpfnWndProc(1, 2, 3, 4), 10) - - - f = wndclass.lpfnWndProc - - del wndclass - del wndproc - - self.assertEqual(f(10, 11, 12, 13), 46) - - def test_dllfunctions(self): - - def NoNullHandle(value): - if not value: - raise WinError() - return value - - strchr = lib.my_strchr - strchr.restype = c_char_p - strchr.argtypes = (c_char_p, c_char) - self.assertEqual(strchr(b"abcdefghi", b"b"), b"bcdefghi") - self.assertEqual(strchr(b"abcdefghi", b"x"), None) - - - strtok = lib.my_strtok - strtok.restype = c_char_p - # Neither of this does work: strtok changes the buffer it is passed -## strtok.argtypes = (c_char_p, c_char_p) -## strtok.argtypes = (c_string, c_char_p) - - def c_string(init): - size = len(init) + 1 - return (c_char*size)(*init) - - s = b"a\nb\nc" - b = c_string(s) - -## b = (c_char * (len(s)+1))() -## b.value = s - -## b = c_string(s) - self.assertEqual(strtok(b, b"\n"), b"a") - self.assertEqual(strtok(None, b"\n"), b"b") - self.assertEqual(strtok(None, b"\n"), b"c") - self.assertEqual(strtok(None, b"\n"), None) - - def test_abstract(self): - from ctypes import _CFuncPtr - - self.assertRaises(TypeError, _CFuncPtr, 13, "name", 42, "iid") - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_functions.py b/Lib/ctypes/test/test_functions.py deleted file mode 100644 index fc571700ce..0000000000 --- a/Lib/ctypes/test/test_functions.py +++ /dev/null @@ -1,384 +0,0 @@ -""" -Here is probably the place to write the docs, since the test-cases -show how the type behave. - -Later... -""" - -from ctypes import * -from ctypes.test import need_symbol -import sys, unittest - -try: - WINFUNCTYPE -except NameError: - # fake to enable this test on Linux - WINFUNCTYPE = CFUNCTYPE - -import _ctypes_test -dll = CDLL(_ctypes_test.__file__) -if sys.platform == "win32": - windll = WinDLL(_ctypes_test.__file__) - -class POINT(Structure): - _fields_ = [("x", c_int), ("y", c_int)] -class RECT(Structure): - _fields_ = [("left", c_int), ("top", c_int), - ("right", c_int), ("bottom", c_int)] -class FunctionTestCase(unittest.TestCase): - - def test_mro(self): - # in Python 2.3, this raises TypeError: MRO conflict among bases classes, - # in Python 2.2 it works. - # - # But in early versions of _ctypes.c, the result of tp_new - # wasn't checked, and it even crashed Python. - # Found by Greg Chapman. - - with self.assertRaises(TypeError): - class X(object, Array): - _length_ = 5 - _type_ = "i" - - from _ctypes import _Pointer - with self.assertRaises(TypeError): - class X(object, _Pointer): - pass - - from _ctypes import _SimpleCData - with self.assertRaises(TypeError): - class X(object, _SimpleCData): - _type_ = "i" - - with self.assertRaises(TypeError): - class X(object, Structure): - _fields_ = [] - - @need_symbol('c_wchar') - def test_wchar_parm(self): - f = dll._testfunc_i_bhilfd - f.argtypes = [c_byte, c_wchar, c_int, c_long, c_float, c_double] - result = f(1, "x", 3, 4, 5.0, 6.0) - self.assertEqual(result, 139) - self.assertEqual(type(result), int) - - @need_symbol('c_wchar') - def test_wchar_result(self): - f = dll._testfunc_i_bhilfd - f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] - f.restype = c_wchar - result = f(0, 0, 0, 0, 0, 0) - self.assertEqual(result, '\x00') - - def test_voidresult(self): - f = dll._testfunc_v - f.restype = None - f.argtypes = [c_int, c_int, POINTER(c_int)] - result = c_int() - self.assertEqual(None, f(1, 2, byref(result))) - self.assertEqual(result.value, 3) - - def test_intresult(self): - f = dll._testfunc_i_bhilfd - f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] - f.restype = c_int - result = f(1, 2, 3, 4, 5.0, 6.0) - self.assertEqual(result, 21) - self.assertEqual(type(result), int) - - result = f(-1, -2, -3, -4, -5.0, -6.0) - self.assertEqual(result, -21) - self.assertEqual(type(result), int) - - # If we declare the function to return a short, - # is the high part split off? - f.restype = c_short - result = f(1, 2, 3, 4, 5.0, 6.0) - self.assertEqual(result, 21) - self.assertEqual(type(result), int) - - result = f(1, 2, 3, 0x10004, 5.0, 6.0) - self.assertEqual(result, 21) - self.assertEqual(type(result), int) - - # You cannot assign character format codes as restype any longer - self.assertRaises(TypeError, setattr, f, "restype", "i") - - def test_floatresult(self): - f = dll._testfunc_f_bhilfd - f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] - f.restype = c_float - result = f(1, 2, 3, 4, 5.0, 6.0) - self.assertEqual(result, 21) - self.assertEqual(type(result), float) - - result = f(-1, -2, -3, -4, -5.0, -6.0) - self.assertEqual(result, -21) - self.assertEqual(type(result), float) - - def test_doubleresult(self): - f = dll._testfunc_d_bhilfd - f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] - f.restype = c_double - result = f(1, 2, 3, 4, 5.0, 6.0) - self.assertEqual(result, 21) - self.assertEqual(type(result), float) - - result = f(-1, -2, -3, -4, -5.0, -6.0) - self.assertEqual(result, -21) - self.assertEqual(type(result), float) - - @need_symbol('c_longdouble') - def test_longdoubleresult(self): - f = dll._testfunc_D_bhilfD - f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_longdouble] - f.restype = c_longdouble - result = f(1, 2, 3, 4, 5.0, 6.0) - self.assertEqual(result, 21) - self.assertEqual(type(result), float) - - result = f(-1, -2, -3, -4, -5.0, -6.0) - self.assertEqual(result, -21) - self.assertEqual(type(result), float) - - @need_symbol('c_longlong') - def test_longlongresult(self): - f = dll._testfunc_q_bhilfd - f.restype = c_longlong - f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] - result = f(1, 2, 3, 4, 5.0, 6.0) - self.assertEqual(result, 21) - - f = dll._testfunc_q_bhilfdq - f.restype = c_longlong - f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double, c_longlong] - result = f(1, 2, 3, 4, 5.0, 6.0, 21) - self.assertEqual(result, 42) - - def test_stringresult(self): - f = dll._testfunc_p_p - f.argtypes = None - f.restype = c_char_p - result = f(b"123") - self.assertEqual(result, b"123") - - result = f(None) - self.assertEqual(result, None) - - def test_pointers(self): - f = dll._testfunc_p_p - f.restype = POINTER(c_int) - f.argtypes = [POINTER(c_int)] - - # This only works if the value c_int(42) passed to the - # function is still alive while the pointer (the result) is - # used. - - v = c_int(42) - - self.assertEqual(pointer(v).contents.value, 42) - result = f(pointer(v)) - self.assertEqual(type(result), POINTER(c_int)) - self.assertEqual(result.contents.value, 42) - - # This on works... - result = f(pointer(v)) - self.assertEqual(result.contents.value, v.value) - - p = pointer(c_int(99)) - result = f(p) - self.assertEqual(result.contents.value, 99) - - arg = byref(v) - result = f(arg) - self.assertNotEqual(result.contents, v.value) - - self.assertRaises(ArgumentError, f, byref(c_short(22))) - - # It is dangerous, however, because you don't control the lifetime - # of the pointer: - result = f(byref(c_int(99))) - self.assertNotEqual(result.contents, 99) - - ################################################################ - def test_shorts(self): - f = dll._testfunc_callback_i_if - - args = [] - expected = [262144, 131072, 65536, 32768, 16384, 8192, 4096, 2048, - 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1] - - def callback(v): - args.append(v) - return v - - CallBack = CFUNCTYPE(c_int, c_int) - - cb = CallBack(callback) - f(2**18, cb) - self.assertEqual(args, expected) - - ################################################################ - - - def test_callbacks(self): - f = dll._testfunc_callback_i_if - f.restype = c_int - f.argtypes = None - - MyCallback = CFUNCTYPE(c_int, c_int) - - def callback(value): - #print "called back with", value - return value - - cb = MyCallback(callback) - result = f(-10, cb) - self.assertEqual(result, -18) - - # test with prototype - f.argtypes = [c_int, MyCallback] - cb = MyCallback(callback) - result = f(-10, cb) - self.assertEqual(result, -18) - - AnotherCallback = WINFUNCTYPE(c_int, c_int, c_int, c_int, c_int) - - # check that the prototype works: we call f with wrong - # argument types - cb = AnotherCallback(callback) - self.assertRaises(ArgumentError, f, -10, cb) - - - def test_callbacks_2(self): - # Can also use simple datatypes as argument type specifiers - # for the callback function. - # In this case the call receives an instance of that type - f = dll._testfunc_callback_i_if - f.restype = c_int - - MyCallback = CFUNCTYPE(c_int, c_int) - - f.argtypes = [c_int, MyCallback] - - def callback(value): - #print "called back with", value - self.assertEqual(type(value), int) - return value - - cb = MyCallback(callback) - result = f(-10, cb) - self.assertEqual(result, -18) - - @need_symbol('c_longlong') - def test_longlong_callbacks(self): - - f = dll._testfunc_callback_q_qf - f.restype = c_longlong - - MyCallback = CFUNCTYPE(c_longlong, c_longlong) - - f.argtypes = [c_longlong, MyCallback] - - def callback(value): - self.assertIsInstance(value, int) - return value & 0x7FFFFFFF - - cb = MyCallback(callback) - - self.assertEqual(13577625587, f(1000000000000, cb)) - - def test_errors(self): - self.assertRaises(AttributeError, getattr, dll, "_xxx_yyy") - self.assertRaises(ValueError, c_int.in_dll, dll, "_xxx_yyy") - - def test_byval(self): - - # without prototype - ptin = POINT(1, 2) - ptout = POINT() - # EXPORT int _testfunc_byval(point in, point *pout) - result = dll._testfunc_byval(ptin, byref(ptout)) - got = result, ptout.x, ptout.y - expected = 3, 1, 2 - self.assertEqual(got, expected) - - # with prototype - ptin = POINT(101, 102) - ptout = POINT() - dll._testfunc_byval.argtypes = (POINT, POINTER(POINT)) - dll._testfunc_byval.restype = c_int - result = dll._testfunc_byval(ptin, byref(ptout)) - got = result, ptout.x, ptout.y - expected = 203, 101, 102 - self.assertEqual(got, expected) - - def test_struct_return_2H(self): - class S2H(Structure): - _fields_ = [("x", c_short), - ("y", c_short)] - dll.ret_2h_func.restype = S2H - dll.ret_2h_func.argtypes = [S2H] - inp = S2H(99, 88) - s2h = dll.ret_2h_func(inp) - self.assertEqual((s2h.x, s2h.y), (99*2, 88*3)) - - @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') - def test_struct_return_2H_stdcall(self): - class S2H(Structure): - _fields_ = [("x", c_short), - ("y", c_short)] - - windll.s_ret_2h_func.restype = S2H - windll.s_ret_2h_func.argtypes = [S2H] - s2h = windll.s_ret_2h_func(S2H(99, 88)) - self.assertEqual((s2h.x, s2h.y), (99*2, 88*3)) - - def test_struct_return_8H(self): - class S8I(Structure): - _fields_ = [("a", c_int), - ("b", c_int), - ("c", c_int), - ("d", c_int), - ("e", c_int), - ("f", c_int), - ("g", c_int), - ("h", c_int)] - dll.ret_8i_func.restype = S8I - dll.ret_8i_func.argtypes = [S8I] - inp = S8I(9, 8, 7, 6, 5, 4, 3, 2) - s8i = dll.ret_8i_func(inp) - self.assertEqual((s8i.a, s8i.b, s8i.c, s8i.d, s8i.e, s8i.f, s8i.g, s8i.h), - (9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9)) - - @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') - def test_struct_return_8H_stdcall(self): - class S8I(Structure): - _fields_ = [("a", c_int), - ("b", c_int), - ("c", c_int), - ("d", c_int), - ("e", c_int), - ("f", c_int), - ("g", c_int), - ("h", c_int)] - windll.s_ret_8i_func.restype = S8I - windll.s_ret_8i_func.argtypes = [S8I] - inp = S8I(9, 8, 7, 6, 5, 4, 3, 2) - s8i = windll.s_ret_8i_func(inp) - self.assertEqual( - (s8i.a, s8i.b, s8i.c, s8i.d, s8i.e, s8i.f, s8i.g, s8i.h), - (9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9)) - - def test_sf1651235(self): - # see https://www.python.org/sf/1651235 - - proto = CFUNCTYPE(c_int, RECT, POINT) - def callback(*args): - return 0 - - callback = proto(callback) - self.assertRaises(ArgumentError, lambda: callback((1, 2, 3, 4), POINT())) - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_incomplete.py b/Lib/ctypes/test/test_incomplete.py deleted file mode 100644 index 00c430ef53..0000000000 --- a/Lib/ctypes/test/test_incomplete.py +++ /dev/null @@ -1,42 +0,0 @@ -import unittest -from ctypes import * - -################################################################ -# -# The incomplete pointer example from the tutorial -# - -class MyTestCase(unittest.TestCase): - - def test_incomplete_example(self): - lpcell = POINTER("cell") - class cell(Structure): - _fields_ = [("name", c_char_p), - ("next", lpcell)] - - SetPointerType(lpcell, cell) - - c1 = cell() - c1.name = b"foo" - c2 = cell() - c2.name = b"bar" - - c1.next = pointer(c2) - c2.next = pointer(c1) - - p = c1 - - result = [] - for i in range(8): - result.append(p.name) - p = p.next[0] - self.assertEqual(result, [b"foo", b"bar"] * 4) - - # to not leak references, we must clean _pointer_type_cache - from ctypes import _pointer_type_cache - del _pointer_type_cache[cell] - -################################################################ - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_init.py b/Lib/ctypes/test/test_init.py deleted file mode 100644 index 75fad112a0..0000000000 --- a/Lib/ctypes/test/test_init.py +++ /dev/null @@ -1,40 +0,0 @@ -from ctypes import * -import unittest - -class X(Structure): - _fields_ = [("a", c_int), - ("b", c_int)] - new_was_called = False - - def __new__(cls): - result = super().__new__(cls) - result.new_was_called = True - return result - - def __init__(self): - self.a = 9 - self.b = 12 - -class Y(Structure): - _fields_ = [("x", X)] - - -class InitTest(unittest.TestCase): - def test_get(self): - # make sure the only accessing a nested structure - # doesn't call the structure's __new__ and __init__ - y = Y() - self.assertEqual((y.x.a, y.x.b), (0, 0)) - self.assertEqual(y.x.new_was_called, False) - - # But explicitly creating an X structure calls __new__ and __init__, of course. - x = X() - self.assertEqual((x.a, x.b), (9, 12)) - self.assertEqual(x.new_was_called, True) - - y.x = x - self.assertEqual((y.x.a, y.x.b), (9, 12)) - self.assertEqual(y.x.new_was_called, False) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_internals.py b/Lib/ctypes/test/test_internals.py deleted file mode 100644 index 271e3f57f8..0000000000 --- a/Lib/ctypes/test/test_internals.py +++ /dev/null @@ -1,100 +0,0 @@ -# This tests the internal _objects attribute -import unittest -from ctypes import * -from sys import getrefcount as grc - -# XXX This test must be reviewed for correctness!!! - -# ctypes' types are container types. -# -# They have an internal memory block, which only consists of some bytes, -# but it has to keep references to other objects as well. This is not -# really needed for trivial C types like int or char, but it is important -# for aggregate types like strings or pointers in particular. -# -# What about pointers? - -class ObjectsTestCase(unittest.TestCase): - def assertSame(self, a, b): - self.assertEqual(id(a), id(b)) - - def test_ints(self): - i = 42000123 - refcnt = grc(i) - ci = c_int(i) - self.assertEqual(refcnt, grc(i)) - self.assertEqual(ci._objects, None) - - def test_c_char_p(self): - s = b"Hello, World" - refcnt = grc(s) - cs = c_char_p(s) - self.assertEqual(refcnt + 1, grc(s)) - self.assertSame(cs._objects, s) - - def test_simple_struct(self): - class X(Structure): - _fields_ = [("a", c_int), ("b", c_int)] - - a = 421234 - b = 421235 - x = X() - self.assertEqual(x._objects, None) - x.a = a - x.b = b - self.assertEqual(x._objects, None) - - def test_embedded_structs(self): - class X(Structure): - _fields_ = [("a", c_int), ("b", c_int)] - - class Y(Structure): - _fields_ = [("x", X), ("y", X)] - - y = Y() - self.assertEqual(y._objects, None) - - x1, x2 = X(), X() - y.x, y.y = x1, x2 - self.assertEqual(y._objects, {"0": {}, "1": {}}) - x1.a, x2.b = 42, 93 - self.assertEqual(y._objects, {"0": {}, "1": {}}) - - def test_xxx(self): - class X(Structure): - _fields_ = [("a", c_char_p), ("b", c_char_p)] - - class Y(Structure): - _fields_ = [("x", X), ("y", X)] - - s1 = b"Hello, World" - s2 = b"Hallo, Welt" - - x = X() - x.a = s1 - x.b = s2 - self.assertEqual(x._objects, {"0": s1, "1": s2}) - - y = Y() - y.x = x - self.assertEqual(y._objects, {"0": {"0": s1, "1": s2}}) -## x = y.x -## del y -## print x._b_base_._objects - - def test_ptr_struct(self): - class X(Structure): - _fields_ = [("data", POINTER(c_int))] - - A = c_int*4 - a = A(11, 22, 33, 44) - self.assertEqual(a._objects, None) - - x = X() - x.data = a -##XXX print x._objects -##XXX print x.data[0] -##XXX print x.data._objects - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_keeprefs.py b/Lib/ctypes/test/test_keeprefs.py deleted file mode 100644 index 94c02573fa..0000000000 --- a/Lib/ctypes/test/test_keeprefs.py +++ /dev/null @@ -1,153 +0,0 @@ -from ctypes import * -import unittest - -class SimpleTestCase(unittest.TestCase): - def test_cint(self): - x = c_int() - self.assertEqual(x._objects, None) - x.value = 42 - self.assertEqual(x._objects, None) - x = c_int(99) - self.assertEqual(x._objects, None) - - def test_ccharp(self): - x = c_char_p() - self.assertEqual(x._objects, None) - x.value = b"abc" - self.assertEqual(x._objects, b"abc") - x = c_char_p(b"spam") - self.assertEqual(x._objects, b"spam") - -class StructureTestCase(unittest.TestCase): - def test_cint_struct(self): - class X(Structure): - _fields_ = [("a", c_int), - ("b", c_int)] - - x = X() - self.assertEqual(x._objects, None) - x.a = 42 - x.b = 99 - self.assertEqual(x._objects, None) - - def test_ccharp_struct(self): - class X(Structure): - _fields_ = [("a", c_char_p), - ("b", c_char_p)] - x = X() - self.assertEqual(x._objects, None) - - x.a = b"spam" - x.b = b"foo" - self.assertEqual(x._objects, {"0": b"spam", "1": b"foo"}) - - def test_struct_struct(self): - class POINT(Structure): - _fields_ = [("x", c_int), ("y", c_int)] - class RECT(Structure): - _fields_ = [("ul", POINT), ("lr", POINT)] - - r = RECT() - r.ul.x = 0 - r.ul.y = 1 - r.lr.x = 2 - r.lr.y = 3 - self.assertEqual(r._objects, None) - - r = RECT() - pt = POINT(1, 2) - r.ul = pt - self.assertEqual(r._objects, {'0': {}}) - r.ul.x = 22 - r.ul.y = 44 - self.assertEqual(r._objects, {'0': {}}) - r.lr = POINT() - self.assertEqual(r._objects, {'0': {}, '1': {}}) - -class ArrayTestCase(unittest.TestCase): - def test_cint_array(self): - INTARR = c_int * 3 - - ia = INTARR() - self.assertEqual(ia._objects, None) - ia[0] = 1 - ia[1] = 2 - ia[2] = 3 - self.assertEqual(ia._objects, None) - - class X(Structure): - _fields_ = [("x", c_int), - ("a", INTARR)] - - x = X() - x.x = 1000 - x.a[0] = 42 - x.a[1] = 96 - self.assertEqual(x._objects, None) - x.a = ia - self.assertEqual(x._objects, {'1': {}}) - -class PointerTestCase(unittest.TestCase): - def test_p_cint(self): - i = c_int(42) - x = pointer(i) - self.assertEqual(x._objects, {'1': i}) - -class DeletePointerTestCase(unittest.TestCase): - @unittest.skip('test disabled') - def test_X(self): - class X(Structure): - _fields_ = [("p", POINTER(c_char_p))] - x = X() - i = c_char_p("abc def") - from sys import getrefcount as grc - print("2?", grc(i)) - x.p = pointer(i) - print("3?", grc(i)) - for i in range(320): - c_int(99) - x.p[0] - print(x.p[0]) -## del x -## print "2?", grc(i) -## del i - import gc - gc.collect() - for i in range(320): - c_int(99) - x.p[0] - print(x.p[0]) - print(x.p.contents) -## print x._objects - - x.p[0] = "spam spam" -## print x.p[0] - print("+" * 42) - print(x._objects) - -class PointerToStructure(unittest.TestCase): - def test(self): - class POINT(Structure): - _fields_ = [("x", c_int), ("y", c_int)] - class RECT(Structure): - _fields_ = [("a", POINTER(POINT)), - ("b", POINTER(POINT))] - r = RECT() - p1 = POINT(1, 2) - - r.a = pointer(p1) - r.b = pointer(p1) -## from pprint import pprint as pp -## pp(p1._objects) -## pp(r._objects) - - r.a[0].x = 42 - r.a[0].y = 99 - - # to avoid leaking when tests are run several times - # clean up the types left in the cache. - from ctypes import _pointer_type_cache - del _pointer_type_cache[POINT] - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_libc.py b/Lib/ctypes/test/test_libc.py deleted file mode 100644 index 56285b5ff8..0000000000 --- a/Lib/ctypes/test/test_libc.py +++ /dev/null @@ -1,33 +0,0 @@ -import unittest - -from ctypes import * -import _ctypes_test - -lib = CDLL(_ctypes_test.__file__) - -def three_way_cmp(x, y): - """Return -1 if x < y, 0 if x == y and 1 if x > y""" - return (x > y) - (x < y) - -class LibTest(unittest.TestCase): - def test_sqrt(self): - lib.my_sqrt.argtypes = c_double, - lib.my_sqrt.restype = c_double - self.assertEqual(lib.my_sqrt(4.0), 2.0) - import math - self.assertEqual(lib.my_sqrt(2.0), math.sqrt(2.0)) - - def test_qsort(self): - comparefunc = CFUNCTYPE(c_int, POINTER(c_char), POINTER(c_char)) - lib.my_qsort.argtypes = c_void_p, c_size_t, c_size_t, comparefunc - lib.my_qsort.restype = None - - def sort(a, b): - return three_way_cmp(a[0], b[0]) - - chars = create_string_buffer(b"spam, spam, and spam") - lib.my_qsort(chars, len(chars)-1, sizeof(c_char), comparefunc(sort)) - self.assertEqual(chars.raw, b" ,,aaaadmmmnpppsss\x00") - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_loading.py b/Lib/ctypes/test/test_loading.py deleted file mode 100644 index ea892277c4..0000000000 --- a/Lib/ctypes/test/test_loading.py +++ /dev/null @@ -1,182 +0,0 @@ -from ctypes import * -import os -import shutil -import subprocess -import sys -import unittest -import test.support -from test.support import import_helper -from test.support import os_helper -from ctypes.util import find_library - -libc_name = None - -def setUpModule(): - global libc_name - if os.name == "nt": - libc_name = find_library("c") - elif sys.platform == "cygwin": - libc_name = "cygwin1.dll" - else: - libc_name = find_library("c") - - if test.support.verbose: - print("libc_name is", libc_name) - -class LoaderTest(unittest.TestCase): - - unknowndll = "xxrandomnamexx" - - def test_load(self): - if libc_name is None: - self.skipTest('could not find libc') - CDLL(libc_name) - CDLL(os.path.basename(libc_name)) - self.assertRaises(OSError, CDLL, self.unknowndll) - - def test_load_version(self): - if libc_name is None: - self.skipTest('could not find libc') - if os.path.basename(libc_name) != 'libc.so.6': - self.skipTest('wrong libc path for test') - cdll.LoadLibrary("libc.so.6") - # linux uses version, libc 9 should not exist - self.assertRaises(OSError, cdll.LoadLibrary, "libc.so.9") - self.assertRaises(OSError, cdll.LoadLibrary, self.unknowndll) - - def test_find(self): - for name in ("c", "m"): - lib = find_library(name) - if lib: - cdll.LoadLibrary(lib) - CDLL(lib) - - @unittest.skipUnless(os.name == "nt", - 'test specific to Windows') - def test_load_library(self): - # CRT is no longer directly loadable. See issue23606 for the - # discussion about alternative approaches. - #self.assertIsNotNone(libc_name) - if test.support.verbose: - print(find_library("kernel32")) - print(find_library("user32")) - - if os.name == "nt": - windll.kernel32.GetModuleHandleW - windll["kernel32"].GetModuleHandleW - windll.LoadLibrary("kernel32").GetModuleHandleW - WinDLL("kernel32").GetModuleHandleW - # embedded null character - self.assertRaises(ValueError, windll.LoadLibrary, "kernel32\0") - - @unittest.skipUnless(os.name == "nt", - 'test specific to Windows') - def test_load_ordinal_functions(self): - import _ctypes_test - dll = WinDLL(_ctypes_test.__file__) - # We load the same function both via ordinal and name - func_ord = dll[2] - func_name = dll.GetString - # addressof gets the address where the function pointer is stored - a_ord = addressof(func_ord) - a_name = addressof(func_name) - f_ord_addr = c_void_p.from_address(a_ord).value - f_name_addr = c_void_p.from_address(a_name).value - self.assertEqual(hex(f_ord_addr), hex(f_name_addr)) - - self.assertRaises(AttributeError, dll.__getitem__, 1234) - - @unittest.skipUnless(os.name == "nt", 'Windows-specific test') - def test_1703286_A(self): - from _ctypes import LoadLibrary, FreeLibrary - # On winXP 64-bit, advapi32 loads at an address that does - # NOT fit into a 32-bit integer. FreeLibrary must be able - # to accept this address. - - # These are tests for https://www.python.org/sf/1703286 - handle = LoadLibrary("advapi32") - FreeLibrary(handle) - - @unittest.skipUnless(os.name == "nt", 'Windows-specific test') - def test_1703286_B(self): - # Since on winXP 64-bit advapi32 loads like described - # above, the (arbitrarily selected) CloseEventLog function - # also has a high address. 'call_function' should accept - # addresses so large. - from _ctypes import call_function - advapi32 = windll.advapi32 - # Calling CloseEventLog with a NULL argument should fail, - # but the call should not segfault or so. - self.assertEqual(0, advapi32.CloseEventLog(None)) - windll.kernel32.GetProcAddress.argtypes = c_void_p, c_char_p - windll.kernel32.GetProcAddress.restype = c_void_p - proc = windll.kernel32.GetProcAddress(advapi32._handle, - b"CloseEventLog") - self.assertTrue(proc) - # This is the real test: call the function via 'call_function' - self.assertEqual(0, call_function(proc, (None,))) - - @unittest.skipUnless(os.name == "nt", - 'test specific to Windows') - def test_load_dll_with_flags(self): - _sqlite3 = import_helper.import_module("_sqlite3") - src = _sqlite3.__file__ - if src.lower().endswith("_d.pyd"): - ext = "_d.dll" - else: - ext = ".dll" - - with os_helper.temp_dir() as tmp: - # We copy two files and load _sqlite3.dll (formerly .pyd), - # which has a dependency on sqlite3.dll. Then we test - # loading it in subprocesses to avoid it starting in memory - # for each test. - target = os.path.join(tmp, "_sqlite3.dll") - shutil.copy(src, target) - shutil.copy(os.path.join(os.path.dirname(src), "sqlite3" + ext), - os.path.join(tmp, "sqlite3" + ext)) - - def should_pass(command): - with self.subTest(command): - subprocess.check_output( - [sys.executable, "-c", - "from ctypes import *; import nt;" + command], - cwd=tmp - ) - - def should_fail(command): - with self.subTest(command): - with self.assertRaises(subprocess.CalledProcessError): - subprocess.check_output( - [sys.executable, "-c", - "from ctypes import *; import nt;" + command], - cwd=tmp, stderr=subprocess.STDOUT, - ) - - # Default load should not find this in CWD - should_fail("WinDLL('_sqlite3.dll')") - - # Relative path (but not just filename) should succeed - should_pass("WinDLL('./_sqlite3.dll')") - - # Insecure load flags should succeed - # Clear the DLL directory to avoid safe search settings propagating - should_pass("windll.kernel32.SetDllDirectoryW(None); WinDLL('_sqlite3.dll', winmode=0)") - - # Full path load without DLL_LOAD_DIR shouldn't find dependency - should_fail("WinDLL(nt._getfullpathname('_sqlite3.dll'), " + - "winmode=nt._LOAD_LIBRARY_SEARCH_SYSTEM32)") - - # Full path load with DLL_LOAD_DIR should succeed - should_pass("WinDLL(nt._getfullpathname('_sqlite3.dll'), " + - "winmode=nt._LOAD_LIBRARY_SEARCH_SYSTEM32|" + - "nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR)") - - # User-specified directory should succeed - should_pass("import os; p = os.add_dll_directory(os.getcwd());" + - "WinDLL('_sqlite3.dll'); p.close()") - - - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_macholib.py b/Lib/ctypes/test/test_macholib.py deleted file mode 100644 index bc75f1a05a..0000000000 --- a/Lib/ctypes/test/test_macholib.py +++ /dev/null @@ -1,110 +0,0 @@ -import os -import sys -import unittest - -# Bob Ippolito: -# -# Ok.. the code to find the filename for __getattr__ should look -# something like: -# -# import os -# from macholib.dyld import dyld_find -# -# def find_lib(name): -# possible = ['lib'+name+'.dylib', name+'.dylib', -# name+'.framework/'+name] -# for dylib in possible: -# try: -# return os.path.realpath(dyld_find(dylib)) -# except ValueError: -# pass -# raise ValueError, "%s not found" % (name,) -# -# It'll have output like this: -# -# >>> find_lib('pthread') -# '/usr/lib/libSystem.B.dylib' -# >>> find_lib('z') -# '/usr/lib/libz.1.dylib' -# >>> find_lib('IOKit') -# '/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit' -# -# -bob - -from ctypes.macholib.dyld import dyld_find -from ctypes.macholib.dylib import dylib_info -from ctypes.macholib.framework import framework_info - -def find_lib(name): - possible = ['lib'+name+'.dylib', name+'.dylib', name+'.framework/'+name] - for dylib in possible: - try: - return os.path.realpath(dyld_find(dylib)) - except ValueError: - pass - raise ValueError("%s not found" % (name,)) - - -def d(location=None, name=None, shortname=None, version=None, suffix=None): - return {'location': location, 'name': name, 'shortname': shortname, - 'version': version, 'suffix': suffix} - - -class MachOTest(unittest.TestCase): - @unittest.skipUnless(sys.platform == "darwin", 'OSX-specific test') - def test_find(self): - self.assertEqual(dyld_find('libSystem.dylib'), - '/usr/lib/libSystem.dylib') - self.assertEqual(dyld_find('System.framework/System'), - '/System/Library/Frameworks/System.framework/System') - - # On Mac OS 11, system dylibs are only present in the shared cache, - # so symlinks like libpthread.dylib -> libSystem.B.dylib will not - # be resolved by dyld_find - self.assertIn(find_lib('pthread'), - ('/usr/lib/libSystem.B.dylib', '/usr/lib/libpthread.dylib')) - - result = find_lib('z') - # Issue #21093: dyld default search path includes $HOME/lib and - # /usr/local/lib before /usr/lib, which caused test failures if - # a local copy of libz exists in one of them. Now ignore the head - # of the path. - self.assertRegex(result, r".*/lib/libz.*\.dylib") - - self.assertIn(find_lib('IOKit'), - ('/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit', - '/System/Library/Frameworks/IOKit.framework/IOKit')) - - @unittest.skipUnless(sys.platform == "darwin", 'OSX-specific test') - def test_info(self): - self.assertIsNone(dylib_info('completely/invalid')) - self.assertIsNone(dylib_info('completely/invalide_debug')) - self.assertEqual(dylib_info('P/Foo.dylib'), d('P', 'Foo.dylib', 'Foo')) - self.assertEqual(dylib_info('P/Foo_debug.dylib'), - d('P', 'Foo_debug.dylib', 'Foo', suffix='debug')) - self.assertEqual(dylib_info('P/Foo.A.dylib'), - d('P', 'Foo.A.dylib', 'Foo', 'A')) - self.assertEqual(dylib_info('P/Foo_debug.A.dylib'), - d('P', 'Foo_debug.A.dylib', 'Foo_debug', 'A')) - self.assertEqual(dylib_info('P/Foo.A_debug.dylib'), - d('P', 'Foo.A_debug.dylib', 'Foo', 'A', 'debug')) - - @unittest.skipUnless(sys.platform == "darwin", 'OSX-specific test') - def test_framework_info(self): - self.assertIsNone(framework_info('completely/invalid')) - self.assertIsNone(framework_info('completely/invalid/_debug')) - self.assertIsNone(framework_info('P/F.framework')) - self.assertIsNone(framework_info('P/F.framework/_debug')) - self.assertEqual(framework_info('P/F.framework/F'), - d('P', 'F.framework/F', 'F')) - self.assertEqual(framework_info('P/F.framework/F_debug'), - d('P', 'F.framework/F_debug', 'F', suffix='debug')) - self.assertIsNone(framework_info('P/F.framework/Versions')) - self.assertIsNone(framework_info('P/F.framework/Versions/A')) - self.assertEqual(framework_info('P/F.framework/Versions/A/F'), - d('P', 'F.framework/Versions/A/F', 'F', 'A')) - self.assertEqual(framework_info('P/F.framework/Versions/A/F_debug'), - d('P', 'F.framework/Versions/A/F_debug', 'F', 'A', 'debug')) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_memfunctions.py b/Lib/ctypes/test/test_memfunctions.py deleted file mode 100644 index e784b9a706..0000000000 --- a/Lib/ctypes/test/test_memfunctions.py +++ /dev/null @@ -1,79 +0,0 @@ -import sys -from test import support -import unittest -from ctypes import * -from ctypes.test import need_symbol - -class MemFunctionsTest(unittest.TestCase): - @unittest.skip('test disabled') - def test_overflow(self): - # string_at and wstring_at must use the Python calling - # convention (which acquires the GIL and checks the Python - # error flag). Provoke an error and catch it; see also issue - # #3554: - self.assertRaises((OverflowError, MemoryError, SystemError), - lambda: wstring_at(u"foo", sys.maxint - 1)) - self.assertRaises((OverflowError, MemoryError, SystemError), - lambda: string_at("foo", sys.maxint - 1)) - - def test_memmove(self): - # large buffers apparently increase the chance that the memory - # is allocated in high address space. - a = create_string_buffer(1000000) - p = b"Hello, World" - result = memmove(a, p, len(p)) - self.assertEqual(a.value, b"Hello, World") - - self.assertEqual(string_at(result), b"Hello, World") - self.assertEqual(string_at(result, 5), b"Hello") - self.assertEqual(string_at(result, 16), b"Hello, World\0\0\0\0") - self.assertEqual(string_at(result, 0), b"") - - def test_memset(self): - a = create_string_buffer(1000000) - result = memset(a, ord('x'), 16) - self.assertEqual(a.value, b"xxxxxxxxxxxxxxxx") - - self.assertEqual(string_at(result), b"xxxxxxxxxxxxxxxx") - self.assertEqual(string_at(a), b"xxxxxxxxxxxxxxxx") - self.assertEqual(string_at(a, 20), b"xxxxxxxxxxxxxxxx\0\0\0\0") - - def test_cast(self): - a = (c_ubyte * 32)(*map(ord, "abcdef")) - self.assertEqual(cast(a, c_char_p).value, b"abcdef") - self.assertEqual(cast(a, POINTER(c_byte))[:7], - [97, 98, 99, 100, 101, 102, 0]) - self.assertEqual(cast(a, POINTER(c_byte))[:7:], - [97, 98, 99, 100, 101, 102, 0]) - self.assertEqual(cast(a, POINTER(c_byte))[6:-1:-1], - [0, 102, 101, 100, 99, 98, 97]) - self.assertEqual(cast(a, POINTER(c_byte))[:7:2], - [97, 99, 101, 0]) - self.assertEqual(cast(a, POINTER(c_byte))[:7:7], - [97]) - - @support.refcount_test - def test_string_at(self): - s = string_at(b"foo bar") - # XXX The following may be wrong, depending on how Python - # manages string instances - self.assertEqual(2, sys.getrefcount(s)) - self.assertTrue(s, "foo bar") - - self.assertEqual(string_at(b"foo bar", 7), b"foo bar") - self.assertEqual(string_at(b"foo bar", 3), b"foo") - - @need_symbol('create_unicode_buffer') - def test_wstring_at(self): - p = create_unicode_buffer("Hello, World") - a = create_unicode_buffer(1000000) - result = memmove(a, p, len(p) * sizeof(c_wchar)) - self.assertEqual(a.value, "Hello, World") - - self.assertEqual(wstring_at(a), "Hello, World") - self.assertEqual(wstring_at(a, 5), "Hello") - self.assertEqual(wstring_at(a, 16), "Hello, World\0\0\0\0") - self.assertEqual(wstring_at(a, 0), "") - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_numbers.py b/Lib/ctypes/test/test_numbers.py deleted file mode 100644 index a5c661b0e9..0000000000 --- a/Lib/ctypes/test/test_numbers.py +++ /dev/null @@ -1,218 +0,0 @@ -from ctypes import * -import unittest -import struct - -def valid_ranges(*types): - # given a sequence of numeric types, collect their _type_ - # attribute, which is a single format character compatible with - # the struct module, use the struct module to calculate the - # minimum and maximum value allowed for this format. - # Returns a list of (min, max) values. - result = [] - for t in types: - fmt = t._type_ - size = struct.calcsize(fmt) - a = struct.unpack(fmt, (b"\x00"*32)[:size])[0] - b = struct.unpack(fmt, (b"\xFF"*32)[:size])[0] - c = struct.unpack(fmt, (b"\x7F"+b"\x00"*32)[:size])[0] - d = struct.unpack(fmt, (b"\x80"+b"\xFF"*32)[:size])[0] - result.append((min(a, b, c, d), max(a, b, c, d))) - return result - -ArgType = type(byref(c_int(0))) - -unsigned_types = [c_ubyte, c_ushort, c_uint, c_ulong] -signed_types = [c_byte, c_short, c_int, c_long, c_longlong] - -bool_types = [] - -float_types = [c_double, c_float] - -try: - c_ulonglong - c_longlong -except NameError: - pass -else: - unsigned_types.append(c_ulonglong) - signed_types.append(c_longlong) - -try: - c_bool -except NameError: - pass -else: - bool_types.append(c_bool) - -unsigned_ranges = valid_ranges(*unsigned_types) -signed_ranges = valid_ranges(*signed_types) -bool_values = [True, False, 0, 1, -1, 5000, 'test', [], [1]] - -################################################################ - -class NumberTestCase(unittest.TestCase): - - def test_default_init(self): - # default values are set to zero - for t in signed_types + unsigned_types + float_types: - self.assertEqual(t().value, 0) - - def test_unsigned_values(self): - # the value given to the constructor is available - # as the 'value' attribute - for t, (l, h) in zip(unsigned_types, unsigned_ranges): - self.assertEqual(t(l).value, l) - self.assertEqual(t(h).value, h) - - def test_signed_values(self): - # see above - for t, (l, h) in zip(signed_types, signed_ranges): - self.assertEqual(t(l).value, l) - self.assertEqual(t(h).value, h) - - def test_bool_values(self): - from operator import truth - for t, v in zip(bool_types, bool_values): - self.assertEqual(t(v).value, truth(v)) - - def test_typeerror(self): - # Only numbers are allowed in the constructor, - # otherwise TypeError is raised - for t in signed_types + unsigned_types + float_types: - self.assertRaises(TypeError, t, "") - self.assertRaises(TypeError, t, None) - - def test_from_param(self): - # the from_param class method attribute always - # returns PyCArgObject instances - for t in signed_types + unsigned_types + float_types: - self.assertEqual(ArgType, type(t.from_param(0))) - - def test_byref(self): - # calling byref returns also a PyCArgObject instance - for t in signed_types + unsigned_types + float_types + bool_types: - parm = byref(t()) - self.assertEqual(ArgType, type(parm)) - - - def test_floats(self): - # c_float and c_double can be created from - # Python int and float - class FloatLike: - def __float__(self): - return 2.0 - f = FloatLike() - for t in float_types: - self.assertEqual(t(2.0).value, 2.0) - self.assertEqual(t(2).value, 2.0) - self.assertEqual(t(2).value, 2.0) - self.assertEqual(t(f).value, 2.0) - - def test_integers(self): - class FloatLike: - def __float__(self): - return 2.0 - f = FloatLike() - class IntLike: - def __int__(self): - return 2 - d = IntLike() - class IndexLike: - def __index__(self): - return 2 - i = IndexLike() - # integers cannot be constructed from floats, - # but from integer-like objects - for t in signed_types + unsigned_types: - self.assertRaises(TypeError, t, 3.14) - self.assertRaises(TypeError, t, f) - self.assertRaises(TypeError, t, d) - self.assertEqual(t(i).value, 2) - - def test_sizes(self): - for t in signed_types + unsigned_types + float_types + bool_types: - try: - size = struct.calcsize(t._type_) - except struct.error: - continue - # sizeof of the type... - self.assertEqual(sizeof(t), size) - # and sizeof of an instance - self.assertEqual(sizeof(t()), size) - - def test_alignments(self): - for t in signed_types + unsigned_types + float_types: - code = t._type_ # the typecode - align = struct.calcsize("c%c" % code) - struct.calcsize(code) - - # alignment of the type... - self.assertEqual((code, alignment(t)), - (code, align)) - # and alignment of an instance - self.assertEqual((code, alignment(t())), - (code, align)) - - def test_int_from_address(self): - from array import array - for t in signed_types + unsigned_types: - # the array module doesn't support all format codes - # (no 'q' or 'Q') - try: - array(t._type_) - except ValueError: - continue - a = array(t._type_, [100]) - - # v now is an integer at an 'external' memory location - v = t.from_address(a.buffer_info()[0]) - self.assertEqual(v.value, a[0]) - self.assertEqual(type(v), t) - - # changing the value at the memory location changes v's value also - a[0] = 42 - self.assertEqual(v.value, a[0]) - - - def test_float_from_address(self): - from array import array - for t in float_types: - a = array(t._type_, [3.14]) - v = t.from_address(a.buffer_info()[0]) - self.assertEqual(v.value, a[0]) - self.assertIs(type(v), t) - a[0] = 2.3456e17 - self.assertEqual(v.value, a[0]) - self.assertIs(type(v), t) - - def test_char_from_address(self): - from ctypes import c_char - from array import array - - a = array('b', [0]) - a[0] = ord('x') - v = c_char.from_address(a.buffer_info()[0]) - self.assertEqual(v.value, b'x') - self.assertIs(type(v), c_char) - - a[0] = ord('?') - self.assertEqual(v.value, b'?') - - def test_init(self): - # c_int() can be initialized from Python's int, and c_int. - # Not from c_long or so, which seems strange, abc should - # probably be changed: - self.assertRaises(TypeError, c_int, c_long(42)) - - def test_float_overflow(self): - import sys - big_int = int(sys.float_info.max) * 2 - for t in float_types + [c_longdouble]: - self.assertRaises(OverflowError, t, big_int) - if (hasattr(t, "__ctype_be__")): - self.assertRaises(OverflowError, t.__ctype_be__, big_int) - if (hasattr(t, "__ctype_le__")): - self.assertRaises(OverflowError, t.__ctype_le__, big_int) - - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_objects.py b/Lib/ctypes/test/test_objects.py deleted file mode 100644 index 19e3dc1f2d..0000000000 --- a/Lib/ctypes/test/test_objects.py +++ /dev/null @@ -1,67 +0,0 @@ -r''' -This tests the '_objects' attribute of ctypes instances. '_objects' -holds references to objects that must be kept alive as long as the -ctypes instance, to make sure that the memory buffer is valid. - -WARNING: The '_objects' attribute is exposed ONLY for debugging ctypes itself, -it MUST NEVER BE MODIFIED! - -'_objects' is initialized to a dictionary on first use, before that it -is None. - -Here is an array of string pointers: - ->>> from ctypes import * ->>> array = (c_char_p * 5)() ->>> print(array._objects) -None ->>> - -The memory block stores pointers to strings, and the strings itself -assigned from Python must be kept. - ->>> array[4] = b'foo bar' ->>> array._objects -{'4': b'foo bar'} ->>> array[4] -b'foo bar' ->>> - -It gets more complicated when the ctypes instance itself is contained -in a 'base' object. - ->>> class X(Structure): -... _fields_ = [("x", c_int), ("y", c_int), ("array", c_char_p * 5)] -... ->>> x = X() ->>> print(x._objects) -None ->>> - -The'array' attribute of the 'x' object shares part of the memory buffer -of 'x' ('_b_base_' is either None, or the root object owning the memory block): - ->>> print(x.array._b_base_) # doctest: +ELLIPSIS - ->>> - ->>> x.array[0] = b'spam spam spam' ->>> x._objects -{'0:2': b'spam spam spam'} ->>> x.array._b_base_._objects -{'0:2': b'spam spam spam'} ->>> - -''' - -import unittest, doctest - -import ctypes.test.test_objects - -class TestCase(unittest.TestCase): - def test(self): - failures, tests = doctest.testmod(ctypes.test.test_objects) - self.assertFalse(failures, 'doctests failed, see output above') - -if __name__ == '__main__': - doctest.testmod(ctypes.test.test_objects) diff --git a/Lib/ctypes/test/test_parameters.py b/Lib/ctypes/test/test_parameters.py deleted file mode 100644 index 38af7ac13d..0000000000 --- a/Lib/ctypes/test/test_parameters.py +++ /dev/null @@ -1,250 +0,0 @@ -import unittest -from ctypes.test import need_symbol -import test.support - -class SimpleTypesTestCase(unittest.TestCase): - - def setUp(self): - import ctypes - try: - from _ctypes import set_conversion_mode - except ImportError: - pass - else: - self.prev_conv_mode = set_conversion_mode("ascii", "strict") - - def tearDown(self): - try: - from _ctypes import set_conversion_mode - except ImportError: - pass - else: - set_conversion_mode(*self.prev_conv_mode) - - def test_subclasses(self): - from ctypes import c_void_p, c_char_p - # ctypes 0.9.5 and before did overwrite from_param in SimpleType_new - class CVOIDP(c_void_p): - def from_param(cls, value): - return value * 2 - from_param = classmethod(from_param) - - class CCHARP(c_char_p): - def from_param(cls, value): - return value * 4 - from_param = classmethod(from_param) - - self.assertEqual(CVOIDP.from_param("abc"), "abcabc") - self.assertEqual(CCHARP.from_param("abc"), "abcabcabcabc") - - @need_symbol('c_wchar_p') - def test_subclasses_c_wchar_p(self): - from ctypes import c_wchar_p - - class CWCHARP(c_wchar_p): - def from_param(cls, value): - return value * 3 - from_param = classmethod(from_param) - - self.assertEqual(CWCHARP.from_param("abc"), "abcabcabc") - - # XXX Replace by c_char_p tests - def test_cstrings(self): - from ctypes import c_char_p - - # c_char_p.from_param on a Python String packs the string - # into a cparam object - s = b"123" - self.assertIs(c_char_p.from_param(s)._obj, s) - - # new in 0.9.1: convert (encode) unicode to ascii - self.assertEqual(c_char_p.from_param(b"123")._obj, b"123") - self.assertRaises(TypeError, c_char_p.from_param, "123\377") - self.assertRaises(TypeError, c_char_p.from_param, 42) - - # calling c_char_p.from_param with a c_char_p instance - # returns the argument itself: - a = c_char_p(b"123") - self.assertIs(c_char_p.from_param(a), a) - - @need_symbol('c_wchar_p') - def test_cw_strings(self): - from ctypes import c_wchar_p - - c_wchar_p.from_param("123") - - self.assertRaises(TypeError, c_wchar_p.from_param, 42) - self.assertRaises(TypeError, c_wchar_p.from_param, b"123\377") - - pa = c_wchar_p.from_param(c_wchar_p("123")) - self.assertEqual(type(pa), c_wchar_p) - - def test_int_pointers(self): - from ctypes import c_short, c_uint, c_int, c_long, POINTER, pointer - LPINT = POINTER(c_int) - -## p = pointer(c_int(42)) -## x = LPINT.from_param(p) - x = LPINT.from_param(pointer(c_int(42))) - self.assertEqual(x.contents.value, 42) - self.assertEqual(LPINT(c_int(42)).contents.value, 42) - - self.assertEqual(LPINT.from_param(None), None) - - if c_int != c_long: - self.assertRaises(TypeError, LPINT.from_param, pointer(c_long(42))) - self.assertRaises(TypeError, LPINT.from_param, pointer(c_uint(42))) - self.assertRaises(TypeError, LPINT.from_param, pointer(c_short(42))) - - def test_byref_pointer(self): - # The from_param class method of POINTER(typ) classes accepts what is - # returned by byref(obj), it type(obj) == typ - from ctypes import c_short, c_uint, c_int, c_long, POINTER, byref - LPINT = POINTER(c_int) - - LPINT.from_param(byref(c_int(42))) - - self.assertRaises(TypeError, LPINT.from_param, byref(c_short(22))) - if c_int != c_long: - self.assertRaises(TypeError, LPINT.from_param, byref(c_long(22))) - self.assertRaises(TypeError, LPINT.from_param, byref(c_uint(22))) - - def test_byref_pointerpointer(self): - # See above - from ctypes import c_short, c_uint, c_int, c_long, pointer, POINTER, byref - - LPLPINT = POINTER(POINTER(c_int)) - LPLPINT.from_param(byref(pointer(c_int(42)))) - - self.assertRaises(TypeError, LPLPINT.from_param, byref(pointer(c_short(22)))) - if c_int != c_long: - self.assertRaises(TypeError, LPLPINT.from_param, byref(pointer(c_long(22)))) - self.assertRaises(TypeError, LPLPINT.from_param, byref(pointer(c_uint(22)))) - - def test_array_pointers(self): - from ctypes import c_short, c_uint, c_int, c_long, POINTER - INTARRAY = c_int * 3 - ia = INTARRAY() - self.assertEqual(len(ia), 3) - self.assertEqual([ia[i] for i in range(3)], [0, 0, 0]) - - # Pointers are only compatible with arrays containing items of - # the same type! - LPINT = POINTER(c_int) - LPINT.from_param((c_int*3)()) - self.assertRaises(TypeError, LPINT.from_param, c_short*3) - self.assertRaises(TypeError, LPINT.from_param, c_long*3) - self.assertRaises(TypeError, LPINT.from_param, c_uint*3) - - def test_noctypes_argtype(self): - import _ctypes_test - from ctypes import CDLL, c_void_p, ArgumentError - - func = CDLL(_ctypes_test.__file__)._testfunc_p_p - func.restype = c_void_p - # TypeError: has no from_param method - self.assertRaises(TypeError, setattr, func, "argtypes", (object,)) - - class Adapter(object): - def from_param(cls, obj): - return None - - func.argtypes = (Adapter(),) - self.assertEqual(func(None), None) - self.assertEqual(func(object()), None) - - class Adapter(object): - def from_param(cls, obj): - return obj - - func.argtypes = (Adapter(),) - # don't know how to convert parameter 1 - self.assertRaises(ArgumentError, func, object()) - self.assertEqual(func(c_void_p(42)), 42) - - class Adapter(object): - def from_param(cls, obj): - raise ValueError(obj) - - func.argtypes = (Adapter(),) - # ArgumentError: argument 1: ValueError: 99 - self.assertRaises(ArgumentError, func, 99) - - def test_abstract(self): - from ctypes import (Array, Structure, Union, _Pointer, - _SimpleCData, _CFuncPtr) - - self.assertRaises(TypeError, Array.from_param, 42) - self.assertRaises(TypeError, Structure.from_param, 42) - self.assertRaises(TypeError, Union.from_param, 42) - self.assertRaises(TypeError, _CFuncPtr.from_param, 42) - self.assertRaises(TypeError, _Pointer.from_param, 42) - self.assertRaises(TypeError, _SimpleCData.from_param, 42) - - @test.support.cpython_only - def test_issue31311(self): - # __setstate__ should neither raise a SystemError nor crash in case - # of a bad __dict__. - from ctypes import Structure - - class BadStruct(Structure): - @property - def __dict__(self): - pass - with self.assertRaises(TypeError): - BadStruct().__setstate__({}, b'foo') - - class WorseStruct(Structure): - @property - def __dict__(self): - 1/0 - with self.assertRaises(ZeroDivisionError): - WorseStruct().__setstate__({}, b'foo') - - def test_parameter_repr(self): - from ctypes import ( - c_bool, - c_char, - c_wchar, - c_byte, - c_ubyte, - c_short, - c_ushort, - c_int, - c_uint, - c_long, - c_ulong, - c_longlong, - c_ulonglong, - c_float, - c_double, - c_longdouble, - c_char_p, - c_wchar_p, - c_void_p, - ) - self.assertRegex(repr(c_bool.from_param(True)), r"^$") - self.assertEqual(repr(c_char.from_param(97)), "") - self.assertRegex(repr(c_wchar.from_param('a')), r"^$") - self.assertEqual(repr(c_byte.from_param(98)), "") - self.assertEqual(repr(c_ubyte.from_param(98)), "") - self.assertEqual(repr(c_short.from_param(511)), "") - self.assertEqual(repr(c_ushort.from_param(511)), "") - self.assertRegex(repr(c_int.from_param(20000)), r"^$") - self.assertRegex(repr(c_uint.from_param(20000)), r"^$") - self.assertRegex(repr(c_long.from_param(20000)), r"^$") - self.assertRegex(repr(c_ulong.from_param(20000)), r"^$") - self.assertRegex(repr(c_longlong.from_param(20000)), r"^$") - self.assertRegex(repr(c_ulonglong.from_param(20000)), r"^$") - self.assertEqual(repr(c_float.from_param(1.5)), "") - self.assertEqual(repr(c_double.from_param(1.5)), "") - self.assertEqual(repr(c_double.from_param(1e300)), "") - self.assertRegex(repr(c_longdouble.from_param(1.5)), r"^$") - self.assertRegex(repr(c_char_p.from_param(b'hihi')), r"^$") - self.assertRegex(repr(c_wchar_p.from_param('hihi')), r"^$") - self.assertRegex(repr(c_void_p.from_param(0x12)), r"^$") - -################################################################ - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_pep3118.py b/Lib/ctypes/test/test_pep3118.py deleted file mode 100644 index efffc80a66..0000000000 --- a/Lib/ctypes/test/test_pep3118.py +++ /dev/null @@ -1,235 +0,0 @@ -import unittest -from ctypes import * -import re, sys - -if sys.byteorder == "little": - THIS_ENDIAN = "<" - OTHER_ENDIAN = ">" -else: - THIS_ENDIAN = ">" - OTHER_ENDIAN = "<" - -def normalize(format): - # Remove current endian specifier and white space from a format - # string - if format is None: - return "" - format = format.replace(OTHER_ENDIAN, THIS_ENDIAN) - return re.sub(r"\s", "", format) - -class Test(unittest.TestCase): - - def test_native_types(self): - for tp, fmt, shape, itemtp in native_types: - ob = tp() - v = memoryview(ob) - try: - self.assertEqual(normalize(v.format), normalize(fmt)) - if shape: - self.assertEqual(len(v), shape[0]) - else: - self.assertEqual(len(v) * sizeof(itemtp), sizeof(ob)) - self.assertEqual(v.itemsize, sizeof(itemtp)) - self.assertEqual(v.shape, shape) - # XXX Issue #12851: PyCData_NewGetBuffer() must provide strides - # if requested. memoryview currently reconstructs missing - # stride information, so this assert will fail. - # self.assertEqual(v.strides, ()) - - # they are always read/write - self.assertFalse(v.readonly) - - if v.shape: - n = 1 - for dim in v.shape: - n = n * dim - self.assertEqual(n * v.itemsize, len(v.tobytes())) - except: - # so that we can see the failing type - print(tp) - raise - - def test_endian_types(self): - for tp, fmt, shape, itemtp in endian_types: - ob = tp() - v = memoryview(ob) - try: - self.assertEqual(v.format, fmt) - if shape: - self.assertEqual(len(v), shape[0]) - else: - self.assertEqual(len(v) * sizeof(itemtp), sizeof(ob)) - self.assertEqual(v.itemsize, sizeof(itemtp)) - self.assertEqual(v.shape, shape) - # XXX Issue #12851 - # self.assertEqual(v.strides, ()) - - # they are always read/write - self.assertFalse(v.readonly) - - if v.shape: - n = 1 - for dim in v.shape: - n = n * dim - self.assertEqual(n, len(v)) - except: - # so that we can see the failing type - print(tp) - raise - -# define some structure classes - -class Point(Structure): - _fields_ = [("x", c_long), ("y", c_long)] - -class PackedPoint(Structure): - _pack_ = 2 - _fields_ = [("x", c_long), ("y", c_long)] - -class Point2(Structure): - pass -Point2._fields_ = [("x", c_long), ("y", c_long)] - -class EmptyStruct(Structure): - _fields_ = [] - -class aUnion(Union): - _fields_ = [("a", c_int)] - -class StructWithArrays(Structure): - _fields_ = [("x", c_long * 3 * 2), ("y", Point * 4)] - -class Incomplete(Structure): - pass - -class Complete(Structure): - pass -PComplete = POINTER(Complete) -Complete._fields_ = [("a", c_long)] - -################################################################ -# -# This table contains format strings as they look on little endian -# machines. The test replaces '<' with '>' on big endian machines. -# - -# Platform-specific type codes -s_bool = {1: '?', 2: 'H', 4: 'L', 8: 'Q'}[sizeof(c_bool)] -s_short = {2: 'h', 4: 'l', 8: 'q'}[sizeof(c_short)] -s_ushort = {2: 'H', 4: 'L', 8: 'Q'}[sizeof(c_ushort)] -s_int = {2: 'h', 4: 'i', 8: 'q'}[sizeof(c_int)] -s_uint = {2: 'H', 4: 'I', 8: 'Q'}[sizeof(c_uint)] -s_long = {4: 'l', 8: 'q'}[sizeof(c_long)] -s_ulong = {4: 'L', 8: 'Q'}[sizeof(c_ulong)] -s_longlong = "q" -s_ulonglong = "Q" -s_float = "f" -s_double = "d" -s_longdouble = "g" - -# Alias definitions in ctypes/__init__.py -if c_int is c_long: - s_int = s_long -if c_uint is c_ulong: - s_uint = s_ulong -if c_longlong is c_long: - s_longlong = s_long -if c_ulonglong is c_ulong: - s_ulonglong = s_ulong -if c_longdouble is c_double: - s_longdouble = s_double - - -native_types = [ - # type format shape calc itemsize - - ## simple types - - (c_char, "l:x:>l:y:}".replace('l', s_long), (), BEPoint), - (LEPoint, "T{l:x:>l:y:}".replace('l', s_long), (), POINTER(BEPoint)), - (POINTER(LEPoint), "&T{= 0: - return a - # View the bits in `a` as unsigned instead. - import struct - num_bits = struct.calcsize("P") * 8 # num bits in native machine address - a += 1 << num_bits - assert a >= 0 - return a - -def c_wbuffer(init): - n = len(init) + 1 - return (c_wchar * n)(*init) - -class CharPointersTestCase(unittest.TestCase): - - def setUp(self): - func = testdll._testfunc_p_p - func.restype = c_long - func.argtypes = None - - def test_paramflags(self): - # function returns c_void_p result, - # and has a required parameter named 'input' - prototype = CFUNCTYPE(c_void_p, c_void_p) - func = prototype(("_testfunc_p_p", testdll), - ((1, "input"),)) - - try: - func() - except TypeError as details: - self.assertEqual(str(details), "required argument 'input' missing") - else: - self.fail("TypeError not raised") - - self.assertEqual(func(None), None) - self.assertEqual(func(input=None), None) - - - def test_int_pointer_arg(self): - func = testdll._testfunc_p_p - if sizeof(c_longlong) == sizeof(c_void_p): - func.restype = c_longlong - else: - func.restype = c_long - self.assertEqual(0, func(0)) - - ci = c_int(0) - - func.argtypes = POINTER(c_int), - self.assertEqual(positive_address(addressof(ci)), - positive_address(func(byref(ci)))) - - func.argtypes = c_char_p, - self.assertRaises(ArgumentError, func, byref(ci)) - - func.argtypes = POINTER(c_short), - self.assertRaises(ArgumentError, func, byref(ci)) - - func.argtypes = POINTER(c_double), - self.assertRaises(ArgumentError, func, byref(ci)) - - def test_POINTER_c_char_arg(self): - func = testdll._testfunc_p_p - func.restype = c_char_p - func.argtypes = POINTER(c_char), - - self.assertEqual(None, func(None)) - self.assertEqual(b"123", func(b"123")) - self.assertEqual(None, func(c_char_p(None))) - self.assertEqual(b"123", func(c_char_p(b"123"))) - - self.assertEqual(b"123", func(c_buffer(b"123"))) - ca = c_char(b"a") - self.assertEqual(ord(b"a"), func(pointer(ca))[0]) - self.assertEqual(ord(b"a"), func(byref(ca))[0]) - - def test_c_char_p_arg(self): - func = testdll._testfunc_p_p - func.restype = c_char_p - func.argtypes = c_char_p, - - self.assertEqual(None, func(None)) - self.assertEqual(b"123", func(b"123")) - self.assertEqual(None, func(c_char_p(None))) - self.assertEqual(b"123", func(c_char_p(b"123"))) - - self.assertEqual(b"123", func(c_buffer(b"123"))) - ca = c_char(b"a") - self.assertEqual(ord(b"a"), func(pointer(ca))[0]) - self.assertEqual(ord(b"a"), func(byref(ca))[0]) - - def test_c_void_p_arg(self): - func = testdll._testfunc_p_p - func.restype = c_char_p - func.argtypes = c_void_p, - - self.assertEqual(None, func(None)) - self.assertEqual(b"123", func(b"123")) - self.assertEqual(b"123", func(c_char_p(b"123"))) - self.assertEqual(None, func(c_char_p(None))) - - self.assertEqual(b"123", func(c_buffer(b"123"))) - ca = c_char(b"a") - self.assertEqual(ord(b"a"), func(pointer(ca))[0]) - self.assertEqual(ord(b"a"), func(byref(ca))[0]) - - func(byref(c_int())) - func(pointer(c_int())) - func((c_int * 3)()) - - @need_symbol('c_wchar_p') - def test_c_void_p_arg_with_c_wchar_p(self): - func = testdll._testfunc_p_p - func.restype = c_wchar_p - func.argtypes = c_void_p, - - self.assertEqual(None, func(c_wchar_p(None))) - self.assertEqual("123", func(c_wchar_p("123"))) - - def test_instance(self): - func = testdll._testfunc_p_p - func.restype = c_void_p - - class X: - _as_parameter_ = None - - func.argtypes = c_void_p, - self.assertEqual(None, func(X())) - - func.argtypes = None - self.assertEqual(None, func(X())) - -@need_symbol('c_wchar') -class WCharPointersTestCase(unittest.TestCase): - - def setUp(self): - func = testdll._testfunc_p_p - func.restype = c_int - func.argtypes = None - - - def test_POINTER_c_wchar_arg(self): - func = testdll._testfunc_p_p - func.restype = c_wchar_p - func.argtypes = POINTER(c_wchar), - - self.assertEqual(None, func(None)) - self.assertEqual("123", func("123")) - self.assertEqual(None, func(c_wchar_p(None))) - self.assertEqual("123", func(c_wchar_p("123"))) - - self.assertEqual("123", func(c_wbuffer("123"))) - ca = c_wchar("a") - self.assertEqual("a", func(pointer(ca))[0]) - self.assertEqual("a", func(byref(ca))[0]) - - def test_c_wchar_p_arg(self): - func = testdll._testfunc_p_p - func.restype = c_wchar_p - func.argtypes = c_wchar_p, - - c_wchar_p.from_param("123") - - self.assertEqual(None, func(None)) - self.assertEqual("123", func("123")) - self.assertEqual(None, func(c_wchar_p(None))) - self.assertEqual("123", func(c_wchar_p("123"))) - - # XXX Currently, these raise TypeErrors, although they shouldn't: - self.assertEqual("123", func(c_wbuffer("123"))) - ca = c_wchar("a") - self.assertEqual("a", func(pointer(ca))[0]) - self.assertEqual("a", func(byref(ca))[0]) - -class ArrayTest(unittest.TestCase): - def test(self): - func = testdll._testfunc_ai8 - func.restype = POINTER(c_int) - func.argtypes = c_int * 8, - - func((c_int * 8)(1, 2, 3, 4, 5, 6, 7, 8)) - - # This did crash before: - - def func(): pass - CFUNCTYPE(None, c_int * 3)(func) - -################################################################ - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_python_api.py b/Lib/ctypes/test/test_python_api.py deleted file mode 100644 index 49571f97bb..0000000000 --- a/Lib/ctypes/test/test_python_api.py +++ /dev/null @@ -1,85 +0,0 @@ -from ctypes import * -import unittest -from test import support - -################################################################ -# This section should be moved into ctypes\__init__.py, when it's ready. - -from _ctypes import PyObj_FromPtr - -################################################################ - -from sys import getrefcount as grc - -class PythonAPITestCase(unittest.TestCase): - - def test_PyBytes_FromStringAndSize(self): - PyBytes_FromStringAndSize = pythonapi.PyBytes_FromStringAndSize - - PyBytes_FromStringAndSize.restype = py_object - PyBytes_FromStringAndSize.argtypes = c_char_p, c_size_t - - self.assertEqual(PyBytes_FromStringAndSize(b"abcdefghi", 3), b"abc") - - @support.refcount_test - def test_PyString_FromString(self): - pythonapi.PyBytes_FromString.restype = py_object - pythonapi.PyBytes_FromString.argtypes = (c_char_p,) - - s = b"abc" - refcnt = grc(s) - pyob = pythonapi.PyBytes_FromString(s) - self.assertEqual(grc(s), refcnt) - self.assertEqual(s, pyob) - del pyob - self.assertEqual(grc(s), refcnt) - - @support.refcount_test - def test_PyLong_Long(self): - ref42 = grc(42) - pythonapi.PyLong_FromLong.restype = py_object - self.assertEqual(pythonapi.PyLong_FromLong(42), 42) - - self.assertEqual(grc(42), ref42) - - pythonapi.PyLong_AsLong.argtypes = (py_object,) - pythonapi.PyLong_AsLong.restype = c_long - - res = pythonapi.PyLong_AsLong(42) - self.assertEqual(grc(res), ref42 + 1) - del res - self.assertEqual(grc(42), ref42) - - @support.refcount_test - def test_PyObj_FromPtr(self): - s = "abc def ghi jkl" - ref = grc(s) - # id(python-object) is the address - pyobj = PyObj_FromPtr(id(s)) - self.assertIs(s, pyobj) - - self.assertEqual(grc(s), ref + 1) - del pyobj - self.assertEqual(grc(s), ref) - - def test_PyOS_snprintf(self): - PyOS_snprintf = pythonapi.PyOS_snprintf - PyOS_snprintf.argtypes = POINTER(c_char), c_size_t, c_char_p - - buf = c_buffer(256) - PyOS_snprintf(buf, sizeof(buf), b"Hello from %s", b"ctypes") - self.assertEqual(buf.value, b"Hello from ctypes") - - PyOS_snprintf(buf, sizeof(buf), b"Hello from %s (%d, %d, %d)", b"ctypes", 1, 2, 3) - self.assertEqual(buf.value, b"Hello from ctypes (1, 2, 3)") - - # not enough arguments - self.assertRaises(TypeError, PyOS_snprintf, buf) - - def test_pyobject_repr(self): - self.assertEqual(repr(py_object()), "py_object()") - self.assertEqual(repr(py_object(42)), "py_object(42)") - self.assertEqual(repr(py_object(object)), "py_object(%r)" % object) - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/ctypes/test/test_random_things.py b/Lib/ctypes/test/test_random_things.py deleted file mode 100644 index 2988e275cf..0000000000 --- a/Lib/ctypes/test/test_random_things.py +++ /dev/null @@ -1,77 +0,0 @@ -from ctypes import * -import contextlib -from test import support -import unittest -import sys - - -def callback_func(arg): - 42 / arg - raise ValueError(arg) - -@unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') -class call_function_TestCase(unittest.TestCase): - # _ctypes.call_function is deprecated and private, but used by - # Gary Bishp's readline module. If we have it, we must test it as well. - - def test(self): - from _ctypes import call_function - windll.kernel32.LoadLibraryA.restype = c_void_p - windll.kernel32.GetProcAddress.argtypes = c_void_p, c_char_p - windll.kernel32.GetProcAddress.restype = c_void_p - - hdll = windll.kernel32.LoadLibraryA(b"kernel32") - funcaddr = windll.kernel32.GetProcAddress(hdll, b"GetModuleHandleA") - - self.assertEqual(call_function(funcaddr, (None,)), - windll.kernel32.GetModuleHandleA(None)) - -class CallbackTracbackTestCase(unittest.TestCase): - # When an exception is raised in a ctypes callback function, the C - # code prints a traceback. - # - # This test makes sure the exception types *and* the exception - # value is printed correctly. - # - # Changed in 0.9.3: No longer is '(in callback)' prepended to the - # error message - instead an additional frame for the C code is - # created, then a full traceback printed. When SystemExit is - # raised in a callback function, the interpreter exits. - - @contextlib.contextmanager - def expect_unraisable(self, exc_type, exc_msg=None): - with support.catch_unraisable_exception() as cm: - yield - - self.assertIsInstance(cm.unraisable.exc_value, exc_type) - if exc_msg is not None: - self.assertEqual(str(cm.unraisable.exc_value), exc_msg) - self.assertEqual(cm.unraisable.err_msg, - "Exception ignored on calling ctypes " - "callback function") - self.assertIs(cm.unraisable.object, callback_func) - - def test_ValueError(self): - cb = CFUNCTYPE(c_int, c_int)(callback_func) - with self.expect_unraisable(ValueError, '42'): - cb(42) - - def test_IntegerDivisionError(self): - cb = CFUNCTYPE(c_int, c_int)(callback_func) - with self.expect_unraisable(ZeroDivisionError): - cb(0) - - def test_FloatDivisionError(self): - cb = CFUNCTYPE(c_int, c_double)(callback_func) - with self.expect_unraisable(ZeroDivisionError): - cb(0.0) - - def test_TypeErrorDivisionError(self): - cb = CFUNCTYPE(c_int, c_char_p)(callback_func) - err_msg = "unsupported operand type(s) for /: 'int' and 'bytes'" - with self.expect_unraisable(TypeError, err_msg): - cb(b"spam") - - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_refcounts.py b/Lib/ctypes/test/test_refcounts.py deleted file mode 100644 index 48958cd2a6..0000000000 --- a/Lib/ctypes/test/test_refcounts.py +++ /dev/null @@ -1,116 +0,0 @@ -import unittest -from test import support -import ctypes -import gc - -MyCallback = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int) -OtherCallback = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_ulonglong) - -import _ctypes_test -dll = ctypes.CDLL(_ctypes_test.__file__) - -class RefcountTestCase(unittest.TestCase): - - @support.refcount_test - def test_1(self): - from sys import getrefcount as grc - - f = dll._testfunc_callback_i_if - f.restype = ctypes.c_int - f.argtypes = [ctypes.c_int, MyCallback] - - def callback(value): - #print "called back with", value - return value - - self.assertEqual(grc(callback), 2) - cb = MyCallback(callback) - - self.assertGreater(grc(callback), 2) - result = f(-10, cb) - self.assertEqual(result, -18) - cb = None - - gc.collect() - - self.assertEqual(grc(callback), 2) - - - @support.refcount_test - def test_refcount(self): - from sys import getrefcount as grc - def func(*args): - pass - # this is the standard refcount for func - self.assertEqual(grc(func), 2) - - # the CFuncPtr instance holds at least one refcount on func: - f = OtherCallback(func) - self.assertGreater(grc(func), 2) - - # and may release it again - del f - self.assertGreaterEqual(grc(func), 2) - - # but now it must be gone - gc.collect() - self.assertEqual(grc(func), 2) - - class X(ctypes.Structure): - _fields_ = [("a", OtherCallback)] - x = X() - x.a = OtherCallback(func) - - # the CFuncPtr instance holds at least one refcount on func: - self.assertGreater(grc(func), 2) - - # and may release it again - del x - self.assertGreaterEqual(grc(func), 2) - - # and now it must be gone again - gc.collect() - self.assertEqual(grc(func), 2) - - f = OtherCallback(func) - - # the CFuncPtr instance holds at least one refcount on func: - self.assertGreater(grc(func), 2) - - # create a cycle - f.cycle = f - - del f - gc.collect() - self.assertEqual(grc(func), 2) - -class AnotherLeak(unittest.TestCase): - def test_callback(self): - import sys - - proto = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int) - def func(a, b): - return a * b * 2 - f = proto(func) - - a = sys.getrefcount(ctypes.c_int) - f(1, 2) - self.assertEqual(sys.getrefcount(ctypes.c_int), a) - - @support.refcount_test - def test_callback_py_object_none_return(self): - # bpo-36880: test that returning None from a py_object callback - # does not decrement the refcount of None. - - for FUNCTYPE in (ctypes.CFUNCTYPE, ctypes.PYFUNCTYPE): - with self.subTest(FUNCTYPE=FUNCTYPE): - @FUNCTYPE(ctypes.py_object) - def func(): - return None - - # Check that calling func does not affect None's refcount. - for _ in range(10000): - func() - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/ctypes/test/test_repr.py b/Lib/ctypes/test/test_repr.py deleted file mode 100644 index 60a2c80345..0000000000 --- a/Lib/ctypes/test/test_repr.py +++ /dev/null @@ -1,29 +0,0 @@ -from ctypes import * -import unittest - -subclasses = [] -for base in [c_byte, c_short, c_int, c_long, c_longlong, - c_ubyte, c_ushort, c_uint, c_ulong, c_ulonglong, - c_float, c_double, c_longdouble, c_bool]: - class X(base): - pass - subclasses.append(X) - -class X(c_char): - pass - -# This test checks if the __repr__ is correct for subclasses of simple types - -class ReprTest(unittest.TestCase): - def test_numbers(self): - for typ in subclasses: - base = typ.__bases__[0] - self.assertTrue(repr(base(42)).startswith(base.__name__)) - self.assertEqual(" Date: Sat, 20 Dec 2025 14:32:07 +0900 Subject: [PATCH 09/10] test_ctypes from CPython 3.13.11 --- Lib/test/test_ctypes.py | 10 - Lib/test/test_ctypes/__init__.py | 10 + Lib/test/test_ctypes/__main__.py | 4 + Lib/test/test_ctypes/_support.py | 24 + .../test_ctypes/test_aligned_structures.py | 321 ++++++ Lib/test/test_ctypes/test_anon.py | 75 ++ Lib/test/test_ctypes/test_array_in_pointer.py | 68 ++ Lib/test/test_ctypes/test_arrays.py | 272 +++++ Lib/test/test_ctypes/test_as_parameter.py | 245 +++++ Lib/test/test_ctypes/test_bitfields.py | 294 ++++++ Lib/test/test_ctypes/test_buffers.py | 70 ++ Lib/test/test_ctypes/test_bytes.py | 67 ++ Lib/test/test_ctypes/test_byteswap.py | 386 +++++++ .../test_ctypes/test_c_simple_type_meta.py | 152 +++ Lib/test/test_ctypes/test_callbacks.py | 333 ++++++ Lib/test/test_ctypes/test_cast.py | 100 ++ Lib/test/test_ctypes/test_cfuncs.py | 211 ++++ Lib/test/test_ctypes/test_checkretval.py | 37 + Lib/test/test_ctypes/test_delattr.py | 26 + Lib/test/test_ctypes/test_dlerror.py | 179 ++++ Lib/test/test_ctypes/test_errno.py | 81 ++ Lib/test/test_ctypes/test_find.py | 156 +++ Lib/test/test_ctypes/test_frombuffer.py | 144 +++ Lib/test/test_ctypes/test_funcptr.py | 134 +++ Lib/test/test_ctypes/test_functions.py | 454 +++++++++ Lib/test/test_ctypes/test_incomplete.py | 50 + Lib/test/test_ctypes/test_init.py | 43 + Lib/test/test_ctypes/test_internals.py | 97 ++ Lib/test/test_ctypes/test_keeprefs.py | 124 +++ Lib/test/test_ctypes/test_libc.py | 38 + Lib/test/test_ctypes/test_loading.py | 208 ++++ Lib/test/test_ctypes/test_macholib.py | 112 ++ Lib/test/test_ctypes/test_memfunctions.py | 82 ++ Lib/test/test_ctypes/test_numbers.py | 200 ++++ Lib/test/test_ctypes/test_objects.py | 66 ++ Lib/test/test_ctypes/test_parameters.py | 288 ++++++ Lib/test/test_ctypes/test_pep3118.py | 250 +++++ Lib/test/test_ctypes/test_pickling.py | 91 ++ Lib/test/test_ctypes/test_pointers.py | 240 +++++ Lib/test/test_ctypes/test_prototypes.py | 252 +++++ Lib/test/test_ctypes/test_python_api.py | 81 ++ Lib/test/test_ctypes/test_random_things.py | 81 ++ Lib/test/test_ctypes/test_refcounts.py | 143 +++ Lib/test/test_ctypes/test_repr.py | 34 + Lib/test/test_ctypes/test_returnfuncptrs.py | 67 ++ Lib/test/test_ctypes/test_simplesubclasses.py | 96 ++ Lib/test/test_ctypes/test_sizes.py | 38 + Lib/test/test_ctypes/test_slicing.py | 170 ++++ Lib/test/test_ctypes/test_stringptr.py | 81 ++ Lib/test/test_ctypes/test_strings.py | 134 +++ Lib/test/test_ctypes/test_struct_fields.py | 126 +++ Lib/test/test_ctypes/test_structures.py | 956 ++++++++++++++++++ .../test_ctypes/test_unaligned_structures.py | 49 + Lib/test/test_ctypes/test_unicode.py | 63 ++ Lib/test/test_ctypes/test_unions.py | 35 + Lib/test/test_ctypes/test_values.py | 107 ++ Lib/test/test_ctypes/test_varsize_struct.py | 52 + Lib/test/test_ctypes/test_win32.py | 152 +++ .../test_win32_com_foreign_func.py | 286 ++++++ Lib/test/test_ctypes/test_wintypes.py | 61 ++ 60 files changed, 8796 insertions(+), 10 deletions(-) delete mode 100644 Lib/test/test_ctypes.py create mode 100644 Lib/test/test_ctypes/__init__.py create mode 100644 Lib/test/test_ctypes/__main__.py create mode 100644 Lib/test/test_ctypes/_support.py create mode 100644 Lib/test/test_ctypes/test_aligned_structures.py create mode 100644 Lib/test/test_ctypes/test_anon.py create mode 100644 Lib/test/test_ctypes/test_array_in_pointer.py create mode 100644 Lib/test/test_ctypes/test_arrays.py create mode 100644 Lib/test/test_ctypes/test_as_parameter.py create mode 100644 Lib/test/test_ctypes/test_bitfields.py create mode 100644 Lib/test/test_ctypes/test_buffers.py create mode 100644 Lib/test/test_ctypes/test_bytes.py create mode 100644 Lib/test/test_ctypes/test_byteswap.py create mode 100644 Lib/test/test_ctypes/test_c_simple_type_meta.py create mode 100644 Lib/test/test_ctypes/test_callbacks.py create mode 100644 Lib/test/test_ctypes/test_cast.py create mode 100644 Lib/test/test_ctypes/test_cfuncs.py create mode 100644 Lib/test/test_ctypes/test_checkretval.py create mode 100644 Lib/test/test_ctypes/test_delattr.py create mode 100644 Lib/test/test_ctypes/test_dlerror.py create mode 100644 Lib/test/test_ctypes/test_errno.py create mode 100644 Lib/test/test_ctypes/test_find.py create mode 100644 Lib/test/test_ctypes/test_frombuffer.py create mode 100644 Lib/test/test_ctypes/test_funcptr.py create mode 100644 Lib/test/test_ctypes/test_functions.py create mode 100644 Lib/test/test_ctypes/test_incomplete.py create mode 100644 Lib/test/test_ctypes/test_init.py create mode 100644 Lib/test/test_ctypes/test_internals.py create mode 100644 Lib/test/test_ctypes/test_keeprefs.py create mode 100644 Lib/test/test_ctypes/test_libc.py create mode 100644 Lib/test/test_ctypes/test_loading.py create mode 100644 Lib/test/test_ctypes/test_macholib.py create mode 100644 Lib/test/test_ctypes/test_memfunctions.py create mode 100644 Lib/test/test_ctypes/test_numbers.py create mode 100644 Lib/test/test_ctypes/test_objects.py create mode 100644 Lib/test/test_ctypes/test_parameters.py create mode 100644 Lib/test/test_ctypes/test_pep3118.py create mode 100644 Lib/test/test_ctypes/test_pickling.py create mode 100644 Lib/test/test_ctypes/test_pointers.py create mode 100644 Lib/test/test_ctypes/test_prototypes.py create mode 100644 Lib/test/test_ctypes/test_python_api.py create mode 100644 Lib/test/test_ctypes/test_random_things.py create mode 100644 Lib/test/test_ctypes/test_refcounts.py create mode 100644 Lib/test/test_ctypes/test_repr.py create mode 100644 Lib/test/test_ctypes/test_returnfuncptrs.py create mode 100644 Lib/test/test_ctypes/test_simplesubclasses.py create mode 100644 Lib/test/test_ctypes/test_sizes.py create mode 100644 Lib/test/test_ctypes/test_slicing.py create mode 100644 Lib/test/test_ctypes/test_stringptr.py create mode 100644 Lib/test/test_ctypes/test_strings.py create mode 100644 Lib/test/test_ctypes/test_struct_fields.py create mode 100644 Lib/test/test_ctypes/test_structures.py create mode 100644 Lib/test/test_ctypes/test_unaligned_structures.py create mode 100644 Lib/test/test_ctypes/test_unicode.py create mode 100644 Lib/test/test_ctypes/test_unions.py create mode 100644 Lib/test/test_ctypes/test_values.py create mode 100644 Lib/test/test_ctypes/test_varsize_struct.py create mode 100644 Lib/test/test_ctypes/test_win32.py create mode 100644 Lib/test/test_ctypes/test_win32_com_foreign_func.py create mode 100644 Lib/test/test_ctypes/test_wintypes.py diff --git a/Lib/test/test_ctypes.py b/Lib/test/test_ctypes.py deleted file mode 100644 index b0a12c9734..0000000000 --- a/Lib/test/test_ctypes.py +++ /dev/null @@ -1,10 +0,0 @@ -import unittest -from test.support.import_helper import import_module - - -ctypes_test = import_module('ctypes.test') - -load_tests = ctypes_test.load_tests - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/test/test_ctypes/__init__.py b/Lib/test/test_ctypes/__init__.py new file mode 100644 index 0000000000..eb9126cbe1 --- /dev/null +++ b/Lib/test/test_ctypes/__init__.py @@ -0,0 +1,10 @@ +import os +from test import support +from test.support import import_helper + + +# skip tests if the _ctypes extension was not built +import_helper.import_module('ctypes') + +def load_tests(*args): + return support.load_package_tests(os.path.dirname(__file__), *args) diff --git a/Lib/test/test_ctypes/__main__.py b/Lib/test/test_ctypes/__main__.py new file mode 100644 index 0000000000..3003d4db89 --- /dev/null +++ b/Lib/test/test_ctypes/__main__.py @@ -0,0 +1,4 @@ +from test.test_ctypes import load_tests +import unittest + +unittest.main() diff --git a/Lib/test/test_ctypes/_support.py b/Lib/test/test_ctypes/_support.py new file mode 100644 index 0000000000..e4c2b33825 --- /dev/null +++ b/Lib/test/test_ctypes/_support.py @@ -0,0 +1,24 @@ +# Some classes and types are not export to _ctypes module directly. + +import ctypes +from _ctypes import Structure, Union, _Pointer, Array, _SimpleCData, CFuncPtr + + +_CData = Structure.__base__ +assert _CData.__name__ == "_CData" + +class _X(Structure): + _fields_ = [("x", ctypes.c_int)] +CField = type(_X.x) + +# metaclasses +PyCStructType = type(Structure) +UnionType = type(Union) +PyCPointerType = type(_Pointer) +PyCArrayType = type(Array) +PyCSimpleType = type(_SimpleCData) +PyCFuncPtrType = type(CFuncPtr) + +# type flags +Py_TPFLAGS_DISALLOW_INSTANTIATION = 1 << 7 +Py_TPFLAGS_IMMUTABLETYPE = 1 << 8 diff --git a/Lib/test/test_ctypes/test_aligned_structures.py b/Lib/test/test_ctypes/test_aligned_structures.py new file mode 100644 index 0000000000..8e8ac42990 --- /dev/null +++ b/Lib/test/test_ctypes/test_aligned_structures.py @@ -0,0 +1,321 @@ +from ctypes import ( + c_char, c_uint32, c_uint16, c_ubyte, c_byte, alignment, sizeof, + BigEndianStructure, LittleEndianStructure, + BigEndianUnion, LittleEndianUnion, Structure +) +import struct +import unittest + + +class TestAlignedStructures(unittest.TestCase): + def test_aligned_string(self): + for base, e in ( + (LittleEndianStructure, "<"), + (BigEndianStructure, ">"), + ): + data = bytearray(struct.pack(f"{e}i12x16s", 7, b"hello world!")) + class Aligned(base): + _align_ = 16 + _fields_ = [ + ('value', c_char * 12) + ] + + class Main(base): + _fields_ = [ + ('first', c_uint32), + ('string', Aligned), + ] + + main = Main.from_buffer(data) + self.assertEqual(main.first, 7) + self.assertEqual(main.string.value, b'hello world!') + self.assertEqual(bytes(main.string), b'hello world!\0\0\0\0') + self.assertEqual(Main.string.offset, 16) + self.assertEqual(Main.string.size, 16) + self.assertEqual(alignment(main.string), 16) + self.assertEqual(alignment(main), 16) + + def test_aligned_structures(self): + for base, data in ( + (LittleEndianStructure, bytearray(b"\1\0\0\0\1\0\0\0\7\0\0\0")), + (BigEndianStructure, bytearray(b"\1\0\0\0\1\0\0\0\7\0\0\0")), + ): + class SomeBools(base): + _align_ = 4 + _fields_ = [ + ("bool1", c_ubyte), + ("bool2", c_ubyte), + ] + class Main(base): + _fields_ = [ + ("x", c_ubyte), + ("y", SomeBools), + ("z", c_ubyte), + ] + + main = Main.from_buffer(data) + self.assertEqual(alignment(SomeBools), 4) + self.assertEqual(alignment(main), 4) + self.assertEqual(alignment(main.y), 4) + self.assertEqual(Main.x.size, 1) + self.assertEqual(Main.y.offset, 4) + self.assertEqual(Main.y.size, 4) + self.assertEqual(main.y.bool1, True) + self.assertEqual(main.y.bool2, False) + self.assertEqual(Main.z.offset, 8) + self.assertEqual(main.z, 7) + + def test_oversized_structure(self): + data = bytearray(b"\0" * 8) + for base in (LittleEndianStructure, BigEndianStructure): + class SomeBoolsTooBig(base): + _align_ = 8 + _fields_ = [ + ("bool1", c_ubyte), + ("bool2", c_ubyte), + ("bool3", c_ubyte), + ] + class Main(base): + _fields_ = [ + ("y", SomeBoolsTooBig), + ("z", c_uint32), + ] + with self.assertRaises(ValueError) as ctx: + Main.from_buffer(data) + self.assertEqual( + ctx.exception.args[0], + 'Buffer size too small (4 instead of at least 8 bytes)' + ) + + def test_aligned_subclasses(self): + for base, e in ( + (LittleEndianStructure, "<"), + (BigEndianStructure, ">"), + ): + data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4)) + class UnalignedSub(base): + x: c_uint32 + _fields_ = [ + ("x", c_uint32), + ] + + class AlignedStruct(UnalignedSub): + _align_ = 8 + _fields_ = [ + ("y", c_uint32), + ] + + class Main(base): + _fields_ = [ + ("a", c_uint32), + ("b", AlignedStruct) + ] + + main = Main.from_buffer(data) + self.assertEqual(alignment(main.b), 8) + self.assertEqual(alignment(main), 8) + self.assertEqual(sizeof(main.b), 8) + self.assertEqual(sizeof(main), 16) + self.assertEqual(main.a, 1) + self.assertEqual(main.b.x, 3) + self.assertEqual(main.b.y, 4) + self.assertEqual(Main.b.offset, 8) + self.assertEqual(Main.b.size, 8) + + def test_aligned_union(self): + for sbase, ubase, e in ( + (LittleEndianStructure, LittleEndianUnion, "<"), + (BigEndianStructure, BigEndianUnion, ">"), + ): + data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4)) + class AlignedUnion(ubase): + _align_ = 8 + _fields_ = [ + ("a", c_uint32), + ("b", c_ubyte * 7), + ] + + class Main(sbase): + _fields_ = [ + ("first", c_uint32), + ("union", AlignedUnion), + ] + + main = Main.from_buffer(data) + self.assertEqual(main.first, 1) + self.assertEqual(main.union.a, 3) + self.assertEqual(bytes(main.union.b), data[8:-1]) + self.assertEqual(Main.union.offset, 8) + self.assertEqual(Main.union.size, 8) + self.assertEqual(alignment(main.union), 8) + self.assertEqual(alignment(main), 8) + + def test_aligned_struct_in_union(self): + for sbase, ubase, e in ( + (LittleEndianStructure, LittleEndianUnion, "<"), + (BigEndianStructure, BigEndianUnion, ">"), + ): + data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4)) + class Sub(sbase): + _align_ = 8 + _fields_ = [ + ("x", c_uint32), + ("y", c_uint32), + ] + + class MainUnion(ubase): + _fields_ = [ + ("a", c_uint32), + ("b", Sub), + ] + + class Main(sbase): + _fields_ = [ + ("first", c_uint32), + ("union", MainUnion), + ] + + main = Main.from_buffer(data) + self.assertEqual(Main.first.size, 4) + self.assertEqual(alignment(main.union), 8) + self.assertEqual(alignment(main), 8) + self.assertEqual(Main.union.offset, 8) + self.assertEqual(Main.union.size, 8) + self.assertEqual(main.first, 1) + self.assertEqual(main.union.a, 3) + self.assertEqual(main.union.b.x, 3) + self.assertEqual(main.union.b.y, 4) + + def test_smaller_aligned_subclassed_union(self): + for sbase, ubase, e in ( + (LittleEndianStructure, LittleEndianUnion, "<"), + (BigEndianStructure, BigEndianUnion, ">"), + ): + data = bytearray(struct.pack(f"{e}H2xI", 1, 0xD60102D7)) + class SubUnion(ubase): + _align_ = 2 + _fields_ = [ + ("unsigned", c_ubyte), + ("signed", c_byte), + ] + + class MainUnion(SubUnion): + _fields_ = [ + ("num", c_uint32) + ] + + class Main(sbase): + _fields_ = [ + ("first", c_uint16), + ("union", MainUnion), + ] + + main = Main.from_buffer(data) + self.assertEqual(main.union.num, 0xD60102D7) + self.assertEqual(main.union.unsigned, data[4]) + self.assertEqual(main.union.signed, data[4] - 256) + self.assertEqual(alignment(main), 4) + self.assertEqual(alignment(main.union), 4) + self.assertEqual(Main.union.offset, 4) + self.assertEqual(Main.union.size, 4) + self.assertEqual(Main.first.size, 2) + + def test_larger_aligned_subclassed_union(self): + for ubase, e in ( + (LittleEndianUnion, "<"), + (BigEndianUnion, ">"), + ): + data = bytearray(struct.pack(f"{e}I4x", 0xD60102D6)) + class SubUnion(ubase): + _align_ = 8 + _fields_ = [ + ("unsigned", c_ubyte), + ("signed", c_byte), + ] + + class Main(SubUnion): + _fields_ = [ + ("num", c_uint32) + ] + + main = Main.from_buffer(data) + self.assertEqual(alignment(main), 8) + self.assertEqual(sizeof(main), 8) + self.assertEqual(main.num, 0xD60102D6) + self.assertEqual(main.unsigned, 0xD6) + self.assertEqual(main.signed, -42) + + def test_aligned_packed_structures(self): + for sbase, e in ( + (LittleEndianStructure, "<"), + (BigEndianStructure, ">"), + ): + data = bytearray(struct.pack(f"{e}B2H4xB", 1, 2, 3, 4)) + + class Inner(sbase): + _align_ = 8 + _fields_ = [ + ("x", c_uint16), + ("y", c_uint16), + ] + + class Main(sbase): + _pack_ = 1 + _fields_ = [ + ("a", c_ubyte), + ("b", Inner), + ("c", c_ubyte), + ] + + main = Main.from_buffer(data) + self.assertEqual(sizeof(main), 10) + self.assertEqual(Main.b.offset, 1) + # Alignment == 8 because _pack_ wins out. + self.assertEqual(alignment(main.b), 8) + # Size is still 8 though since inside this Structure, it will have + # effect. + self.assertEqual(sizeof(main.b), 8) + self.assertEqual(Main.c.offset, 9) + self.assertEqual(main.a, 1) + self.assertEqual(main.b.x, 2) + self.assertEqual(main.b.y, 3) + self.assertEqual(main.c, 4) + + def test_negative_align(self): + for base in (Structure, LittleEndianStructure, BigEndianStructure): + with ( + self.subTest(base=base), + self.assertRaisesRegex( + ValueError, + '_align_ must be a non-negative integer', + ) + ): + class MyStructure(base): + _align_ = -1 + _fields_ = [] + + def test_zero_align_no_fields(self): + for base in (Structure, LittleEndianStructure, BigEndianStructure): + with self.subTest(base=base): + class MyStructure(base): + _align_ = 0 + _fields_ = [] + + self.assertEqual(alignment(MyStructure), 1) + self.assertEqual(alignment(MyStructure()), 1) + + def test_zero_align_with_fields(self): + for base in (Structure, LittleEndianStructure, BigEndianStructure): + with self.subTest(base=base): + class MyStructure(base): + _align_ = 0 + _fields_ = [ + ("x", c_ubyte), + ] + + self.assertEqual(alignment(MyStructure), 1) + self.assertEqual(alignment(MyStructure()), 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_anon.py b/Lib/test/test_ctypes/test_anon.py new file mode 100644 index 0000000000..b36397b510 --- /dev/null +++ b/Lib/test/test_ctypes/test_anon.py @@ -0,0 +1,75 @@ +import unittest +import test.support +from ctypes import c_int, Union, Structure, sizeof + + +class AnonTest(unittest.TestCase): + + def test_anon(self): + class ANON(Union): + _fields_ = [("a", c_int), + ("b", c_int)] + + class Y(Structure): + _fields_ = [("x", c_int), + ("_", ANON), + ("y", c_int)] + _anonymous_ = ["_"] + + self.assertEqual(Y.a.offset, sizeof(c_int)) + self.assertEqual(Y.b.offset, sizeof(c_int)) + + self.assertEqual(ANON.a.offset, 0) + self.assertEqual(ANON.b.offset, 0) + + def test_anon_nonseq(self): + # TypeError: _anonymous_ must be a sequence + self.assertRaises(TypeError, + lambda: type(Structure)("Name", + (Structure,), + {"_fields_": [], "_anonymous_": 42})) + + def test_anon_nonmember(self): + # AttributeError: type object 'Name' has no attribute 'x' + self.assertRaises(AttributeError, + lambda: type(Structure)("Name", + (Structure,), + {"_fields_": [], + "_anonymous_": ["x"]})) + + @test.support.cpython_only + def test_issue31490(self): + # There shouldn't be an assertion failure in case the class has an + # attribute whose name is specified in _anonymous_ but not in _fields_. + + # AttributeError: 'x' is specified in _anonymous_ but not in _fields_ + with self.assertRaises(AttributeError): + class Name(Structure): + _fields_ = [] + _anonymous_ = ["x"] + x = 42 + + def test_nested(self): + class ANON_S(Structure): + _fields_ = [("a", c_int)] + + class ANON_U(Union): + _fields_ = [("_", ANON_S), + ("b", c_int)] + _anonymous_ = ["_"] + + class Y(Structure): + _fields_ = [("x", c_int), + ("_", ANON_U), + ("y", c_int)] + _anonymous_ = ["_"] + + self.assertEqual(Y.x.offset, 0) + self.assertEqual(Y.a.offset, sizeof(c_int)) + self.assertEqual(Y.b.offset, sizeof(c_int)) + self.assertEqual(Y._.offset, sizeof(c_int)) + self.assertEqual(Y.y.offset, sizeof(c_int) * 2) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_array_in_pointer.py b/Lib/test/test_ctypes/test_array_in_pointer.py new file mode 100644 index 0000000000..b7c96b2fa4 --- /dev/null +++ b/Lib/test/test_ctypes/test_array_in_pointer.py @@ -0,0 +1,68 @@ +import binascii +import re +import unittest +from ctypes import c_byte, Structure, POINTER, cast + + +def dump(obj): + # helper function to dump memory contents in hex, with a hyphen + # between the bytes. + h = binascii.hexlify(memoryview(obj)).decode() + return re.sub(r"(..)", r"\1-", h)[:-1] + + +class Value(Structure): + _fields_ = [("val", c_byte)] + + +class Container(Structure): + _fields_ = [("pvalues", POINTER(Value))] + + +class Test(unittest.TestCase): + def test(self): + # create an array of 4 values + val_array = (Value * 4)() + + # create a container, which holds a pointer to the pvalues array. + c = Container() + c.pvalues = val_array + + # memory contains 4 NUL bytes now, that's correct + self.assertEqual("00-00-00-00", dump(val_array)) + + # set the values of the array through the pointer: + for i in range(4): + c.pvalues[i].val = i + 1 + + values = [c.pvalues[i].val for i in range(4)] + + # These are the expected results: here s the bug! + self.assertEqual( + (values, dump(val_array)), + ([1, 2, 3, 4], "01-02-03-04") + ) + + def test_2(self): + + val_array = (Value * 4)() + + # memory contains 4 NUL bytes now, that's correct + self.assertEqual("00-00-00-00", dump(val_array)) + + ptr = cast(val_array, POINTER(Value)) + # set the values of the array through the pointer: + for i in range(4): + ptr[i].val = i + 1 + + values = [ptr[i].val for i in range(4)] + + # These are the expected results: here s the bug! + self.assertEqual( + (values, dump(val_array)), + ([1, 2, 3, 4], "01-02-03-04") + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_arrays.py b/Lib/test/test_ctypes/test_arrays.py new file mode 100644 index 0000000000..c80fdff5de --- /dev/null +++ b/Lib/test/test_ctypes/test_arrays.py @@ -0,0 +1,272 @@ +import ctypes +import sys +import unittest +from ctypes import (Structure, Array, ARRAY, sizeof, addressof, + create_string_buffer, create_unicode_buffer, + c_char, c_wchar, c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint, + c_long, c_ulonglong, c_float, c_double, c_longdouble) +from test.support import bigmemtest, _2G +from ._support import (_CData, PyCArrayType, Py_TPFLAGS_DISALLOW_INSTANTIATION, + Py_TPFLAGS_IMMUTABLETYPE) + + +formats = "bBhHiIlLqQfd" + +formats = c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint, \ + c_long, c_ulonglong, c_float, c_double, c_longdouble + + +class ArrayTestCase(unittest.TestCase): + def test_inheritance_hierarchy(self): + self.assertEqual(Array.mro(), [Array, _CData, object]) + + self.assertEqual(PyCArrayType.__name__, "PyCArrayType") + self.assertEqual(type(PyCArrayType), type) + + def test_type_flags(self): + for cls in Array, PyCArrayType: + with self.subTest(cls=cls): + self.assertTrue(cls.__flags__ & Py_TPFLAGS_IMMUTABLETYPE) + self.assertFalse(cls.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION) + + def test_metaclass_details(self): + # Abstract classes (whose metaclass __init__ was not called) can't be + # instantiated directly + NewArray = PyCArrayType.__new__(PyCArrayType, 'NewArray', (Array,), {}) + for cls in Array, NewArray: + with self.subTest(cls=cls): + with self.assertRaisesRegex(TypeError, "abstract class"): + obj = cls() + + # Cannot call the metaclass __init__ more than once + class T(Array): + _type_ = c_int + _length_ = 13 + with self.assertRaisesRegex(SystemError, "already initialized"): + PyCArrayType.__init__(T, 'ptr', (), {}) + + def test_simple(self): + # create classes holding simple numeric types, and check + # various properties. + + init = list(range(15, 25)) + + for fmt in formats: + alen = len(init) + int_array = ARRAY(fmt, alen) + + ia = int_array(*init) + # length of instance ok? + self.assertEqual(len(ia), alen) + + # slot values ok? + values = [ia[i] for i in range(alen)] + self.assertEqual(values, init) + + # out-of-bounds accesses should be caught + with self.assertRaises(IndexError): ia[alen] + with self.assertRaises(IndexError): ia[-alen-1] + + # change the items + new_values = list(range(42, 42+alen)) + for n in range(alen): + ia[n] = new_values[n] + values = [ia[i] for i in range(alen)] + self.assertEqual(values, new_values) + + # are the items initialized to 0? + ia = int_array() + values = [ia[i] for i in range(alen)] + self.assertEqual(values, [0] * alen) + + # Too many initializers should be caught + self.assertRaises(IndexError, int_array, *range(alen*2)) + + CharArray = ARRAY(c_char, 3) + + ca = CharArray(b"a", b"b", b"c") + + # Should this work? It doesn't: + # CharArray("abc") + self.assertRaises(TypeError, CharArray, "abc") + + self.assertEqual(ca[0], b"a") + self.assertEqual(ca[1], b"b") + self.assertEqual(ca[2], b"c") + self.assertEqual(ca[-3], b"a") + self.assertEqual(ca[-2], b"b") + self.assertEqual(ca[-1], b"c") + + self.assertEqual(len(ca), 3) + + # cannot delete items + with self.assertRaises(TypeError): + del ca[0] + + def test_step_overflow(self): + a = (c_int * 5)() + a[3::sys.maxsize] = (1,) + self.assertListEqual(a[3::sys.maxsize], [1]) + a = (c_char * 5)() + a[3::sys.maxsize] = b"A" + self.assertEqual(a[3::sys.maxsize], b"A") + a = (c_wchar * 5)() + a[3::sys.maxsize] = u"X" + self.assertEqual(a[3::sys.maxsize], u"X") + + def test_numeric_arrays(self): + + alen = 5 + + numarray = ARRAY(c_int, alen) + + na = numarray() + values = [na[i] for i in range(alen)] + self.assertEqual(values, [0] * alen) + + na = numarray(*[c_int()] * alen) + values = [na[i] for i in range(alen)] + self.assertEqual(values, [0]*alen) + + na = numarray(1, 2, 3, 4, 5) + values = [i for i in na] + self.assertEqual(values, [1, 2, 3, 4, 5]) + + na = numarray(*map(c_int, (1, 2, 3, 4, 5))) + values = [i for i in na] + self.assertEqual(values, [1, 2, 3, 4, 5]) + + def test_classcache(self): + self.assertIsNot(ARRAY(c_int, 3), ARRAY(c_int, 4)) + self.assertIs(ARRAY(c_int, 3), ARRAY(c_int, 3)) + + def test_from_address(self): + # Failed with 0.9.8, reported by JUrner + p = create_string_buffer(b"foo") + sz = (c_char * 3).from_address(addressof(p)) + self.assertEqual(sz[:], b"foo") + self.assertEqual(sz[::], b"foo") + self.assertEqual(sz[::-1], b"oof") + self.assertEqual(sz[::3], b"f") + self.assertEqual(sz[1:4:2], b"o") + self.assertEqual(sz.value, b"foo") + + def test_from_addressW(self): + p = create_unicode_buffer("foo") + sz = (c_wchar * 3).from_address(addressof(p)) + self.assertEqual(sz[:], "foo") + self.assertEqual(sz[::], "foo") + self.assertEqual(sz[::-1], "oof") + self.assertEqual(sz[::3], "f") + self.assertEqual(sz[1:4:2], "o") + self.assertEqual(sz.value, "foo") + + def test_cache(self): + # Array types are cached internally in the _ctypes extension, + # in a WeakValueDictionary. Make sure the array type is + # removed from the cache when the itemtype goes away. This + # test will not fail, but will show a leak in the testsuite. + + # Create a new type: + class my_int(c_int): + pass + # Create a new array type based on it: + t1 = my_int * 1 + t2 = my_int * 1 + self.assertIs(t1, t2) + + def test_subclass(self): + class T(Array): + _type_ = c_int + _length_ = 13 + class U(T): + pass + class V(U): + pass + class W(V): + pass + class X(T): + _type_ = c_short + class Y(T): + _length_ = 187 + + for c in [T, U, V, W]: + self.assertEqual(c._type_, c_int) + self.assertEqual(c._length_, 13) + self.assertEqual(c()._type_, c_int) + self.assertEqual(c()._length_, 13) + + self.assertEqual(X._type_, c_short) + self.assertEqual(X._length_, 13) + self.assertEqual(X()._type_, c_short) + self.assertEqual(X()._length_, 13) + + self.assertEqual(Y._type_, c_int) + self.assertEqual(Y._length_, 187) + self.assertEqual(Y()._type_, c_int) + self.assertEqual(Y()._length_, 187) + + def test_bad_subclass(self): + with self.assertRaises(AttributeError): + class T(Array): + pass + with self.assertRaises(AttributeError): + class T2(Array): + _type_ = c_int + with self.assertRaises(AttributeError): + class T3(Array): + _length_ = 13 + + def test_bad_length(self): + with self.assertRaises(ValueError): + class T(Array): + _type_ = c_int + _length_ = - sys.maxsize * 2 + with self.assertRaises(ValueError): + class T2(Array): + _type_ = c_int + _length_ = -1 + with self.assertRaises(TypeError): + class T3(Array): + _type_ = c_int + _length_ = 1.87 + with self.assertRaises(OverflowError): + class T4(Array): + _type_ = c_int + _length_ = sys.maxsize * 2 + + def test_zero_length(self): + # _length_ can be zero. + class T(Array): + _type_ = c_int + _length_ = 0 + + def test_empty_element_struct(self): + class EmptyStruct(Structure): + _fields_ = [] + + obj = (EmptyStruct * 2)() # bpo37188: Floating-point exception + self.assertEqual(sizeof(obj), 0) + + def test_empty_element_array(self): + class EmptyArray(Array): + _type_ = c_int + _length_ = 0 + + obj = (EmptyArray * 2)() # bpo37188: Floating-point exception + self.assertEqual(sizeof(obj), 0) + + def test_bpo36504_signed_int_overflow(self): + # The overflow check in PyCArrayType_new() could cause signed integer + # overflow. + with self.assertRaises(OverflowError): + c_char * sys.maxsize * 2 + + @unittest.skipUnless(sys.maxsize > 2**32, 'requires 64bit platform') + @bigmemtest(size=_2G, memuse=1, dry_run=False) + def test_large_array(self, size): + c_char * size + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_as_parameter.py b/Lib/test/test_ctypes/test_as_parameter.py new file mode 100644 index 0000000000..c5e1840b0e --- /dev/null +++ b/Lib/test/test_ctypes/test_as_parameter.py @@ -0,0 +1,245 @@ +import ctypes +import unittest +from ctypes import (Structure, CDLL, CFUNCTYPE, + POINTER, pointer, byref, + c_short, c_int, c_long, c_longlong, + c_byte, c_wchar, c_float, c_double, + ArgumentError) +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +dll = CDLL(_ctypes_test.__file__) + +try: + CALLBACK_FUNCTYPE = ctypes.WINFUNCTYPE +except AttributeError: + # fake to enable this test on Linux + CALLBACK_FUNCTYPE = CFUNCTYPE + + +class POINT(Structure): + _fields_ = [("x", c_int), ("y", c_int)] + + +class BasicWrapTestCase(unittest.TestCase): + def wrap(self, param): + return param + + def test_wchar_parm(self): + f = dll._testfunc_i_bhilfd + f.argtypes = [c_byte, c_wchar, c_int, c_long, c_float, c_double] + result = f(self.wrap(1), self.wrap("x"), self.wrap(3), self.wrap(4), self.wrap(5.0), self.wrap(6.0)) + self.assertEqual(result, 139) + self.assertIs(type(result), int) + + def test_pointers(self): + f = dll._testfunc_p_p + f.restype = POINTER(c_int) + f.argtypes = [POINTER(c_int)] + + # This only works if the value c_int(42) passed to the + # function is still alive while the pointer (the result) is + # used. + + v = c_int(42) + + self.assertEqual(pointer(v).contents.value, 42) + result = f(self.wrap(pointer(v))) + self.assertEqual(type(result), POINTER(c_int)) + self.assertEqual(result.contents.value, 42) + + # This on works... + result = f(self.wrap(pointer(v))) + self.assertEqual(result.contents.value, v.value) + + p = pointer(c_int(99)) + result = f(self.wrap(p)) + self.assertEqual(result.contents.value, 99) + + def test_shorts(self): + f = dll._testfunc_callback_i_if + + args = [] + expected = [262144, 131072, 65536, 32768, 16384, 8192, 4096, 2048, + 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1] + + def callback(v): + args.append(v) + return v + + CallBack = CFUNCTYPE(c_int, c_int) + + cb = CallBack(callback) + f(self.wrap(2**18), self.wrap(cb)) + self.assertEqual(args, expected) + + def test_callbacks(self): + f = dll._testfunc_callback_i_if + f.restype = c_int + f.argtypes = None + + MyCallback = CFUNCTYPE(c_int, c_int) + + def callback(value): + return value + + cb = MyCallback(callback) + + result = f(self.wrap(-10), self.wrap(cb)) + self.assertEqual(result, -18) + + # test with prototype + f.argtypes = [c_int, MyCallback] + cb = MyCallback(callback) + + result = f(self.wrap(-10), self.wrap(cb)) + self.assertEqual(result, -18) + + result = f(self.wrap(-10), self.wrap(cb)) + self.assertEqual(result, -18) + + AnotherCallback = CALLBACK_FUNCTYPE(c_int, c_int, c_int, c_int, c_int) + + # check that the prototype works: we call f with wrong + # argument types + cb = AnotherCallback(callback) + self.assertRaises(ArgumentError, f, self.wrap(-10), self.wrap(cb)) + + def test_callbacks_2(self): + # Can also use simple datatypes as argument type specifiers + # for the callback function. + # In this case the call receives an instance of that type + f = dll._testfunc_callback_i_if + f.restype = c_int + + MyCallback = CFUNCTYPE(c_int, c_int) + + f.argtypes = [c_int, MyCallback] + + def callback(value): + self.assertEqual(type(value), int) + return value + + cb = MyCallback(callback) + result = f(self.wrap(-10), self.wrap(cb)) + self.assertEqual(result, -18) + + def test_longlong_callbacks(self): + f = dll._testfunc_callback_q_qf + f.restype = c_longlong + + MyCallback = CFUNCTYPE(c_longlong, c_longlong) + + f.argtypes = [c_longlong, MyCallback] + + def callback(value): + self.assertIsInstance(value, int) + return value & 0x7FFFFFFF + + cb = MyCallback(callback) + + self.assertEqual(13577625587, int(f(self.wrap(1000000000000), self.wrap(cb)))) + + def test_byval(self): + # without prototype + ptin = POINT(1, 2) + ptout = POINT() + # EXPORT int _testfunc_byval(point in, point *pout) + result = dll._testfunc_byval(ptin, byref(ptout)) + got = result, ptout.x, ptout.y + expected = 3, 1, 2 + self.assertEqual(got, expected) + + # with prototype + ptin = POINT(101, 102) + ptout = POINT() + dll._testfunc_byval.argtypes = (POINT, POINTER(POINT)) + dll._testfunc_byval.restype = c_int + result = dll._testfunc_byval(self.wrap(ptin), byref(ptout)) + got = result, ptout.x, ptout.y + expected = 203, 101, 102 + self.assertEqual(got, expected) + + def test_struct_return_2H(self): + class S2H(Structure): + _fields_ = [("x", c_short), + ("y", c_short)] + dll.ret_2h_func.restype = S2H + dll.ret_2h_func.argtypes = [S2H] + inp = S2H(99, 88) + s2h = dll.ret_2h_func(self.wrap(inp)) + self.assertEqual((s2h.x, s2h.y), (99*2, 88*3)) + + # Test also that the original struct was unmodified (i.e. was passed by + # value) + self.assertEqual((inp.x, inp.y), (99, 88)) + + def test_struct_return_8H(self): + class S8I(Structure): + _fields_ = [("a", c_int), + ("b", c_int), + ("c", c_int), + ("d", c_int), + ("e", c_int), + ("f", c_int), + ("g", c_int), + ("h", c_int)] + dll.ret_8i_func.restype = S8I + dll.ret_8i_func.argtypes = [S8I] + inp = S8I(9, 8, 7, 6, 5, 4, 3, 2) + s8i = dll.ret_8i_func(self.wrap(inp)) + self.assertEqual((s8i.a, s8i.b, s8i.c, s8i.d, s8i.e, s8i.f, s8i.g, s8i.h), + (9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9)) + + def test_recursive_as_param(self): + class A: + pass + + a = A() + a._as_parameter_ = a + for c_type in ( + ctypes.c_wchar_p, + ctypes.c_char_p, + ctypes.c_void_p, + ctypes.c_int, # PyCSimpleType + POINT, # CDataType + ): + with self.subTest(c_type=c_type): + with self.assertRaises(RecursionError): + c_type.from_param(a) + + +class AsParamWrapper: + def __init__(self, param): + self._as_parameter_ = param + +class AsParamWrapperTestCase(BasicWrapTestCase): + wrap = AsParamWrapper + + +class AsParamPropertyWrapper: + def __init__(self, param): + self._param = param + + def getParameter(self): + return self._param + _as_parameter_ = property(getParameter) + +class AsParamPropertyWrapperTestCase(BasicWrapTestCase): + wrap = AsParamPropertyWrapper + + +class AsParamNestedWrapperTestCase(BasicWrapTestCase): + """Test that _as_parameter_ is evaluated recursively. + + The _as_parameter_ attribute can be another object which + defines its own _as_parameter_ attribute. + """ + + def wrap(self, param): + return AsParamWrapper(AsParamWrapper(AsParamWrapper(param))) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_bitfields.py b/Lib/test/test_ctypes/test_bitfields.py new file mode 100644 index 0000000000..0332544b58 --- /dev/null +++ b/Lib/test/test_ctypes/test_bitfields.py @@ -0,0 +1,294 @@ +import os +import unittest +from ctypes import (CDLL, Structure, sizeof, POINTER, byref, alignment, + LittleEndianStructure, BigEndianStructure, + c_byte, c_ubyte, c_char, c_char_p, c_void_p, c_wchar, + c_uint32, c_uint64, + c_short, c_ushort, c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong) +from test import support +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +class BITS(Structure): + _fields_ = [("A", c_int, 1), + ("B", c_int, 2), + ("C", c_int, 3), + ("D", c_int, 4), + ("E", c_int, 5), + ("F", c_int, 6), + ("G", c_int, 7), + ("H", c_int, 8), + ("I", c_int, 9), + + ("M", c_short, 1), + ("N", c_short, 2), + ("O", c_short, 3), + ("P", c_short, 4), + ("Q", c_short, 5), + ("R", c_short, 6), + ("S", c_short, 7)] + +func = CDLL(_ctypes_test.__file__).unpack_bitfields +func.argtypes = POINTER(BITS), c_char + + +class C_Test(unittest.TestCase): + + def test_ints(self): + for i in range(512): + for name in "ABCDEFGHI": + b = BITS() + setattr(b, name, i) + self.assertEqual(getattr(b, name), func(byref(b), name.encode('ascii'))) + + # bpo-46913: _ctypes/cfield.c h_get() has an undefined behavior + @support.skip_if_sanitizer(ub=True) + def test_shorts(self): + b = BITS() + name = "M" + if func(byref(b), name.encode('ascii')) == 999: + self.skipTest("Compiler does not support signed short bitfields") + for i in range(256): + for name in "MNOPQRS": + b = BITS() + setattr(b, name, i) + self.assertEqual(getattr(b, name), func(byref(b), name.encode('ascii'))) + + +signed_int_types = (c_byte, c_short, c_int, c_long, c_longlong) +unsigned_int_types = (c_ubyte, c_ushort, c_uint, c_ulong, c_ulonglong) +int_types = unsigned_int_types + signed_int_types + +class BitFieldTest(unittest.TestCase): + + def test_longlong(self): + class X(Structure): + _fields_ = [("a", c_longlong, 1), + ("b", c_longlong, 62), + ("c", c_longlong, 1)] + + self.assertEqual(sizeof(X), sizeof(c_longlong)) + x = X() + x.a, x.b, x.c = -1, 7, -1 + self.assertEqual((x.a, x.b, x.c), (-1, 7, -1)) + + def test_ulonglong(self): + class X(Structure): + _fields_ = [("a", c_ulonglong, 1), + ("b", c_ulonglong, 62), + ("c", c_ulonglong, 1)] + + self.assertEqual(sizeof(X), sizeof(c_longlong)) + x = X() + self.assertEqual((x.a, x.b, x.c), (0, 0, 0)) + x.a, x.b, x.c = 7, 7, 7 + self.assertEqual((x.a, x.b, x.c), (1, 7, 1)) + + def test_signed(self): + for c_typ in signed_int_types: + class X(Structure): + _fields_ = [("dummy", c_typ), + ("a", c_typ, 3), + ("b", c_typ, 3), + ("c", c_typ, 1)] + self.assertEqual(sizeof(X), sizeof(c_typ)*2) + + x = X() + self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 0, 0)) + x.a = -1 + self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, -1, 0, 0)) + x.a, x.b = 0, -1 + self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, -1, 0)) + + + def test_unsigned(self): + for c_typ in unsigned_int_types: + class X(Structure): + _fields_ = [("a", c_typ, 3), + ("b", c_typ, 3), + ("c", c_typ, 1)] + self.assertEqual(sizeof(X), sizeof(c_typ)) + + x = X() + self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 0, 0)) + x.a = -1 + self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 7, 0, 0)) + x.a, x.b = 0, -1 + self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 7, 0)) + + def fail_fields(self, *fields): + return self.get_except(type(Structure), "X", (), + {"_fields_": fields}) + + def test_nonint_types(self): + # bit fields are not allowed on non-integer types. + result = self.fail_fields(("a", c_char_p, 1)) + self.assertEqual(result, (TypeError, 'bit fields not allowed for type c_char_p')) + + result = self.fail_fields(("a", c_void_p, 1)) + self.assertEqual(result, (TypeError, 'bit fields not allowed for type c_void_p')) + + if c_int != c_long: + result = self.fail_fields(("a", POINTER(c_int), 1)) + self.assertEqual(result, (TypeError, 'bit fields not allowed for type LP_c_int')) + + result = self.fail_fields(("a", c_char, 1)) + self.assertEqual(result, (TypeError, 'bit fields not allowed for type c_char')) + + class Dummy(Structure): + _fields_ = [] + + result = self.fail_fields(("a", Dummy, 1)) + self.assertEqual(result, (TypeError, 'bit fields not allowed for type Dummy')) + + def test_c_wchar(self): + result = self.fail_fields(("a", c_wchar, 1)) + self.assertEqual(result, + (TypeError, 'bit fields not allowed for type c_wchar')) + + def test_single_bitfield_size(self): + for c_typ in int_types: + result = self.fail_fields(("a", c_typ, -1)) + self.assertEqual(result, (ValueError, 'number of bits invalid for bit field')) + + result = self.fail_fields(("a", c_typ, 0)) + self.assertEqual(result, (ValueError, 'number of bits invalid for bit field')) + + class X(Structure): + _fields_ = [("a", c_typ, 1)] + self.assertEqual(sizeof(X), sizeof(c_typ)) + + class X(Structure): + _fields_ = [("a", c_typ, sizeof(c_typ)*8)] + self.assertEqual(sizeof(X), sizeof(c_typ)) + + result = self.fail_fields(("a", c_typ, sizeof(c_typ)*8 + 1)) + self.assertEqual(result, (ValueError, 'number of bits invalid for bit field')) + + def test_multi_bitfields_size(self): + class X(Structure): + _fields_ = [("a", c_short, 1), + ("b", c_short, 14), + ("c", c_short, 1)] + self.assertEqual(sizeof(X), sizeof(c_short)) + + class X(Structure): + _fields_ = [("a", c_short, 1), + ("a1", c_short), + ("b", c_short, 14), + ("c", c_short, 1)] + self.assertEqual(sizeof(X), sizeof(c_short)*3) + self.assertEqual(X.a.offset, 0) + self.assertEqual(X.a1.offset, sizeof(c_short)) + self.assertEqual(X.b.offset, sizeof(c_short)*2) + self.assertEqual(X.c.offset, sizeof(c_short)*2) + + class X(Structure): + _fields_ = [("a", c_short, 3), + ("b", c_short, 14), + ("c", c_short, 14)] + self.assertEqual(sizeof(X), sizeof(c_short)*3) + self.assertEqual(X.a.offset, sizeof(c_short)*0) + self.assertEqual(X.b.offset, sizeof(c_short)*1) + self.assertEqual(X.c.offset, sizeof(c_short)*2) + + def get_except(self, func, *args, **kw): + try: + func(*args, **kw) + except Exception as detail: + return detail.__class__, str(detail) + + def test_mixed_1(self): + class X(Structure): + _fields_ = [("a", c_byte, 4), + ("b", c_int, 4)] + if os.name == "nt": + self.assertEqual(sizeof(X), sizeof(c_int)*2) + else: + self.assertEqual(sizeof(X), sizeof(c_int)) + + def test_mixed_2(self): + class X(Structure): + _fields_ = [("a", c_byte, 4), + ("b", c_int, 32)] + self.assertEqual(sizeof(X), alignment(c_int)+sizeof(c_int)) + + def test_mixed_3(self): + class X(Structure): + _fields_ = [("a", c_byte, 4), + ("b", c_ubyte, 4)] + self.assertEqual(sizeof(X), sizeof(c_byte)) + + def test_mixed_4(self): + class X(Structure): + _fields_ = [("a", c_short, 4), + ("b", c_short, 4), + ("c", c_int, 24), + ("d", c_short, 4), + ("e", c_short, 4), + ("f", c_int, 24)] + # MSVC does NOT combine c_short and c_int into one field, GCC + # does (unless GCC is run with '-mms-bitfields' which + # produces code compatible with MSVC). + if os.name == "nt": + self.assertEqual(sizeof(X), sizeof(c_int) * 4) + else: + self.assertEqual(sizeof(X), sizeof(c_int) * 2) + + def test_anon_bitfields(self): + # anonymous bit-fields gave a strange error message + class X(Structure): + _fields_ = [("a", c_byte, 4), + ("b", c_ubyte, 4)] + class Y(Structure): + _anonymous_ = ["_"] + _fields_ = [("_", X)] + + def test_uint32(self): + class X(Structure): + _fields_ = [("a", c_uint32, 32)] + x = X() + x.a = 10 + self.assertEqual(x.a, 10) + x.a = 0xFDCBA987 + self.assertEqual(x.a, 0xFDCBA987) + + def test_uint64(self): + class X(Structure): + _fields_ = [("a", c_uint64, 64)] + x = X() + x.a = 10 + self.assertEqual(x.a, 10) + x.a = 0xFEDCBA9876543211 + self.assertEqual(x.a, 0xFEDCBA9876543211) + + def test_uint32_swap_little_endian(self): + # Issue #23319 + class Little(LittleEndianStructure): + _fields_ = [("a", c_uint32, 24), + ("b", c_uint32, 4), + ("c", c_uint32, 4)] + b = bytearray(4) + x = Little.from_buffer(b) + x.a = 0xabcdef + x.b = 1 + x.c = 2 + self.assertEqual(b, b'\xef\xcd\xab\x21') + + def test_uint32_swap_big_endian(self): + # Issue #23319 + class Big(BigEndianStructure): + _fields_ = [("a", c_uint32, 24), + ("b", c_uint32, 4), + ("c", c_uint32, 4)] + b = bytearray(4) + x = Big.from_buffer(b) + x.a = 0xabcdef + x.b = 1 + x.c = 2 + self.assertEqual(b, b'\xab\xcd\xef\x12') + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_buffers.py b/Lib/test/test_ctypes/test_buffers.py new file mode 100644 index 0000000000..468f41eb7c --- /dev/null +++ b/Lib/test/test_ctypes/test_buffers.py @@ -0,0 +1,70 @@ +import unittest +from ctypes import (create_string_buffer, create_unicode_buffer, sizeof, + c_char, c_wchar) + + +class StringBufferTestCase(unittest.TestCase): + def test_buffer(self): + b = create_string_buffer(32) + self.assertEqual(len(b), 32) + self.assertEqual(sizeof(b), 32 * sizeof(c_char)) + self.assertIs(type(b[0]), bytes) + + b = create_string_buffer(b"abc") + self.assertEqual(len(b), 4) # trailing nul char + self.assertEqual(sizeof(b), 4 * sizeof(c_char)) + self.assertIs(type(b[0]), bytes) + self.assertEqual(b[0], b"a") + self.assertEqual(b[:], b"abc\0") + self.assertEqual(b[::], b"abc\0") + self.assertEqual(b[::-1], b"\0cba") + self.assertEqual(b[::2], b"ac") + self.assertEqual(b[::5], b"a") + + self.assertRaises(TypeError, create_string_buffer, "abc") + + def test_buffer_interface(self): + self.assertEqual(len(bytearray(create_string_buffer(0))), 0) + self.assertEqual(len(bytearray(create_string_buffer(1))), 1) + + def test_unicode_buffer(self): + b = create_unicode_buffer(32) + self.assertEqual(len(b), 32) + self.assertEqual(sizeof(b), 32 * sizeof(c_wchar)) + self.assertIs(type(b[0]), str) + + b = create_unicode_buffer("abc") + self.assertEqual(len(b), 4) # trailing nul char + self.assertEqual(sizeof(b), 4 * sizeof(c_wchar)) + self.assertIs(type(b[0]), str) + self.assertEqual(b[0], "a") + self.assertEqual(b[:], "abc\0") + self.assertEqual(b[::], "abc\0") + self.assertEqual(b[::-1], "\0cba") + self.assertEqual(b[::2], "ac") + self.assertEqual(b[::5], "a") + + self.assertRaises(TypeError, create_unicode_buffer, b"abc") + + def test_unicode_conversion(self): + b = create_unicode_buffer("abc") + self.assertEqual(len(b), 4) # trailing nul char + self.assertEqual(sizeof(b), 4 * sizeof(c_wchar)) + self.assertIs(type(b[0]), str) + self.assertEqual(b[0], "a") + self.assertEqual(b[:], "abc\0") + self.assertEqual(b[::], "abc\0") + self.assertEqual(b[::-1], "\0cba") + self.assertEqual(b[::2], "ac") + self.assertEqual(b[::5], "a") + + def test_create_unicode_buffer_non_bmp(self): + expected = 5 if sizeof(c_wchar) == 2 else 3 + for s in '\U00010000\U00100000', '\U00010000\U0010ffff': + b = create_unicode_buffer(s) + self.assertEqual(len(b), expected) + self.assertEqual(b[-1], '\0') + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_bytes.py b/Lib/test/test_ctypes/test_bytes.py new file mode 100644 index 0000000000..fa11e1bbd4 --- /dev/null +++ b/Lib/test/test_ctypes/test_bytes.py @@ -0,0 +1,67 @@ +"""Test where byte objects are accepted""" +import sys +import unittest +from _ctypes import _SimpleCData +from ctypes import Structure, c_char, c_char_p, c_wchar, c_wchar_p + + +class BytesTest(unittest.TestCase): + def test_c_char(self): + x = c_char(b"x") + self.assertRaises(TypeError, c_char, "x") + x.value = b"y" + with self.assertRaises(TypeError): + x.value = "y" + c_char.from_param(b"x") + self.assertRaises(TypeError, c_char.from_param, "x") + self.assertIn('xbd', repr(c_char.from_param(b"\xbd"))) + (c_char * 3)(b"a", b"b", b"c") + self.assertRaises(TypeError, c_char * 3, "a", "b", "c") + + def test_c_wchar(self): + x = c_wchar("x") + self.assertRaises(TypeError, c_wchar, b"x") + x.value = "y" + with self.assertRaises(TypeError): + x.value = b"y" + c_wchar.from_param("x") + self.assertRaises(TypeError, c_wchar.from_param, b"x") + (c_wchar * 3)("a", "b", "c") + self.assertRaises(TypeError, c_wchar * 3, b"a", b"b", b"c") + + def test_c_char_p(self): + c_char_p(b"foo bar") + self.assertRaises(TypeError, c_char_p, "foo bar") + + def test_c_wchar_p(self): + c_wchar_p("foo bar") + self.assertRaises(TypeError, c_wchar_p, b"foo bar") + + def test_struct(self): + class X(Structure): + _fields_ = [("a", c_char * 3)] + + x = X(b"abc") + self.assertRaises(TypeError, X, "abc") + self.assertEqual(x.a, b"abc") + self.assertEqual(type(x.a), bytes) + + def test_struct_W(self): + class X(Structure): + _fields_ = [("a", c_wchar * 3)] + + x = X("abc") + self.assertRaises(TypeError, X, b"abc") + self.assertEqual(x.a, "abc") + self.assertEqual(type(x.a), str) + + @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') + def test_BSTR(self): + class BSTR(_SimpleCData): + _type_ = "X" + + BSTR("abc") + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_byteswap.py b/Lib/test/test_ctypes/test_byteswap.py new file mode 100644 index 0000000000..78eff0392c --- /dev/null +++ b/Lib/test/test_ctypes/test_byteswap.py @@ -0,0 +1,386 @@ +import binascii +import ctypes +import math +import struct +import sys +import unittest +from ctypes import (Structure, Union, LittleEndianUnion, BigEndianUnion, + BigEndianStructure, LittleEndianStructure, + POINTER, sizeof, cast, + c_byte, c_ubyte, c_char, c_wchar, c_void_p, + c_short, c_ushort, c_int, c_uint, + c_long, c_ulong, c_longlong, c_ulonglong, + c_uint32, c_float, c_double) + + +def bin(s): + return binascii.hexlify(memoryview(s)).decode().upper() + + +# Each *simple* type that supports different byte orders has an +# __ctype_be__ attribute that specifies the same type in BIG ENDIAN +# byte order, and a __ctype_le__ attribute that is the same type in +# LITTLE ENDIAN byte order. +# +# For Structures and Unions, these types are created on demand. + +class Test(unittest.TestCase): + def test_slots(self): + class BigPoint(BigEndianStructure): + __slots__ = () + _fields_ = [("x", c_int), ("y", c_int)] + + class LowPoint(LittleEndianStructure): + __slots__ = () + _fields_ = [("x", c_int), ("y", c_int)] + + big = BigPoint() + little = LowPoint() + big.x = 4 + big.y = 2 + little.x = 2 + little.y = 4 + with self.assertRaises(AttributeError): + big.z = 42 + with self.assertRaises(AttributeError): + little.z = 24 + + def test_endian_short(self): + if sys.byteorder == "little": + self.assertIs(c_short.__ctype_le__, c_short) + self.assertIs(c_short.__ctype_be__.__ctype_le__, c_short) + else: + self.assertIs(c_short.__ctype_be__, c_short) + self.assertIs(c_short.__ctype_le__.__ctype_be__, c_short) + s = c_short.__ctype_be__(0x1234) + self.assertEqual(bin(struct.pack(">h", 0x1234)), "1234") + self.assertEqual(bin(s), "1234") + self.assertEqual(s.value, 0x1234) + + s = c_short.__ctype_le__(0x1234) + self.assertEqual(bin(struct.pack("h", 0x1234)), "1234") + self.assertEqual(bin(s), "1234") + self.assertEqual(s.value, 0x1234) + + s = c_ushort.__ctype_le__(0x1234) + self.assertEqual(bin(struct.pack("i", 0x12345678)), "12345678") + self.assertEqual(bin(s), "12345678") + self.assertEqual(s.value, 0x12345678) + + s = c_int.__ctype_le__(0x12345678) + self.assertEqual(bin(struct.pack("I", 0x12345678)), "12345678") + self.assertEqual(bin(s), "12345678") + self.assertEqual(s.value, 0x12345678) + + s = c_uint.__ctype_le__(0x12345678) + self.assertEqual(bin(struct.pack("q", 0x1234567890ABCDEF)), "1234567890ABCDEF") + self.assertEqual(bin(s), "1234567890ABCDEF") + self.assertEqual(s.value, 0x1234567890ABCDEF) + + s = c_longlong.__ctype_le__(0x1234567890ABCDEF) + self.assertEqual(bin(struct.pack("Q", 0x1234567890ABCDEF)), "1234567890ABCDEF") + self.assertEqual(bin(s), "1234567890ABCDEF") + self.assertEqual(s.value, 0x1234567890ABCDEF) + + s = c_ulonglong.__ctype_le__(0x1234567890ABCDEF) + self.assertEqual(bin(struct.pack("f", math.pi)), bin(s)) + + def test_endian_double(self): + if sys.byteorder == "little": + self.assertIs(c_double.__ctype_le__, c_double) + self.assertIs(c_double.__ctype_be__.__ctype_le__, c_double) + else: + self.assertIs(c_double.__ctype_be__, c_double) + self.assertIs(c_double.__ctype_le__.__ctype_be__, c_double) + s = c_double(math.pi) + self.assertEqual(s.value, math.pi) + self.assertEqual(bin(struct.pack("d", math.pi)), bin(s)) + s = c_double.__ctype_le__(math.pi) + self.assertEqual(s.value, math.pi) + self.assertEqual(bin(struct.pack("d", math.pi)), bin(s)) + + def test_endian_other(self): + self.assertIs(c_byte.__ctype_le__, c_byte) + self.assertIs(c_byte.__ctype_be__, c_byte) + + self.assertIs(c_ubyte.__ctype_le__, c_ubyte) + self.assertIs(c_ubyte.__ctype_be__, c_ubyte) + + self.assertIs(c_char.__ctype_le__, c_char) + self.assertIs(c_char.__ctype_be__, c_char) + + def test_struct_fields_unsupported_byte_order(self): + + fields = [ + ("a", c_ubyte), + ("b", c_byte), + ("c", c_short), + ("d", c_ushort), + ("e", c_int), + ("f", c_uint), + ("g", c_long), + ("h", c_ulong), + ("i", c_longlong), + ("k", c_ulonglong), + ("l", c_float), + ("m", c_double), + ("n", c_char), + ("b1", c_byte, 3), + ("b2", c_byte, 3), + ("b3", c_byte, 2), + ("a", c_int * 3 * 3 * 3) + ] + + # these fields do not support different byte order: + for typ in c_wchar, c_void_p, POINTER(c_int): + with self.assertRaises(TypeError): + class T(BigEndianStructure if sys.byteorder == "little" else LittleEndianStructure): + _fields_ = fields + [("x", typ)] + + + def test_struct_struct(self): + # nested structures with different byteorders + + # create nested structures with given byteorders and set memory to data + + for nested, data in ( + (BigEndianStructure, b'\0\0\0\1\0\0\0\2'), + (LittleEndianStructure, b'\1\0\0\0\2\0\0\0'), + ): + for parent in ( + BigEndianStructure, + LittleEndianStructure, + Structure, + ): + class NestedStructure(nested): + _fields_ = [("x", c_uint32), + ("y", c_uint32)] + + class TestStructure(parent): + _fields_ = [("point", NestedStructure)] + + self.assertEqual(len(data), sizeof(TestStructure)) + ptr = POINTER(TestStructure) + s = cast(data, ptr)[0] + del ctypes._pointer_type_cache[TestStructure] + self.assertEqual(s.point.x, 1) + self.assertEqual(s.point.y, 2) + + def test_struct_field_alignment(self): + # standard packing in struct uses no alignment. + # So, we have to align using pad bytes. + # + # Unaligned accesses will crash Python (on those platforms that + # don't allow it, like sparc solaris). + if sys.byteorder == "little": + base = BigEndianStructure + fmt = ">bxhid" + else: + base = LittleEndianStructure + fmt = " float -> double + self.check_type(c_float, math.e) + self.check_type(c_float, -math.e) + + def test_double(self): + self.check_type(c_double, 3.14) + self.check_type(c_double, -3.14) + + def test_longdouble(self): + self.check_type(c_longdouble, 3.14) + self.check_type(c_longdouble, -3.14) + + def test_char(self): + self.check_type(c_char, b"x") + self.check_type(c_char, b"a") + + def test_pyobject(self): + o = () + for o in (), [], object(): + initial = sys.getrefcount(o) + # This call leaks a reference to 'o'... + self.check_type(py_object, o) + before = sys.getrefcount(o) + # ...but this call doesn't leak any more. Where is the refcount? + self.check_type(py_object, o) + after = sys.getrefcount(o) + self.assertEqual((after, o), (before, o)) + + def test_unsupported_restype_1(self): + # Only "fundamental" result types are supported for callback + # functions, the type must have a non-NULL stginfo->setfunc. + # POINTER(c_double), for example, is not supported. + + prototype = self.functype.__func__(POINTER(c_double)) + # The type is checked when the prototype is called + self.assertRaises(TypeError, prototype, lambda: None) + + def test_unsupported_restype_2(self): + prototype = self.functype.__func__(object) + self.assertRaises(TypeError, prototype, lambda: None) + + def test_issue_7959(self): + proto = self.functype.__func__(None) + + class X: + def func(self): pass + def __init__(self): + self.v = proto(self.func) + + for i in range(32): + X() + gc.collect() + live = [x for x in gc.get_objects() + if isinstance(x, X)] + self.assertEqual(len(live), 0) + + def test_issue12483(self): + class Nasty: + def __del__(self): + gc.collect() + CFUNCTYPE(None)(lambda x=Nasty(): None) + + @unittest.skipUnless(hasattr(ctypes, 'WINFUNCTYPE'), + 'ctypes.WINFUNCTYPE is required') + def test_i38748_stackCorruption(self): + callback_funcType = ctypes.WINFUNCTYPE(c_long, c_long, c_longlong) + @callback_funcType + def callback(a, b): + c = a + b + print(f"a={a}, b={b}, c={c}") + return c + dll = cdll[_ctypes_test.__file__] + with support.captured_stdout() as out: + # With no fix for i38748, the next line will raise OSError and cause the test to fail. + self.assertEqual(dll._test_i38748_runCallback(callback, 5, 10), 15) + self.assertEqual(out.getvalue(), "a=5, b=10, c=15\n") + +if hasattr(ctypes, 'WINFUNCTYPE'): + class StdcallCallbacks(Callbacks): + functype = ctypes.WINFUNCTYPE + + +class SampleCallbacksTestCase(unittest.TestCase): + + def test_integrate(self): + # Derived from some then non-working code, posted by David Foster + dll = CDLL(_ctypes_test.__file__) + + # The function prototype called by 'integrate': double func(double); + CALLBACK = CFUNCTYPE(c_double, c_double) + + # The integrate function itself, exposed from the _ctypes_test dll + integrate = dll.integrate + integrate.argtypes = (c_double, c_double, CALLBACK, c_long) + integrate.restype = c_double + + def func(x): + return x**2 + + result = integrate(0.0, 1.0, CALLBACK(func), 10) + diff = abs(result - 1./3.) + + self.assertLess(diff, 0.01, "%s not less than 0.01" % diff) + + def test_issue_8959_a(self): + libc_path = find_library("c") + if not libc_path: + self.skipTest('could not find libc') + libc = CDLL(libc_path) + + @CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int)) + def cmp_func(a, b): + return a[0] - b[0] + + array = (c_int * 5)(5, 1, 99, 7, 33) + + libc.qsort(array, len(array), sizeof(c_int), cmp_func) + self.assertEqual(array[:], [1, 5, 7, 33, 99]) + + @unittest.skipUnless(hasattr(ctypes, 'WINFUNCTYPE'), + 'ctypes.WINFUNCTYPE is required') + def test_issue_8959_b(self): + from ctypes.wintypes import BOOL, HWND, LPARAM + global windowCount + windowCount = 0 + + @ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM) + def EnumWindowsCallbackFunc(hwnd, lParam): + global windowCount + windowCount += 1 + return True #Allow windows to keep enumerating + + user32 = ctypes.windll.user32 + user32.EnumWindows(EnumWindowsCallbackFunc, 0) + + def test_callback_register_int(self): + # Issue #8275: buggy handling of callback args under Win64 + # NOTE: should be run on release builds as well + dll = CDLL(_ctypes_test.__file__) + CALLBACK = CFUNCTYPE(c_int, c_int, c_int, c_int, c_int, c_int) + # All this function does is call the callback with its args squared + func = dll._testfunc_cbk_reg_int + func.argtypes = (c_int, c_int, c_int, c_int, c_int, CALLBACK) + func.restype = c_int + + def callback(a, b, c, d, e): + return a + b + c + d + e + + result = func(2, 3, 4, 5, 6, CALLBACK(callback)) + self.assertEqual(result, callback(2*2, 3*3, 4*4, 5*5, 6*6)) + + def test_callback_register_double(self): + # Issue #8275: buggy handling of callback args under Win64 + # NOTE: should be run on release builds as well + dll = CDLL(_ctypes_test.__file__) + CALLBACK = CFUNCTYPE(c_double, c_double, c_double, c_double, + c_double, c_double) + # All this function does is call the callback with its args squared + func = dll._testfunc_cbk_reg_double + func.argtypes = (c_double, c_double, c_double, + c_double, c_double, CALLBACK) + func.restype = c_double + + def callback(a, b, c, d, e): + return a + b + c + d + e + + result = func(1.1, 2.2, 3.3, 4.4, 5.5, CALLBACK(callback)) + self.assertEqual(result, + callback(1.1*1.1, 2.2*2.2, 3.3*3.3, 4.4*4.4, 5.5*5.5)) + + def test_callback_large_struct(self): + class Check: pass + + # This should mirror the structure in Modules/_ctypes/_ctypes_test.c + class X(Structure): + _fields_ = [ + ('first', c_ulong), + ('second', c_ulong), + ('third', c_ulong), + ] + + def callback(check, s): + check.first = s.first + check.second = s.second + check.third = s.third + # See issue #29565. + # The structure should be passed by value, so + # any changes to it should not be reflected in + # the value passed + s.first = s.second = s.third = 0x0badf00d + + check = Check() + s = X() + s.first = 0xdeadbeef + s.second = 0xcafebabe + s.third = 0x0bad1dea + + CALLBACK = CFUNCTYPE(None, X) + dll = CDLL(_ctypes_test.__file__) + func = dll._testfunc_cbk_large_struct + func.argtypes = (X, CALLBACK) + func.restype = None + # the function just calls the callback with the passed structure + func(s, CALLBACK(functools.partial(callback, check))) + self.assertEqual(check.first, s.first) + self.assertEqual(check.second, s.second) + self.assertEqual(check.third, s.third) + self.assertEqual(check.first, 0xdeadbeef) + self.assertEqual(check.second, 0xcafebabe) + self.assertEqual(check.third, 0x0bad1dea) + # See issue #29565. + # Ensure that the original struct is unchanged. + self.assertEqual(s.first, check.first) + self.assertEqual(s.second, check.second) + self.assertEqual(s.third, check.third) + + def test_callback_too_many_args(self): + def func(*args): + return len(args) + + # valid call with nargs <= CTYPES_MAX_ARGCOUNT + proto = CFUNCTYPE(c_int, *(c_int,) * CTYPES_MAX_ARGCOUNT) + cb = proto(func) + args1 = (1,) * CTYPES_MAX_ARGCOUNT + self.assertEqual(cb(*args1), CTYPES_MAX_ARGCOUNT) + + # invalid call with nargs > CTYPES_MAX_ARGCOUNT + args2 = (1,) * (CTYPES_MAX_ARGCOUNT + 1) + with self.assertRaises(ArgumentError): + cb(*args2) + + # error when creating the type with too many arguments + with self.assertRaises(ArgumentError): + CFUNCTYPE(c_int, *(c_int,) * (CTYPES_MAX_ARGCOUNT + 1)) + + def test_convert_result_error(self): + def func(): + return ("tuple",) + + proto = CFUNCTYPE(c_int) + ctypes_func = proto(func) + with support.catch_unraisable_exception() as cm: + # don't test the result since it is an uninitialized value + result = ctypes_func() + + self.assertIsInstance(cm.unraisable.exc_value, TypeError) + self.assertEqual(cm.unraisable.err_msg, + f"Exception ignored on converting result " + f"of ctypes callback function {func!r}") + self.assertIsNone(cm.unraisable.object) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_cast.py b/Lib/test/test_ctypes/test_cast.py new file mode 100644 index 0000000000..604f44f03d --- /dev/null +++ b/Lib/test/test_ctypes/test_cast.py @@ -0,0 +1,100 @@ +import sys +import unittest +from ctypes import (Structure, Union, POINTER, cast, sizeof, addressof, + c_void_p, c_char_p, c_wchar_p, + c_byte, c_short, c_int) + + +class Test(unittest.TestCase): + def test_array2pointer(self): + array = (c_int * 3)(42, 17, 2) + + # casting an array to a pointer works. + ptr = cast(array, POINTER(c_int)) + self.assertEqual([ptr[i] for i in range(3)], [42, 17, 2]) + + if 2 * sizeof(c_short) == sizeof(c_int): + ptr = cast(array, POINTER(c_short)) + if sys.byteorder == "little": + self.assertEqual([ptr[i] for i in range(6)], + [42, 0, 17, 0, 2, 0]) + else: + self.assertEqual([ptr[i] for i in range(6)], + [0, 42, 0, 17, 0, 2]) + + def test_address2pointer(self): + array = (c_int * 3)(42, 17, 2) + + address = addressof(array) + ptr = cast(c_void_p(address), POINTER(c_int)) + self.assertEqual([ptr[i] for i in range(3)], [42, 17, 2]) + + ptr = cast(address, POINTER(c_int)) + self.assertEqual([ptr[i] for i in range(3)], [42, 17, 2]) + + def test_p2a_objects(self): + array = (c_char_p * 5)() + self.assertEqual(array._objects, None) + array[0] = b"foo bar" + self.assertEqual(array._objects, {'0': b"foo bar"}) + + p = cast(array, POINTER(c_char_p)) + # array and p share a common _objects attribute + self.assertIs(p._objects, array._objects) + self.assertEqual(array._objects, {'0': b"foo bar", id(array): array}) + p[0] = b"spam spam" + self.assertEqual(p._objects, {'0': b"spam spam", id(array): array}) + self.assertIs(array._objects, p._objects) + p[1] = b"foo bar" + self.assertEqual(p._objects, {'1': b'foo bar', '0': b"spam spam", id(array): array}) + self.assertIs(array._objects, p._objects) + + def test_other(self): + p = cast((c_int * 4)(1, 2, 3, 4), POINTER(c_int)) + self.assertEqual(p[:4], [1,2, 3, 4]) + self.assertEqual(p[:4:], [1, 2, 3, 4]) + self.assertEqual(p[3:-1:-1], [4, 3, 2, 1]) + self.assertEqual(p[:4:3], [1, 4]) + c_int() + self.assertEqual(p[:4], [1, 2, 3, 4]) + self.assertEqual(p[:4:], [1, 2, 3, 4]) + self.assertEqual(p[3:-1:-1], [4, 3, 2, 1]) + self.assertEqual(p[:4:3], [1, 4]) + p[2] = 96 + self.assertEqual(p[:4], [1, 2, 96, 4]) + self.assertEqual(p[:4:], [1, 2, 96, 4]) + self.assertEqual(p[3:-1:-1], [4, 96, 2, 1]) + self.assertEqual(p[:4:3], [1, 4]) + c_int() + self.assertEqual(p[:4], [1, 2, 96, 4]) + self.assertEqual(p[:4:], [1, 2, 96, 4]) + self.assertEqual(p[3:-1:-1], [4, 96, 2, 1]) + self.assertEqual(p[:4:3], [1, 4]) + + def test_char_p(self): + # This didn't work: bad argument to internal function + s = c_char_p(b"hiho") + self.assertEqual(cast(cast(s, c_void_p), c_char_p).value, + b"hiho") + + def test_wchar_p(self): + s = c_wchar_p("hiho") + self.assertEqual(cast(cast(s, c_void_p), c_wchar_p).value, + "hiho") + + def test_bad_type_arg(self): + # The type argument must be a ctypes pointer type. + array_type = c_byte * sizeof(c_int) + array = array_type() + self.assertRaises(TypeError, cast, array, None) + self.assertRaises(TypeError, cast, array, array_type) + class Struct(Structure): + _fields_ = [("a", c_int)] + self.assertRaises(TypeError, cast, array, Struct) + class MyUnion(Union): + _fields_ = [("a", c_int)] + self.assertRaises(TypeError, cast, array, MyUnion) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_cfuncs.py b/Lib/test/test_ctypes/test_cfuncs.py new file mode 100644 index 0000000000..48330c4b0a --- /dev/null +++ b/Lib/test/test_ctypes/test_cfuncs.py @@ -0,0 +1,211 @@ +import ctypes +import unittest +from ctypes import (CDLL, + c_byte, c_ubyte, c_char, + c_short, c_ushort, c_int, c_uint, + c_long, c_ulong, c_longlong, c_ulonglong, + c_float, c_double, c_longdouble) +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +class CFunctions(unittest.TestCase): + _dll = CDLL(_ctypes_test.__file__) + + def S(self): + return c_longlong.in_dll(self._dll, "last_tf_arg_s").value + def U(self): + return c_ulonglong.in_dll(self._dll, "last_tf_arg_u").value + + def test_byte(self): + self._dll.tf_b.restype = c_byte + self._dll.tf_b.argtypes = (c_byte,) + self.assertEqual(self._dll.tf_b(-126), -42) + self.assertEqual(self.S(), -126) + + def test_byte_plus(self): + self._dll.tf_bb.restype = c_byte + self._dll.tf_bb.argtypes = (c_byte, c_byte) + self.assertEqual(self._dll.tf_bb(0, -126), -42) + self.assertEqual(self.S(), -126) + + def test_ubyte(self): + self._dll.tf_B.restype = c_ubyte + self._dll.tf_B.argtypes = (c_ubyte,) + self.assertEqual(self._dll.tf_B(255), 85) + self.assertEqual(self.U(), 255) + + def test_ubyte_plus(self): + self._dll.tf_bB.restype = c_ubyte + self._dll.tf_bB.argtypes = (c_byte, c_ubyte) + self.assertEqual(self._dll.tf_bB(0, 255), 85) + self.assertEqual(self.U(), 255) + + def test_short(self): + self._dll.tf_h.restype = c_short + self._dll.tf_h.argtypes = (c_short,) + self.assertEqual(self._dll.tf_h(-32766), -10922) + self.assertEqual(self.S(), -32766) + + def test_short_plus(self): + self._dll.tf_bh.restype = c_short + self._dll.tf_bh.argtypes = (c_byte, c_short) + self.assertEqual(self._dll.tf_bh(0, -32766), -10922) + self.assertEqual(self.S(), -32766) + + def test_ushort(self): + self._dll.tf_H.restype = c_ushort + self._dll.tf_H.argtypes = (c_ushort,) + self.assertEqual(self._dll.tf_H(65535), 21845) + self.assertEqual(self.U(), 65535) + + def test_ushort_plus(self): + self._dll.tf_bH.restype = c_ushort + self._dll.tf_bH.argtypes = (c_byte, c_ushort) + self.assertEqual(self._dll.tf_bH(0, 65535), 21845) + self.assertEqual(self.U(), 65535) + + def test_int(self): + self._dll.tf_i.restype = c_int + self._dll.tf_i.argtypes = (c_int,) + self.assertEqual(self._dll.tf_i(-2147483646), -715827882) + self.assertEqual(self.S(), -2147483646) + + def test_int_plus(self): + self._dll.tf_bi.restype = c_int + self._dll.tf_bi.argtypes = (c_byte, c_int) + self.assertEqual(self._dll.tf_bi(0, -2147483646), -715827882) + self.assertEqual(self.S(), -2147483646) + + def test_uint(self): + self._dll.tf_I.restype = c_uint + self._dll.tf_I.argtypes = (c_uint,) + self.assertEqual(self._dll.tf_I(4294967295), 1431655765) + self.assertEqual(self.U(), 4294967295) + + def test_uint_plus(self): + self._dll.tf_bI.restype = c_uint + self._dll.tf_bI.argtypes = (c_byte, c_uint) + self.assertEqual(self._dll.tf_bI(0, 4294967295), 1431655765) + self.assertEqual(self.U(), 4294967295) + + def test_long(self): + self._dll.tf_l.restype = c_long + self._dll.tf_l.argtypes = (c_long,) + self.assertEqual(self._dll.tf_l(-2147483646), -715827882) + self.assertEqual(self.S(), -2147483646) + + def test_long_plus(self): + self._dll.tf_bl.restype = c_long + self._dll.tf_bl.argtypes = (c_byte, c_long) + self.assertEqual(self._dll.tf_bl(0, -2147483646), -715827882) + self.assertEqual(self.S(), -2147483646) + + def test_ulong(self): + self._dll.tf_L.restype = c_ulong + self._dll.tf_L.argtypes = (c_ulong,) + self.assertEqual(self._dll.tf_L(4294967295), 1431655765) + self.assertEqual(self.U(), 4294967295) + + def test_ulong_plus(self): + self._dll.tf_bL.restype = c_ulong + self._dll.tf_bL.argtypes = (c_char, c_ulong) + self.assertEqual(self._dll.tf_bL(b' ', 4294967295), 1431655765) + self.assertEqual(self.U(), 4294967295) + + def test_longlong(self): + self._dll.tf_q.restype = c_longlong + self._dll.tf_q.argtypes = (c_longlong, ) + self.assertEqual(self._dll.tf_q(-9223372036854775806), -3074457345618258602) + self.assertEqual(self.S(), -9223372036854775806) + + def test_longlong_plus(self): + self._dll.tf_bq.restype = c_longlong + self._dll.tf_bq.argtypes = (c_byte, c_longlong) + self.assertEqual(self._dll.tf_bq(0, -9223372036854775806), -3074457345618258602) + self.assertEqual(self.S(), -9223372036854775806) + + def test_ulonglong(self): + self._dll.tf_Q.restype = c_ulonglong + self._dll.tf_Q.argtypes = (c_ulonglong, ) + self.assertEqual(self._dll.tf_Q(18446744073709551615), 6148914691236517205) + self.assertEqual(self.U(), 18446744073709551615) + + def test_ulonglong_plus(self): + self._dll.tf_bQ.restype = c_ulonglong + self._dll.tf_bQ.argtypes = (c_byte, c_ulonglong) + self.assertEqual(self._dll.tf_bQ(0, 18446744073709551615), 6148914691236517205) + self.assertEqual(self.U(), 18446744073709551615) + + def test_float(self): + self._dll.tf_f.restype = c_float + self._dll.tf_f.argtypes = (c_float,) + self.assertEqual(self._dll.tf_f(-42.), -14.) + self.assertEqual(self.S(), -42) + + def test_float_plus(self): + self._dll.tf_bf.restype = c_float + self._dll.tf_bf.argtypes = (c_byte, c_float) + self.assertEqual(self._dll.tf_bf(0, -42.), -14.) + self.assertEqual(self.S(), -42) + + def test_double(self): + self._dll.tf_d.restype = c_double + self._dll.tf_d.argtypes = (c_double,) + self.assertEqual(self._dll.tf_d(42.), 14.) + self.assertEqual(self.S(), 42) + + def test_double_plus(self): + self._dll.tf_bd.restype = c_double + self._dll.tf_bd.argtypes = (c_byte, c_double) + self.assertEqual(self._dll.tf_bd(0, 42.), 14.) + self.assertEqual(self.S(), 42) + + def test_longdouble(self): + self._dll.tf_D.restype = c_longdouble + self._dll.tf_D.argtypes = (c_longdouble,) + self.assertEqual(self._dll.tf_D(42.), 14.) + self.assertEqual(self.S(), 42) + + def test_longdouble_plus(self): + self._dll.tf_bD.restype = c_longdouble + self._dll.tf_bD.argtypes = (c_byte, c_longdouble) + self.assertEqual(self._dll.tf_bD(0, 42.), 14.) + self.assertEqual(self.S(), 42) + + def test_callwithresult(self): + def process_result(result): + return result * 2 + self._dll.tf_i.restype = process_result + self._dll.tf_i.argtypes = (c_int,) + self.assertEqual(self._dll.tf_i(42), 28) + self.assertEqual(self.S(), 42) + self.assertEqual(self._dll.tf_i(-42), -28) + self.assertEqual(self.S(), -42) + + def test_void(self): + self._dll.tv_i.restype = None + self._dll.tv_i.argtypes = (c_int,) + self.assertEqual(self._dll.tv_i(42), None) + self.assertEqual(self.S(), 42) + self.assertEqual(self._dll.tv_i(-42), None) + self.assertEqual(self.S(), -42) + + +# The following repeats the above tests with stdcall functions (where +# they are available) +if hasattr(ctypes, 'WinDLL'): + class stdcall_dll(ctypes.WinDLL): + def __getattr__(self, name): + if name[:2] == '__' and name[-2:] == '__': + raise AttributeError(name) + func = self._FuncPtr(("s_" + name, self)) + setattr(self, name, func) + return func + + class stdcallCFunctions(CFunctions): + _dll = stdcall_dll(_ctypes_test.__file__) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_checkretval.py b/Lib/test/test_ctypes/test_checkretval.py new file mode 100644 index 0000000000..9d6bfdb845 --- /dev/null +++ b/Lib/test/test_ctypes/test_checkretval.py @@ -0,0 +1,37 @@ +import ctypes +import unittest +from ctypes import CDLL, c_int +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +class CHECKED(c_int): + def _check_retval_(value): + # Receives a CHECKED instance. + return str(value.value) + _check_retval_ = staticmethod(_check_retval_) + + +class Test(unittest.TestCase): + def test_checkretval(self): + dll = CDLL(_ctypes_test.__file__) + self.assertEqual(42, dll._testfunc_p_p(42)) + + dll._testfunc_p_p.restype = CHECKED + self.assertEqual("42", dll._testfunc_p_p(42)) + + dll._testfunc_p_p.restype = None + self.assertEqual(None, dll._testfunc_p_p(42)) + + del dll._testfunc_p_p.restype + self.assertEqual(42, dll._testfunc_p_p(42)) + + @unittest.skipUnless(hasattr(ctypes, 'oledll'), + 'ctypes.oledll is required') + def test_oledll(self): + oleaut32 = ctypes.oledll.oleaut32 + self.assertRaises(OSError, oleaut32.CreateTypeLib2, 0, None, None) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_delattr.py b/Lib/test/test_ctypes/test_delattr.py new file mode 100644 index 0000000000..e80b5fa6ef --- /dev/null +++ b/Lib/test/test_ctypes/test_delattr.py @@ -0,0 +1,26 @@ +import unittest +from ctypes import Structure, c_char, c_int + + +class X(Structure): + _fields_ = [("foo", c_int)] + + +class TestCase(unittest.TestCase): + def test_simple(self): + with self.assertRaises(TypeError): + del c_int(42).value + + def test_chararray(self): + chararray = (c_char * 5)() + with self.assertRaises(TypeError): + del chararray.value + + def test_struct(self): + struct = X() + with self.assertRaises(TypeError): + del struct.foo + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_dlerror.py b/Lib/test/test_ctypes/test_dlerror.py new file mode 100644 index 0000000000..1c1b2aab3d --- /dev/null +++ b/Lib/test/test_ctypes/test_dlerror.py @@ -0,0 +1,179 @@ +import _ctypes +import os +import platform +import sys +import test.support +import unittest +from ctypes import CDLL, c_int +from ctypes.util import find_library + + +FOO_C = r""" +#include + +/* This is a 'GNU indirect function' (IFUNC) that will be called by + dlsym() to resolve the symbol "foo" to an address. Typically, such + a function would return the address of an actual function, but it + can also just return NULL. For some background on IFUNCs, see + https://willnewton.name/uncategorized/using-gnu-indirect-functions. + + Adapted from Michael Kerrisk's answer: https://stackoverflow.com/a/53590014. +*/ + +asm (".type foo STT_GNU_IFUNC"); + +void *foo(void) +{ + write($DESCRIPTOR, "OK", 2); + return NULL; +} +""" + + +@unittest.skipUnless(sys.platform.startswith('linux'), + 'test requires GNU IFUNC support') +class TestNullDlsym(unittest.TestCase): + """GH-126554: Ensure that we catch NULL dlsym return values + + In rare cases, such as when using GNU IFUNCs, dlsym(), + the C function that ctypes' CDLL uses to get the address + of symbols, can return NULL. + + The objective way of telling if an error during symbol + lookup happened is to call glibc's dlerror() and check + for a non-NULL return value. + + However, there can be cases where dlsym() returns NULL + and dlerror() is also NULL, meaning that glibc did not + encounter any error. + + In the case of ctypes, we subjectively treat that as + an error, and throw a relevant exception. + + This test case ensures that we correctly enforce + this 'dlsym returned NULL -> throw Error' rule. + """ + + def test_null_dlsym(self): + import subprocess + import tempfile + + try: + retcode = subprocess.call(["gcc", "--version"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) + except OSError: + self.skipTest("gcc is missing") + if retcode != 0: + self.skipTest("gcc --version failed") + + pipe_r, pipe_w = os.pipe() + self.addCleanup(os.close, pipe_r) + self.addCleanup(os.close, pipe_w) + + with tempfile.TemporaryDirectory() as d: + # Create a C file with a GNU Indirect Function (FOO_C) + # and compile it into a shared library. + srcname = os.path.join(d, 'foo.c') + dstname = os.path.join(d, 'libfoo.so') + with open(srcname, 'w') as f: + f.write(FOO_C.replace('$DESCRIPTOR', str(pipe_w))) + args = ['gcc', '-fPIC', '-shared', '-o', dstname, srcname] + p = subprocess.run(args, capture_output=True) + + if p.returncode != 0: + # IFUNC is not supported on all architectures. + if platform.machine() == 'x86_64': + # It should be supported here. Something else went wrong. + p.check_returncode() + else: + # IFUNC might not be supported on this machine. + self.skipTest(f"could not compile indirect function: {p}") + + # Case #1: Test 'PyCFuncPtr_FromDll' from Modules/_ctypes/_ctypes.c + L = CDLL(dstname) + with self.assertRaisesRegex(AttributeError, "function 'foo' not found"): + # Try accessing the 'foo' symbol. + # It should resolve via dlsym() to NULL, + # and since we subjectively treat NULL + # addresses as errors, we should get + # an error. + L.foo + + # Assert that the IFUNC was called + self.assertEqual(os.read(pipe_r, 2), b'OK') + + # Case #2: Test 'CDataType_in_dll_impl' from Modules/_ctypes/_ctypes.c + with self.assertRaisesRegex(ValueError, "symbol 'foo' not found"): + c_int.in_dll(L, "foo") + + # Assert that the IFUNC was called + self.assertEqual(os.read(pipe_r, 2), b'OK') + + # Case #3: Test 'py_dl_sym' from Modules/_ctypes/callproc.c + dlopen = test.support.get_attribute(_ctypes, 'dlopen') + dlsym = test.support.get_attribute(_ctypes, 'dlsym') + L = dlopen(dstname) + with self.assertRaisesRegex(OSError, "symbol 'foo' not found"): + dlsym(L, "foo") + + # Assert that the IFUNC was called + self.assertEqual(os.read(pipe_r, 2), b'OK') + + +@unittest.skipUnless(os.name != 'nt', 'test requires dlerror() calls') +class TestLocalization(unittest.TestCase): + + @staticmethod + def configure_locales(func): + return test.support.run_with_locale( + 'LC_ALL', + 'fr_FR.iso88591', 'ja_JP.sjis', 'zh_CN.gbk', + 'fr_FR.utf8', 'en_US.utf8', + '', + )(func) + + @classmethod + def setUpClass(cls): + cls.libc_filename = find_library("c") + if cls.libc_filename is None: + raise unittest.SkipTest('cannot find libc') + + @configure_locales + def test_localized_error_from_dll(self): + dll = CDLL(self.libc_filename) + with self.assertRaises(AttributeError): + dll.this_name_does_not_exist + + @configure_locales + def test_localized_error_in_dll(self): + dll = CDLL(self.libc_filename) + with self.assertRaises(ValueError): + c_int.in_dll(dll, 'this_name_does_not_exist') + + @unittest.skipUnless(hasattr(_ctypes, 'dlopen'), + 'test requires _ctypes.dlopen()') + @configure_locales + def test_localized_error_dlopen(self): + missing_filename = b'missing\xff.so' + # Depending whether the locale, we may encode '\xff' differently + # but we are only interested in avoiding a UnicodeDecodeError + # when reporting the dlerror() error message which contains + # the localized filename. + filename_pattern = r'missing.*?\.so' + with self.assertRaisesRegex(OSError, filename_pattern): + _ctypes.dlopen(missing_filename, 2) + + @unittest.skipUnless(hasattr(_ctypes, 'dlopen'), + 'test requires _ctypes.dlopen()') + @unittest.skipUnless(hasattr(_ctypes, 'dlsym'), + 'test requires _ctypes.dlsym()') + @configure_locales + def test_localized_error_dlsym(self): + dll = _ctypes.dlopen(self.libc_filename) + with self.assertRaises(OSError): + _ctypes.dlsym(dll, 'this_name_does_not_exist') + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_errno.py b/Lib/test/test_ctypes/test_errno.py new file mode 100644 index 0000000000..65d99c1e49 --- /dev/null +++ b/Lib/test/test_ctypes/test_errno.py @@ -0,0 +1,81 @@ +import ctypes +import errno +import os +import threading +import unittest +from ctypes import CDLL, c_int, c_char_p, c_wchar_p, get_errno, set_errno +from ctypes.util import find_library + + +class Test(unittest.TestCase): + def test_open(self): + libc_name = find_library("c") + if libc_name is None: + self.skipTest("Unable to find C library") + + libc = CDLL(libc_name, use_errno=True) + if os.name == "nt": + libc_open = libc._open + else: + libc_open = libc.open + + libc_open.argtypes = c_char_p, c_int + + self.assertEqual(libc_open(b"", 0), -1) + self.assertEqual(get_errno(), errno.ENOENT) + + self.assertEqual(set_errno(32), errno.ENOENT) + self.assertEqual(get_errno(), 32) + + def _worker(): + set_errno(0) + + libc = CDLL(libc_name, use_errno=False) + if os.name == "nt": + libc_open = libc._open + else: + libc_open = libc.open + libc_open.argtypes = c_char_p, c_int + self.assertEqual(libc_open(b"", 0), -1) + self.assertEqual(get_errno(), 0) + + t = threading.Thread(target=_worker) + t.start() + t.join() + + self.assertEqual(get_errno(), 32) + set_errno(0) + + @unittest.skipUnless(os.name == "nt", 'Test specific to Windows') + def test_GetLastError(self): + dll = ctypes.WinDLL("kernel32", use_last_error=True) + GetModuleHandle = dll.GetModuleHandleA + GetModuleHandle.argtypes = [c_wchar_p] + + self.assertEqual(0, GetModuleHandle("foo")) + self.assertEqual(ctypes.get_last_error(), 126) + + self.assertEqual(ctypes.set_last_error(32), 126) + self.assertEqual(ctypes.get_last_error(), 32) + + def _worker(): + ctypes.set_last_error(0) + + dll = ctypes.WinDLL("kernel32", use_last_error=False) + GetModuleHandle = dll.GetModuleHandleW + GetModuleHandle.argtypes = [c_wchar_p] + GetModuleHandle("bar") + + self.assertEqual(ctypes.get_last_error(), 0) + + t = threading.Thread(target=_worker) + t.start() + t.join() + + self.assertEqual(ctypes.get_last_error(), 32) + + ctypes.set_last_error(0) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_find.py b/Lib/test/test_ctypes/test_find.py new file mode 100644 index 0000000000..85b28617d2 --- /dev/null +++ b/Lib/test/test_ctypes/test_find.py @@ -0,0 +1,156 @@ +import os.path +import sys +import test.support +import unittest +import unittest.mock +from ctypes import CDLL, RTLD_GLOBAL +from ctypes.util import find_library +from test.support import os_helper + + +# On some systems, loading the OpenGL libraries needs the RTLD_GLOBAL mode. +class Test_OpenGL_libs(unittest.TestCase): + @classmethod + def setUpClass(cls): + lib_gl = lib_glu = lib_gle = None + if sys.platform == "win32": + lib_gl = find_library("OpenGL32") + lib_glu = find_library("Glu32") + elif sys.platform == "darwin": + lib_gl = lib_glu = find_library("OpenGL") + else: + lib_gl = find_library("GL") + lib_glu = find_library("GLU") + lib_gle = find_library("gle") + + # print, for debugging + if test.support.verbose: + print("OpenGL libraries:") + for item in (("GL", lib_gl), + ("GLU", lib_glu), + ("gle", lib_gle)): + print("\t", item) + + cls.gl = cls.glu = cls.gle = None + if lib_gl: + try: + cls.gl = CDLL(lib_gl, mode=RTLD_GLOBAL) + except OSError: + pass + + if lib_glu: + try: + cls.glu = CDLL(lib_glu, RTLD_GLOBAL) + except OSError: + pass + + if lib_gle: + try: + cls.gle = CDLL(lib_gle) + except OSError: + pass + + @classmethod + def tearDownClass(cls): + cls.gl = cls.glu = cls.gle = None + + def test_gl(self): + if self.gl is None: + self.skipTest('lib_gl not available') + self.gl.glClearIndex + + def test_glu(self): + if self.glu is None: + self.skipTest('lib_glu not available') + self.glu.gluBeginCurve + + def test_gle(self): + if self.gle is None: + self.skipTest('lib_gle not available') + self.gle.gleGetJoinStyle + + def test_shell_injection(self): + result = find_library('; echo Hello shell > ' + os_helper.TESTFN) + self.assertFalse(os.path.lexists(os_helper.TESTFN)) + self.assertIsNone(result) + + +@unittest.skipUnless(sys.platform.startswith('linux'), + 'Test only valid for Linux') +class FindLibraryLinux(unittest.TestCase): + def test_find_on_libpath(self): + import subprocess + import tempfile + + try: + p = subprocess.Popen(['gcc', '--version'], stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL) + out, _ = p.communicate() + except OSError: + raise unittest.SkipTest('gcc, needed for test, not available') + with tempfile.TemporaryDirectory() as d: + # create an empty temporary file + srcname = os.path.join(d, 'dummy.c') + libname = 'py_ctypes_test_dummy' + dstname = os.path.join(d, 'lib%s.so' % libname) + with open(srcname, 'wb') as f: + pass + self.assertTrue(os.path.exists(srcname)) + # compile the file to a shared library + cmd = ['gcc', '-o', dstname, '--shared', + '-Wl,-soname,lib%s.so' % libname, srcname] + out = subprocess.check_output(cmd) + self.assertTrue(os.path.exists(dstname)) + # now check that the .so can't be found (since not in + # LD_LIBRARY_PATH) + self.assertIsNone(find_library(libname)) + # now add the location to LD_LIBRARY_PATH + with os_helper.EnvironmentVarGuard() as env: + KEY = 'LD_LIBRARY_PATH' + if KEY not in env: + v = d + else: + v = '%s:%s' % (env[KEY], d) + env.set(KEY, v) + # now check that the .so can be found (since in + # LD_LIBRARY_PATH) + self.assertEqual(find_library(libname), 'lib%s.so' % libname) + + def test_find_library_with_gcc(self): + with unittest.mock.patch("ctypes.util._findSoname_ldconfig", lambda *args: None): + self.assertNotEqual(find_library('c'), None) + + def test_find_library_with_ld(self): + with unittest.mock.patch("ctypes.util._findSoname_ldconfig", lambda *args: None), \ + unittest.mock.patch("ctypes.util._findLib_gcc", lambda *args: None): + self.assertNotEqual(find_library('c'), None) + + def test_gh114257(self): + self.assertIsNone(find_library("libc")) + + +@unittest.skipUnless(sys.platform == 'android', 'Test only valid for Android') +class FindLibraryAndroid(unittest.TestCase): + def test_find(self): + for name in [ + "c", "m", # POSIX + "z", # Non-POSIX, but present on Linux + "log", # Not present on Linux + ]: + with self.subTest(name=name): + path = find_library(name) + self.assertIsInstance(path, str) + self.assertEqual( + os.path.dirname(path), + "/system/lib64" if "64" in os.uname().machine + else "/system/lib") + self.assertEqual(os.path.basename(path), f"lib{name}.so") + self.assertTrue(os.path.isfile(path), path) + + for name in ["libc", "nonexistent"]: + with self.subTest(name=name): + self.assertIsNone(find_library(name)) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_frombuffer.py b/Lib/test/test_ctypes/test_frombuffer.py new file mode 100644 index 0000000000..d4e161f864 --- /dev/null +++ b/Lib/test/test_ctypes/test_frombuffer.py @@ -0,0 +1,144 @@ +import array +import gc +import unittest +from ctypes import (Structure, Union, Array, sizeof, + _Pointer, _SimpleCData, _CFuncPtr, + c_char, c_int) + + +class X(Structure): + _fields_ = [("c_int", c_int)] + init_called = False + def __init__(self): + self._init_called = True + + +class Test(unittest.TestCase): + def test_from_buffer(self): + a = array.array("i", range(16)) + x = (c_int * 16).from_buffer(a) + + y = X.from_buffer(a) + self.assertEqual(y.c_int, a[0]) + self.assertFalse(y.init_called) + + self.assertEqual(x[:], a.tolist()) + + a[0], a[-1] = 200, -200 + self.assertEqual(x[:], a.tolist()) + + self.assertRaises(BufferError, a.append, 100) + self.assertRaises(BufferError, a.pop) + + del x; del y; gc.collect(); gc.collect(); gc.collect() + a.append(100) + a.pop() + x = (c_int * 16).from_buffer(a) + + self.assertIn(a, [obj.obj if isinstance(obj, memoryview) else obj + for obj in x._objects.values()]) + + expected = x[:] + del a; gc.collect(); gc.collect(); gc.collect() + self.assertEqual(x[:], expected) + + with self.assertRaisesRegex(TypeError, "not writable"): + (c_char * 16).from_buffer(b"a" * 16) + with self.assertRaisesRegex(TypeError, "not writable"): + (c_char * 16).from_buffer(memoryview(b"a" * 16)) + with self.assertRaisesRegex(TypeError, "not C contiguous"): + (c_char * 16).from_buffer(memoryview(bytearray(b"a" * 16))[::-1]) + msg = "bytes-like object is required" + with self.assertRaisesRegex(TypeError, msg): + (c_char * 16).from_buffer("a" * 16) + + def test_fortran_contiguous(self): + try: + import _testbuffer + except ImportError as err: + self.skipTest(str(err)) + flags = _testbuffer.ND_WRITABLE | _testbuffer.ND_FORTRAN + array = _testbuffer.ndarray( + [97] * 16, format="B", shape=[4, 4], flags=flags) + with self.assertRaisesRegex(TypeError, "not C contiguous"): + (c_char * 16).from_buffer(array) + array = memoryview(array) + self.assertTrue(array.f_contiguous) + self.assertFalse(array.c_contiguous) + with self.assertRaisesRegex(TypeError, "not C contiguous"): + (c_char * 16).from_buffer(array) + + def test_from_buffer_with_offset(self): + a = array.array("i", range(16)) + x = (c_int * 15).from_buffer(a, sizeof(c_int)) + + self.assertEqual(x[:], a.tolist()[1:]) + with self.assertRaises(ValueError): + c_int.from_buffer(a, -1) + with self.assertRaises(ValueError): + (c_int * 16).from_buffer(a, sizeof(c_int)) + with self.assertRaises(ValueError): + (c_int * 1).from_buffer(a, 16 * sizeof(c_int)) + + def test_from_buffer_memoryview(self): + a = [c_char.from_buffer(memoryview(bytearray(b'a')))] + a.append(a) + del a + gc.collect() # Should not crash + + def test_from_buffer_copy(self): + a = array.array("i", range(16)) + x = (c_int * 16).from_buffer_copy(a) + + y = X.from_buffer_copy(a) + self.assertEqual(y.c_int, a[0]) + self.assertFalse(y.init_called) + + self.assertEqual(x[:], list(range(16))) + + a[0], a[-1] = 200, -200 + self.assertEqual(x[:], list(range(16))) + + a.append(100) + self.assertEqual(x[:], list(range(16))) + + self.assertEqual(x._objects, None) + + del a; gc.collect(); gc.collect(); gc.collect() + self.assertEqual(x[:], list(range(16))) + + x = (c_char * 16).from_buffer_copy(b"a" * 16) + self.assertEqual(x[:], b"a" * 16) + with self.assertRaises(TypeError): + (c_char * 16).from_buffer_copy("a" * 16) + + def test_from_buffer_copy_with_offset(self): + a = array.array("i", range(16)) + x = (c_int * 15).from_buffer_copy(a, sizeof(c_int)) + + self.assertEqual(x[:], a.tolist()[1:]) + with self.assertRaises(ValueError): + c_int.from_buffer_copy(a, -1) + with self.assertRaises(ValueError): + (c_int * 16).from_buffer_copy(a, sizeof(c_int)) + with self.assertRaises(ValueError): + (c_int * 1).from_buffer_copy(a, 16 * sizeof(c_int)) + + def test_abstract(self): + self.assertRaises(TypeError, Array.from_buffer, bytearray(10)) + self.assertRaises(TypeError, Structure.from_buffer, bytearray(10)) + self.assertRaises(TypeError, Union.from_buffer, bytearray(10)) + self.assertRaises(TypeError, _CFuncPtr.from_buffer, bytearray(10)) + self.assertRaises(TypeError, _Pointer.from_buffer, bytearray(10)) + self.assertRaises(TypeError, _SimpleCData.from_buffer, bytearray(10)) + + self.assertRaises(TypeError, Array.from_buffer_copy, b"123") + self.assertRaises(TypeError, Structure.from_buffer_copy, b"123") + self.assertRaises(TypeError, Union.from_buffer_copy, b"123") + self.assertRaises(TypeError, _CFuncPtr.from_buffer_copy, b"123") + self.assertRaises(TypeError, _Pointer.from_buffer_copy, b"123") + self.assertRaises(TypeError, _SimpleCData.from_buffer_copy, b"123") + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_funcptr.py b/Lib/test/test_ctypes/test_funcptr.py new file mode 100644 index 0000000000..8362fb16d9 --- /dev/null +++ b/Lib/test/test_ctypes/test_funcptr.py @@ -0,0 +1,134 @@ +import ctypes +import unittest +from ctypes import (CDLL, Structure, CFUNCTYPE, sizeof, _CFuncPtr, + c_void_p, c_char_p, c_char, c_int, c_uint, c_long) +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") +from ._support import (_CData, PyCFuncPtrType, Py_TPFLAGS_DISALLOW_INSTANTIATION, + Py_TPFLAGS_IMMUTABLETYPE) + + +try: + WINFUNCTYPE = ctypes.WINFUNCTYPE +except AttributeError: + # fake to enable this test on Linux + WINFUNCTYPE = CFUNCTYPE + +lib = CDLL(_ctypes_test.__file__) + + +class CFuncPtrTestCase(unittest.TestCase): + def test_inheritance_hierarchy(self): + self.assertEqual(_CFuncPtr.mro(), [_CFuncPtr, _CData, object]) + + self.assertEqual(PyCFuncPtrType.__name__, "PyCFuncPtrType") + self.assertEqual(type(PyCFuncPtrType), type) + + def test_type_flags(self): + for cls in _CFuncPtr, PyCFuncPtrType: + with self.subTest(cls=cls): + self.assertTrue(_CFuncPtr.__flags__ & Py_TPFLAGS_IMMUTABLETYPE) + self.assertFalse(_CFuncPtr.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION) + + def test_metaclass_details(self): + # Cannot call the metaclass __init__ more than once + CdeclCallback = CFUNCTYPE(c_int, c_int, c_int) + with self.assertRaisesRegex(SystemError, "already initialized"): + PyCFuncPtrType.__init__(CdeclCallback, 'ptr', (), {}) + + def test_basic(self): + X = WINFUNCTYPE(c_int, c_int, c_int) + + def func(*args): + return len(args) + + x = X(func) + self.assertEqual(x.restype, c_int) + self.assertEqual(x.argtypes, (c_int, c_int)) + self.assertEqual(sizeof(x), sizeof(c_void_p)) + self.assertEqual(sizeof(X), sizeof(c_void_p)) + + def test_first(self): + StdCallback = WINFUNCTYPE(c_int, c_int, c_int) + CdeclCallback = CFUNCTYPE(c_int, c_int, c_int) + + def func(a, b): + return a + b + + s = StdCallback(func) + c = CdeclCallback(func) + + self.assertEqual(s(1, 2), 3) + self.assertEqual(c(1, 2), 3) + # The following no longer raises a TypeError - it is now + # possible, as in C, to call cdecl functions with more parameters. + #self.assertRaises(TypeError, c, 1, 2, 3) + self.assertEqual(c(1, 2, 3, 4, 5, 6), 3) + if WINFUNCTYPE is not CFUNCTYPE: + self.assertRaises(TypeError, s, 1, 2, 3) + + def test_structures(self): + WNDPROC = WINFUNCTYPE(c_long, c_int, c_int, c_int, c_int) + + def wndproc(hwnd, msg, wParam, lParam): + return hwnd + msg + wParam + lParam + + HINSTANCE = c_int + HICON = c_int + HCURSOR = c_int + LPCTSTR = c_char_p + + class WNDCLASS(Structure): + _fields_ = [("style", c_uint), + ("lpfnWndProc", WNDPROC), + ("cbClsExtra", c_int), + ("cbWndExtra", c_int), + ("hInstance", HINSTANCE), + ("hIcon", HICON), + ("hCursor", HCURSOR), + ("lpszMenuName", LPCTSTR), + ("lpszClassName", LPCTSTR)] + + wndclass = WNDCLASS() + wndclass.lpfnWndProc = WNDPROC(wndproc) + + WNDPROC_2 = WINFUNCTYPE(c_long, c_int, c_int, c_int, c_int) + + self.assertIs(WNDPROC, WNDPROC_2) + self.assertEqual(wndclass.lpfnWndProc(1, 2, 3, 4), 10) + + f = wndclass.lpfnWndProc + + del wndclass + del wndproc + + self.assertEqual(f(10, 11, 12, 13), 46) + + def test_dllfunctions(self): + strchr = lib.my_strchr + strchr.restype = c_char_p + strchr.argtypes = (c_char_p, c_char) + self.assertEqual(strchr(b"abcdefghi", b"b"), b"bcdefghi") + self.assertEqual(strchr(b"abcdefghi", b"x"), None) + + strtok = lib.my_strtok + strtok.restype = c_char_p + + def c_string(init): + size = len(init) + 1 + return (c_char*size)(*init) + + s = b"a\nb\nc" + b = c_string(s) + + self.assertEqual(strtok(b, b"\n"), b"a") + self.assertEqual(strtok(None, b"\n"), b"b") + self.assertEqual(strtok(None, b"\n"), b"c") + self.assertEqual(strtok(None, b"\n"), None) + + def test_abstract(self): + self.assertRaises(TypeError, _CFuncPtr, 13, "name", 42, "iid") + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_functions.py b/Lib/test/test_ctypes/test_functions.py new file mode 100644 index 0000000000..63e393f7b7 --- /dev/null +++ b/Lib/test/test_ctypes/test_functions.py @@ -0,0 +1,454 @@ +import ctypes +import sys +import unittest +from ctypes import (CDLL, Structure, Array, CFUNCTYPE, + byref, POINTER, pointer, ArgumentError, + c_char, c_wchar, c_byte, c_char_p, c_wchar_p, + c_short, c_int, c_long, c_longlong, c_void_p, + c_float, c_double, c_longdouble) +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") +from _ctypes import _Pointer, _SimpleCData + + +try: + WINFUNCTYPE = ctypes.WINFUNCTYPE +except AttributeError: + # fake to enable this test on Linux + WINFUNCTYPE = CFUNCTYPE + +dll = CDLL(_ctypes_test.__file__) +if sys.platform == "win32": + windll = ctypes.WinDLL(_ctypes_test.__file__) + + +class POINT(Structure): + _fields_ = [("x", c_int), ("y", c_int)] + + +class RECT(Structure): + _fields_ = [("left", c_int), ("top", c_int), + ("right", c_int), ("bottom", c_int)] + + +class FunctionTestCase(unittest.TestCase): + + def test_mro(self): + # in Python 2.3, this raises TypeError: MRO conflict among bases classes, + # in Python 2.2 it works. + # + # But in early versions of _ctypes.c, the result of tp_new + # wasn't checked, and it even crashed Python. + # Found by Greg Chapman. + + with self.assertRaises(TypeError): + class X(object, Array): + _length_ = 5 + _type_ = "i" + + with self.assertRaises(TypeError): + class X2(object, _Pointer): + pass + + with self.assertRaises(TypeError): + class X3(object, _SimpleCData): + _type_ = "i" + + with self.assertRaises(TypeError): + class X4(object, Structure): + _fields_ = [] + + def test_c_char_parm(self): + proto = CFUNCTYPE(c_int, c_char) + def callback(*args): + return 0 + + callback = proto(callback) + + self.assertEqual(callback(b"a"), 0) + + with self.assertRaises(ArgumentError) as cm: + callback(b"abc") + + self.assertEqual(str(cm.exception), + "argument 1: TypeError: one character bytes, " + "bytearray or integer expected") + + def test_wchar_parm(self): + f = dll._testfunc_i_bhilfd + f.argtypes = [c_byte, c_wchar, c_int, c_long, c_float, c_double] + result = f(1, "x", 3, 4, 5.0, 6.0) + self.assertEqual(result, 139) + self.assertEqual(type(result), int) + + with self.assertRaises(ArgumentError) as cm: + f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(str(cm.exception), + "argument 2: TypeError: unicode string expected " + "instead of int instance") + + with self.assertRaises(ArgumentError) as cm: + f(1, "abc", 3, 4, 5.0, 6.0) + self.assertEqual(str(cm.exception), + "argument 2: TypeError: one character unicode string " + "expected") + + def test_c_char_p_parm(self): + """Test the error message when converting an incompatible type to c_char_p.""" + proto = CFUNCTYPE(c_int, c_char_p) + def callback(*args): + return 0 + + callback = proto(callback) + self.assertEqual(callback(b"abc"), 0) + + with self.assertRaises(ArgumentError) as cm: + callback(10) + + self.assertEqual(str(cm.exception), + "argument 1: TypeError: 'int' object cannot be " + "interpreted as ctypes.c_char_p") + + def test_c_wchar_p_parm(self): + """Test the error message when converting an incompatible type to c_wchar_p.""" + proto = CFUNCTYPE(c_int, c_wchar_p) + def callback(*args): + return 0 + + callback = proto(callback) + self.assertEqual(callback("abc"), 0) + + with self.assertRaises(ArgumentError) as cm: + callback(10) + + self.assertEqual(str(cm.exception), + "argument 1: TypeError: 'int' object cannot be " + "interpreted as ctypes.c_wchar_p") + + def test_c_void_p_parm(self): + """Test the error message when converting an incompatible type to c_void_p.""" + proto = CFUNCTYPE(c_int, c_void_p) + def callback(*args): + return 0 + + callback = proto(callback) + self.assertEqual(callback(5), 0) + + with self.assertRaises(ArgumentError) as cm: + callback(2.5) + + self.assertEqual(str(cm.exception), + "argument 1: TypeError: 'float' object cannot be " + "interpreted as ctypes.c_void_p") + + def test_wchar_result(self): + f = dll._testfunc_i_bhilfd + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + f.restype = c_wchar + result = f(0, 0, 0, 0, 0, 0) + self.assertEqual(result, '\x00') + + def test_voidresult(self): + f = dll._testfunc_v + f.restype = None + f.argtypes = [c_int, c_int, POINTER(c_int)] + result = c_int() + self.assertEqual(None, f(1, 2, byref(result))) + self.assertEqual(result.value, 3) + + def test_intresult(self): + f = dll._testfunc_i_bhilfd + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + f.restype = c_int + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), int) + + result = f(-1, -2, -3, -4, -5.0, -6.0) + self.assertEqual(result, -21) + self.assertEqual(type(result), int) + + # If we declare the function to return a short, + # is the high part split off? + f.restype = c_short + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), int) + + result = f(1, 2, 3, 0x10004, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), int) + + # You cannot assign character format codes as restype any longer + self.assertRaises(TypeError, setattr, f, "restype", "i") + + def test_floatresult(self): + f = dll._testfunc_f_bhilfd + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + f.restype = c_float + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), float) + + result = f(-1, -2, -3, -4, -5.0, -6.0) + self.assertEqual(result, -21) + self.assertEqual(type(result), float) + + def test_doubleresult(self): + f = dll._testfunc_d_bhilfd + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + f.restype = c_double + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), float) + + result = f(-1, -2, -3, -4, -5.0, -6.0) + self.assertEqual(result, -21) + self.assertEqual(type(result), float) + + def test_longdoubleresult(self): + f = dll._testfunc_D_bhilfD + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_longdouble] + f.restype = c_longdouble + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), float) + + result = f(-1, -2, -3, -4, -5.0, -6.0) + self.assertEqual(result, -21) + self.assertEqual(type(result), float) + + def test_longlongresult(self): + f = dll._testfunc_q_bhilfd + f.restype = c_longlong + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + + f = dll._testfunc_q_bhilfdq + f.restype = c_longlong + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double, c_longlong] + result = f(1, 2, 3, 4, 5.0, 6.0, 21) + self.assertEqual(result, 42) + + def test_stringresult(self): + f = dll._testfunc_p_p + f.argtypes = None + f.restype = c_char_p + result = f(b"123") + self.assertEqual(result, b"123") + + result = f(None) + self.assertEqual(result, None) + + def test_pointers(self): + f = dll._testfunc_p_p + f.restype = POINTER(c_int) + f.argtypes = [POINTER(c_int)] + + # This only works if the value c_int(42) passed to the + # function is still alive while the pointer (the result) is + # used. + + v = c_int(42) + + self.assertEqual(pointer(v).contents.value, 42) + result = f(pointer(v)) + self.assertEqual(type(result), POINTER(c_int)) + self.assertEqual(result.contents.value, 42) + + # This on works... + result = f(pointer(v)) + self.assertEqual(result.contents.value, v.value) + + p = pointer(c_int(99)) + result = f(p) + self.assertEqual(result.contents.value, 99) + + arg = byref(v) + result = f(arg) + self.assertNotEqual(result.contents, v.value) + + self.assertRaises(ArgumentError, f, byref(c_short(22))) + + # It is dangerous, however, because you don't control the lifetime + # of the pointer: + result = f(byref(c_int(99))) + self.assertNotEqual(result.contents, 99) + + def test_shorts(self): + f = dll._testfunc_callback_i_if + + args = [] + expected = [262144, 131072, 65536, 32768, 16384, 8192, 4096, 2048, + 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1] + + def callback(v): + args.append(v) + return v + + CallBack = CFUNCTYPE(c_int, c_int) + + cb = CallBack(callback) + f(2**18, cb) + self.assertEqual(args, expected) + + def test_callbacks(self): + f = dll._testfunc_callback_i_if + f.restype = c_int + f.argtypes = None + + MyCallback = CFUNCTYPE(c_int, c_int) + + def callback(value): + return value + + cb = MyCallback(callback) + result = f(-10, cb) + self.assertEqual(result, -18) + + # test with prototype + f.argtypes = [c_int, MyCallback] + cb = MyCallback(callback) + result = f(-10, cb) + self.assertEqual(result, -18) + + AnotherCallback = WINFUNCTYPE(c_int, c_int, c_int, c_int, c_int) + + # check that the prototype works: we call f with wrong + # argument types + cb = AnotherCallback(callback) + self.assertRaises(ArgumentError, f, -10, cb) + + + def test_callbacks_2(self): + # Can also use simple datatypes as argument type specifiers + # for the callback function. + # In this case the call receives an instance of that type + f = dll._testfunc_callback_i_if + f.restype = c_int + + MyCallback = CFUNCTYPE(c_int, c_int) + + f.argtypes = [c_int, MyCallback] + + def callback(value): + self.assertEqual(type(value), int) + return value + + cb = MyCallback(callback) + result = f(-10, cb) + self.assertEqual(result, -18) + + def test_longlong_callbacks(self): + + f = dll._testfunc_callback_q_qf + f.restype = c_longlong + + MyCallback = CFUNCTYPE(c_longlong, c_longlong) + + f.argtypes = [c_longlong, MyCallback] + + def callback(value): + self.assertIsInstance(value, int) + return value & 0x7FFFFFFF + + cb = MyCallback(callback) + + self.assertEqual(13577625587, f(1000000000000, cb)) + + def test_errors(self): + self.assertRaises(AttributeError, getattr, dll, "_xxx_yyy") + self.assertRaises(ValueError, c_int.in_dll, dll, "_xxx_yyy") + + def test_byval(self): + + # without prototype + ptin = POINT(1, 2) + ptout = POINT() + # EXPORT int _testfunc_byval(point in, point *pout) + result = dll._testfunc_byval(ptin, byref(ptout)) + got = result, ptout.x, ptout.y + expected = 3, 1, 2 + self.assertEqual(got, expected) + + # with prototype + ptin = POINT(101, 102) + ptout = POINT() + dll._testfunc_byval.argtypes = (POINT, POINTER(POINT)) + dll._testfunc_byval.restype = c_int + result = dll._testfunc_byval(ptin, byref(ptout)) + got = result, ptout.x, ptout.y + expected = 203, 101, 102 + self.assertEqual(got, expected) + + def test_struct_return_2H(self): + class S2H(Structure): + _fields_ = [("x", c_short), + ("y", c_short)] + dll.ret_2h_func.restype = S2H + dll.ret_2h_func.argtypes = [S2H] + inp = S2H(99, 88) + s2h = dll.ret_2h_func(inp) + self.assertEqual((s2h.x, s2h.y), (99*2, 88*3)) + + @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') + def test_struct_return_2H_stdcall(self): + class S2H(Structure): + _fields_ = [("x", c_short), + ("y", c_short)] + + windll.s_ret_2h_func.restype = S2H + windll.s_ret_2h_func.argtypes = [S2H] + s2h = windll.s_ret_2h_func(S2H(99, 88)) + self.assertEqual((s2h.x, s2h.y), (99*2, 88*3)) + + def test_struct_return_8H(self): + class S8I(Structure): + _fields_ = [("a", c_int), + ("b", c_int), + ("c", c_int), + ("d", c_int), + ("e", c_int), + ("f", c_int), + ("g", c_int), + ("h", c_int)] + dll.ret_8i_func.restype = S8I + dll.ret_8i_func.argtypes = [S8I] + inp = S8I(9, 8, 7, 6, 5, 4, 3, 2) + s8i = dll.ret_8i_func(inp) + self.assertEqual((s8i.a, s8i.b, s8i.c, s8i.d, s8i.e, s8i.f, s8i.g, s8i.h), + (9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9)) + + @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') + def test_struct_return_8H_stdcall(self): + class S8I(Structure): + _fields_ = [("a", c_int), + ("b", c_int), + ("c", c_int), + ("d", c_int), + ("e", c_int), + ("f", c_int), + ("g", c_int), + ("h", c_int)] + windll.s_ret_8i_func.restype = S8I + windll.s_ret_8i_func.argtypes = [S8I] + inp = S8I(9, 8, 7, 6, 5, 4, 3, 2) + s8i = windll.s_ret_8i_func(inp) + self.assertEqual( + (s8i.a, s8i.b, s8i.c, s8i.d, s8i.e, s8i.f, s8i.g, s8i.h), + (9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9)) + + def test_sf1651235(self): + # see https://bugs.python.org/issue1651235 + + proto = CFUNCTYPE(c_int, RECT, POINT) + def callback(*args): + return 0 + + callback = proto(callback) + self.assertRaises(ArgumentError, lambda: callback((1, 2, 3, 4), POINT())) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_incomplete.py b/Lib/test/test_ctypes/test_incomplete.py new file mode 100644 index 0000000000..9f859793d8 --- /dev/null +++ b/Lib/test/test_ctypes/test_incomplete.py @@ -0,0 +1,50 @@ +import ctypes +import unittest +import warnings +from ctypes import Structure, POINTER, pointer, c_char_p + + +# The incomplete pointer example from the tutorial +class TestSetPointerType(unittest.TestCase): + def tearDown(self): + # to not leak references, we must clean _pointer_type_cache + ctypes._reset_cache() + + def test_incomplete_example(self): + lpcell = POINTER("cell") + class cell(Structure): + _fields_ = [("name", c_char_p), + ("next", lpcell)] + + with warnings.catch_warnings(): + warnings.simplefilter('ignore', DeprecationWarning) + ctypes.SetPointerType(lpcell, cell) + + c1 = cell() + c1.name = b"foo" + c2 = cell() + c2.name = b"bar" + + c1.next = pointer(c2) + c2.next = pointer(c1) + + p = c1 + + result = [] + for i in range(8): + result.append(p.name) + p = p.next[0] + self.assertEqual(result, [b"foo", b"bar"] * 4) + + def test_deprecation(self): + lpcell = POINTER("cell") + class cell(Structure): + _fields_ = [("name", c_char_p), + ("next", lpcell)] + + with self.assertWarns(DeprecationWarning): + ctypes.SetPointerType(lpcell, cell) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_init.py b/Lib/test/test_ctypes/test_init.py new file mode 100644 index 0000000000..113425e582 --- /dev/null +++ b/Lib/test/test_ctypes/test_init.py @@ -0,0 +1,43 @@ +import unittest +from ctypes import Structure, c_int + + +class X(Structure): + _fields_ = [("a", c_int), + ("b", c_int)] + new_was_called = False + + def __new__(cls): + result = super().__new__(cls) + result.new_was_called = True + return result + + def __init__(self): + self.a = 9 + self.b = 12 + + +class Y(Structure): + _fields_ = [("x", X)] + + +class InitTest(unittest.TestCase): + def test_get(self): + # make sure the only accessing a nested structure + # doesn't call the structure's __new__ and __init__ + y = Y() + self.assertEqual((y.x.a, y.x.b), (0, 0)) + self.assertEqual(y.x.new_was_called, False) + + # But explicitly creating an X structure calls __new__ and __init__, of course. + x = X() + self.assertEqual((x.a, x.b), (9, 12)) + self.assertEqual(x.new_was_called, True) + + y.x = x + self.assertEqual((y.x.a, y.x.b), (9, 12)) + self.assertEqual(y.x.new_was_called, False) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_internals.py b/Lib/test/test_ctypes/test_internals.py new file mode 100644 index 0000000000..778da6573d --- /dev/null +++ b/Lib/test/test_ctypes/test_internals.py @@ -0,0 +1,97 @@ +# This tests the internal _objects attribute + +# XXX This test must be reviewed for correctness!!! + +# ctypes' types are container types. +# +# They have an internal memory block, which only consists of some bytes, +# but it has to keep references to other objects as well. This is not +# really needed for trivial C types like int or char, but it is important +# for aggregate types like strings or pointers in particular. +# +# What about pointers? + +import sys +import unittest +from ctypes import Structure, POINTER, c_char_p, c_int + + +class ObjectsTestCase(unittest.TestCase): + def assertSame(self, a, b): + self.assertEqual(id(a), id(b)) + + def test_ints(self): + i = 42000123 + refcnt = sys.getrefcount(i) + ci = c_int(i) + self.assertEqual(refcnt, sys.getrefcount(i)) + self.assertEqual(ci._objects, None) + + def test_c_char_p(self): + s = "Hello, World".encode("ascii") + refcnt = sys.getrefcount(s) + cs = c_char_p(s) + self.assertEqual(refcnt + 1, sys.getrefcount(s)) + self.assertSame(cs._objects, s) + + def test_simple_struct(self): + class X(Structure): + _fields_ = [("a", c_int), ("b", c_int)] + + a = 421234 + b = 421235 + x = X() + self.assertEqual(x._objects, None) + x.a = a + x.b = b + self.assertEqual(x._objects, None) + + def test_embedded_structs(self): + class X(Structure): + _fields_ = [("a", c_int), ("b", c_int)] + + class Y(Structure): + _fields_ = [("x", X), ("y", X)] + + y = Y() + self.assertEqual(y._objects, None) + + x1, x2 = X(), X() + y.x, y.y = x1, x2 + self.assertEqual(y._objects, {"0": {}, "1": {}}) + x1.a, x2.b = 42, 93 + self.assertEqual(y._objects, {"0": {}, "1": {}}) + + def test_xxx(self): + class X(Structure): + _fields_ = [("a", c_char_p), ("b", c_char_p)] + + class Y(Structure): + _fields_ = [("x", X), ("y", X)] + + s1 = b"Hello, World" + s2 = b"Hallo, Welt" + + x = X() + x.a = s1 + x.b = s2 + self.assertEqual(x._objects, {"0": s1, "1": s2}) + + y = Y() + y.x = x + self.assertEqual(y._objects, {"0": {"0": s1, "1": s2}}) + + def test_ptr_struct(self): + class X(Structure): + _fields_ = [("data", POINTER(c_int))] + + A = c_int*4 + a = A(11, 22, 33, 44) + self.assertEqual(a._objects, None) + + x = X() + x.data = a + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_keeprefs.py b/Lib/test/test_ctypes/test_keeprefs.py new file mode 100644 index 0000000000..23b03b64b4 --- /dev/null +++ b/Lib/test/test_ctypes/test_keeprefs.py @@ -0,0 +1,124 @@ +import unittest +from ctypes import (Structure, POINTER, pointer, _pointer_type_cache, + c_char_p, c_int) + + +class SimpleTestCase(unittest.TestCase): + def test_cint(self): + x = c_int() + self.assertEqual(x._objects, None) + x.value = 42 + self.assertEqual(x._objects, None) + x = c_int(99) + self.assertEqual(x._objects, None) + + def test_ccharp(self): + x = c_char_p() + self.assertEqual(x._objects, None) + x.value = b"abc" + self.assertEqual(x._objects, b"abc") + x = c_char_p(b"spam") + self.assertEqual(x._objects, b"spam") + + +class StructureTestCase(unittest.TestCase): + def test_cint_struct(self): + class X(Structure): + _fields_ = [("a", c_int), + ("b", c_int)] + + x = X() + self.assertEqual(x._objects, None) + x.a = 42 + x.b = 99 + self.assertEqual(x._objects, None) + + def test_ccharp_struct(self): + class X(Structure): + _fields_ = [("a", c_char_p), + ("b", c_char_p)] + x = X() + self.assertEqual(x._objects, None) + + x.a = b"spam" + x.b = b"foo" + self.assertEqual(x._objects, {"0": b"spam", "1": b"foo"}) + + def test_struct_struct(self): + class POINT(Structure): + _fields_ = [("x", c_int), ("y", c_int)] + class RECT(Structure): + _fields_ = [("ul", POINT), ("lr", POINT)] + + r = RECT() + r.ul.x = 0 + r.ul.y = 1 + r.lr.x = 2 + r.lr.y = 3 + self.assertEqual(r._objects, None) + + r = RECT() + pt = POINT(1, 2) + r.ul = pt + self.assertEqual(r._objects, {'0': {}}) + r.ul.x = 22 + r.ul.y = 44 + self.assertEqual(r._objects, {'0': {}}) + r.lr = POINT() + self.assertEqual(r._objects, {'0': {}, '1': {}}) + + +class ArrayTestCase(unittest.TestCase): + def test_cint_array(self): + INTARR = c_int * 3 + + ia = INTARR() + self.assertEqual(ia._objects, None) + ia[0] = 1 + ia[1] = 2 + ia[2] = 3 + self.assertEqual(ia._objects, None) + + class X(Structure): + _fields_ = [("x", c_int), + ("a", INTARR)] + + x = X() + x.x = 1000 + x.a[0] = 42 + x.a[1] = 96 + self.assertEqual(x._objects, None) + x.a = ia + self.assertEqual(x._objects, {'1': {}}) + + +class PointerTestCase(unittest.TestCase): + def test_p_cint(self): + i = c_int(42) + x = pointer(i) + self.assertEqual(x._objects, {'1': i}) + + +class PointerToStructure(unittest.TestCase): + def test(self): + class POINT(Structure): + _fields_ = [("x", c_int), ("y", c_int)] + class RECT(Structure): + _fields_ = [("a", POINTER(POINT)), + ("b", POINTER(POINT))] + r = RECT() + p1 = POINT(1, 2) + + r.a = pointer(p1) + r.b = pointer(p1) + + r.a[0].x = 42 + r.a[0].y = 99 + + # to avoid leaking when tests are run several times + # clean up the types left in the cache. + del _pointer_type_cache[POINT] + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_libc.py b/Lib/test/test_ctypes/test_libc.py new file mode 100644 index 0000000000..7716100b08 --- /dev/null +++ b/Lib/test/test_ctypes/test_libc.py @@ -0,0 +1,38 @@ +import math +import unittest +from ctypes import (CDLL, CFUNCTYPE, POINTER, create_string_buffer, sizeof, + c_void_p, c_char, c_int, c_double, c_size_t) +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +lib = CDLL(_ctypes_test.__file__) + + +def three_way_cmp(x, y): + """Return -1 if x < y, 0 if x == y and 1 if x > y""" + return (x > y) - (x < y) + + +class LibTest(unittest.TestCase): + def test_sqrt(self): + lib.my_sqrt.argtypes = c_double, + lib.my_sqrt.restype = c_double + self.assertEqual(lib.my_sqrt(4.0), 2.0) + self.assertEqual(lib.my_sqrt(2.0), math.sqrt(2.0)) + + def test_qsort(self): + comparefunc = CFUNCTYPE(c_int, POINTER(c_char), POINTER(c_char)) + lib.my_qsort.argtypes = c_void_p, c_size_t, c_size_t, comparefunc + lib.my_qsort.restype = None + + def sort(a, b): + return three_way_cmp(a[0], b[0]) + + chars = create_string_buffer(b"spam, spam, and spam") + lib.my_qsort(chars, len(chars)-1, sizeof(c_char), comparefunc(sort)) + self.assertEqual(chars.raw, b" ,,aaaadmmmnpppsss\x00") + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_loading.py b/Lib/test/test_ctypes/test_loading.py new file mode 100644 index 0000000000..a4d54f676a --- /dev/null +++ b/Lib/test/test_ctypes/test_loading.py @@ -0,0 +1,208 @@ +import _ctypes +import ctypes +import os +import shutil +import subprocess +import sys +import test.support +import unittest +from ctypes import CDLL, cdll, addressof, c_void_p, c_char_p +from ctypes.util import find_library +from test.support import import_helper, os_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +libc_name = None + + +def setUpModule(): + global libc_name + if os.name == "nt": + libc_name = find_library("c") + elif sys.platform == "cygwin": + libc_name = "cygwin1.dll" + else: + libc_name = find_library("c") + + if test.support.verbose: + print("libc_name is", libc_name) + + +class LoaderTest(unittest.TestCase): + + unknowndll = "xxrandomnamexx" + + def test_load(self): + if libc_name is not None: + test_lib = libc_name + else: + if os.name == "nt": + test_lib = _ctypes_test.__file__ + else: + self.skipTest('could not find library to load') + CDLL(test_lib) + CDLL(os.path.basename(test_lib)) + CDLL(os_helper.FakePath(test_lib)) + self.assertRaises(OSError, CDLL, self.unknowndll) + + def test_load_version(self): + if libc_name is None: + self.skipTest('could not find libc') + if os.path.basename(libc_name) != 'libc.so.6': + self.skipTest('wrong libc path for test') + cdll.LoadLibrary("libc.so.6") + # linux uses version, libc 9 should not exist + self.assertRaises(OSError, cdll.LoadLibrary, "libc.so.9") + self.assertRaises(OSError, cdll.LoadLibrary, self.unknowndll) + + def test_find(self): + found = False + for name in ("c", "m"): + lib = find_library(name) + if lib: + found = True + cdll.LoadLibrary(lib) + CDLL(lib) + if not found: + self.skipTest("Could not find c and m libraries") + + @unittest.skipUnless(os.name == "nt", + 'test specific to Windows') + def test_load_library(self): + # CRT is no longer directly loadable. See issue23606 for the + # discussion about alternative approaches. + #self.assertIsNotNone(libc_name) + if test.support.verbose: + print(find_library("kernel32")) + print(find_library("user32")) + + if os.name == "nt": + ctypes.windll.kernel32.GetModuleHandleW + ctypes.windll["kernel32"].GetModuleHandleW + ctypes.windll.LoadLibrary("kernel32").GetModuleHandleW + ctypes.WinDLL("kernel32").GetModuleHandleW + # embedded null character + self.assertRaises(ValueError, ctypes.windll.LoadLibrary, "kernel32\0") + + @unittest.skipUnless(os.name == "nt", + 'test specific to Windows') + def test_load_ordinal_functions(self): + dll = ctypes.WinDLL(_ctypes_test.__file__) + # We load the same function both via ordinal and name + func_ord = dll[2] + func_name = dll.GetString + # addressof gets the address where the function pointer is stored + a_ord = addressof(func_ord) + a_name = addressof(func_name) + f_ord_addr = c_void_p.from_address(a_ord).value + f_name_addr = c_void_p.from_address(a_name).value + self.assertEqual(hex(f_ord_addr), hex(f_name_addr)) + + self.assertRaises(AttributeError, dll.__getitem__, 1234) + + @unittest.skipUnless(os.name == "nt", 'Windows-specific test') + def test_load_without_name_and_with_handle(self): + handle = ctypes.windll.kernel32._handle + lib = ctypes.WinDLL(name=None, handle=handle) + self.assertIs(handle, lib._handle) + + @unittest.skipUnless(os.name == "nt", 'Windows-specific test') + def test_1703286_A(self): + # On winXP 64-bit, advapi32 loads at an address that does + # NOT fit into a 32-bit integer. FreeLibrary must be able + # to accept this address. + + # These are tests for https://bugs.python.org/issue1703286 + handle = _ctypes.LoadLibrary("advapi32") + _ctypes.FreeLibrary(handle) + + @unittest.skipUnless(os.name == "nt", 'Windows-specific test') + def test_1703286_B(self): + # Since on winXP 64-bit advapi32 loads like described + # above, the (arbitrarily selected) CloseEventLog function + # also has a high address. 'call_function' should accept + # addresses so large. + + advapi32 = ctypes.windll.advapi32 + # Calling CloseEventLog with a NULL argument should fail, + # but the call should not segfault or so. + self.assertEqual(0, advapi32.CloseEventLog(None)) + + kernel32 = ctypes.windll.kernel32 + kernel32.GetProcAddress.argtypes = c_void_p, c_char_p + kernel32.GetProcAddress.restype = c_void_p + proc = kernel32.GetProcAddress(advapi32._handle, b"CloseEventLog") + self.assertTrue(proc) + + # This is the real test: call the function via 'call_function' + self.assertEqual(0, _ctypes.call_function(proc, (None,))) + + @unittest.skipUnless(os.name == "nt", + 'test specific to Windows') + def test_load_hasattr(self): + # bpo-34816: shouldn't raise OSError + self.assertFalse(hasattr(ctypes.windll, 'test')) + + @unittest.skipUnless(os.name == "nt", + 'test specific to Windows') + def test_load_dll_with_flags(self): + _sqlite3 = import_helper.import_module("_sqlite3") + src = _sqlite3.__file__ + if os.path.basename(src).partition(".")[0].lower().endswith("_d"): + ext = "_d.dll" + else: + ext = ".dll" + + with os_helper.temp_dir() as tmp: + # We copy two files and load _sqlite3.dll (formerly .pyd), + # which has a dependency on sqlite3.dll. Then we test + # loading it in subprocesses to avoid it starting in memory + # for each test. + target = os.path.join(tmp, "_sqlite3.dll") + shutil.copy(src, target) + shutil.copy(os.path.join(os.path.dirname(src), "sqlite3" + ext), + os.path.join(tmp, "sqlite3" + ext)) + + def should_pass(command): + with self.subTest(command): + subprocess.check_output( + [sys.executable, "-c", + "from ctypes import *; import nt;" + command], + cwd=tmp + ) + + def should_fail(command): + with self.subTest(command): + with self.assertRaises(subprocess.CalledProcessError): + subprocess.check_output( + [sys.executable, "-c", + "from ctypes import *; import nt;" + command], + cwd=tmp, stderr=subprocess.STDOUT, + ) + + # Default load should not find this in CWD + should_fail("WinDLL('_sqlite3.dll')") + + # Relative path (but not just filename) should succeed + should_pass("WinDLL('./_sqlite3.dll')") + + # Insecure load flags should succeed + # Clear the DLL directory to avoid safe search settings propagating + should_pass("windll.kernel32.SetDllDirectoryW(None); WinDLL('_sqlite3.dll', winmode=0)") + + # Full path load without DLL_LOAD_DIR shouldn't find dependency + should_fail("WinDLL(nt._getfullpathname('_sqlite3.dll'), " + + "winmode=nt._LOAD_LIBRARY_SEARCH_SYSTEM32)") + + # Full path load with DLL_LOAD_DIR should succeed + should_pass("WinDLL(nt._getfullpathname('_sqlite3.dll'), " + + "winmode=nt._LOAD_LIBRARY_SEARCH_SYSTEM32|" + + "nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR)") + + # User-specified directory should succeed + should_pass("import os; p = os.add_dll_directory(os.getcwd());" + + "WinDLL('_sqlite3.dll'); p.close()") + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_macholib.py b/Lib/test/test_ctypes/test_macholib.py new file mode 100644 index 0000000000..9d90617995 --- /dev/null +++ b/Lib/test/test_ctypes/test_macholib.py @@ -0,0 +1,112 @@ +# Bob Ippolito: +# +# Ok.. the code to find the filename for __getattr__ should look +# something like: +# +# import os +# from macholib.dyld import dyld_find +# +# def find_lib(name): +# possible = ['lib'+name+'.dylib', name+'.dylib', +# name+'.framework/'+name] +# for dylib in possible: +# try: +# return os.path.realpath(dyld_find(dylib)) +# except ValueError: +# pass +# raise ValueError, "%s not found" % (name,) +# +# It'll have output like this: +# +# >>> find_lib('pthread') +# '/usr/lib/libSystem.B.dylib' +# >>> find_lib('z') +# '/usr/lib/libz.1.dylib' +# >>> find_lib('IOKit') +# '/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit' +# +# -bob + +import os +import sys +import unittest + +from ctypes.macholib.dyld import dyld_find +from ctypes.macholib.dylib import dylib_info +from ctypes.macholib.framework import framework_info + + +def find_lib(name): + possible = ['lib'+name+'.dylib', name+'.dylib', name+'.framework/'+name] + for dylib in possible: + try: + return os.path.realpath(dyld_find(dylib)) + except ValueError: + pass + raise ValueError("%s not found" % (name,)) + + +def d(location=None, name=None, shortname=None, version=None, suffix=None): + return {'location': location, 'name': name, 'shortname': shortname, + 'version': version, 'suffix': suffix} + + +class MachOTest(unittest.TestCase): + @unittest.skipUnless(sys.platform == "darwin", 'OSX-specific test') + def test_find(self): + self.assertEqual(dyld_find('libSystem.dylib'), + '/usr/lib/libSystem.dylib') + self.assertEqual(dyld_find('System.framework/System'), + '/System/Library/Frameworks/System.framework/System') + + # On Mac OS 11, system dylibs are only present in the shared cache, + # so symlinks like libpthread.dylib -> libSystem.B.dylib will not + # be resolved by dyld_find + self.assertIn(find_lib('pthread'), + ('/usr/lib/libSystem.B.dylib', '/usr/lib/libpthread.dylib')) + + result = find_lib('z') + # Issue #21093: dyld default search path includes $HOME/lib and + # /usr/local/lib before /usr/lib, which caused test failures if + # a local copy of libz exists in one of them. Now ignore the head + # of the path. + self.assertRegex(result, r".*/lib/libz.*\.dylib") + + self.assertIn(find_lib('IOKit'), + ('/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit', + '/System/Library/Frameworks/IOKit.framework/IOKit')) + + @unittest.skipUnless(sys.platform == "darwin", 'OSX-specific test') + def test_info(self): + self.assertIsNone(dylib_info('completely/invalid')) + self.assertIsNone(dylib_info('completely/invalide_debug')) + self.assertEqual(dylib_info('P/Foo.dylib'), d('P', 'Foo.dylib', 'Foo')) + self.assertEqual(dylib_info('P/Foo_debug.dylib'), + d('P', 'Foo_debug.dylib', 'Foo', suffix='debug')) + self.assertEqual(dylib_info('P/Foo.A.dylib'), + d('P', 'Foo.A.dylib', 'Foo', 'A')) + self.assertEqual(dylib_info('P/Foo_debug.A.dylib'), + d('P', 'Foo_debug.A.dylib', 'Foo_debug', 'A')) + self.assertEqual(dylib_info('P/Foo.A_debug.dylib'), + d('P', 'Foo.A_debug.dylib', 'Foo', 'A', 'debug')) + + @unittest.skipUnless(sys.platform == "darwin", 'OSX-specific test') + def test_framework_info(self): + self.assertIsNone(framework_info('completely/invalid')) + self.assertIsNone(framework_info('completely/invalid/_debug')) + self.assertIsNone(framework_info('P/F.framework')) + self.assertIsNone(framework_info('P/F.framework/_debug')) + self.assertEqual(framework_info('P/F.framework/F'), + d('P', 'F.framework/F', 'F')) + self.assertEqual(framework_info('P/F.framework/F_debug'), + d('P', 'F.framework/F_debug', 'F', suffix='debug')) + self.assertIsNone(framework_info('P/F.framework/Versions')) + self.assertIsNone(framework_info('P/F.framework/Versions/A')) + self.assertEqual(framework_info('P/F.framework/Versions/A/F'), + d('P', 'F.framework/Versions/A/F', 'F', 'A')) + self.assertEqual(framework_info('P/F.framework/Versions/A/F_debug'), + d('P', 'F.framework/Versions/A/F_debug', 'F', 'A', 'debug')) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_memfunctions.py b/Lib/test/test_ctypes/test_memfunctions.py new file mode 100644 index 0000000000..112b27ba48 --- /dev/null +++ b/Lib/test/test_ctypes/test_memfunctions.py @@ -0,0 +1,82 @@ +import sys +import unittest +from test import support +from ctypes import (POINTER, sizeof, cast, + create_string_buffer, string_at, + create_unicode_buffer, wstring_at, + memmove, memset, + c_char_p, c_byte, c_ubyte, c_wchar) + + +class MemFunctionsTest(unittest.TestCase): + def test_overflow(self): + # string_at and wstring_at must use the Python calling + # convention (which acquires the GIL and checks the Python + # error flag). Provoke an error and catch it; see also issue + # gh-47804. + self.assertRaises((OverflowError, MemoryError, SystemError), + lambda: wstring_at(u"foo", sys.maxsize - 1)) + self.assertRaises((OverflowError, MemoryError, SystemError), + lambda: string_at("foo", sys.maxsize - 1)) + + def test_memmove(self): + # large buffers apparently increase the chance that the memory + # is allocated in high address space. + a = create_string_buffer(1000000) + p = b"Hello, World" + result = memmove(a, p, len(p)) + self.assertEqual(a.value, b"Hello, World") + + self.assertEqual(string_at(result), b"Hello, World") + self.assertEqual(string_at(result, 5), b"Hello") + self.assertEqual(string_at(result, 16), b"Hello, World\0\0\0\0") + self.assertEqual(string_at(result, 0), b"") + + def test_memset(self): + a = create_string_buffer(1000000) + result = memset(a, ord('x'), 16) + self.assertEqual(a.value, b"xxxxxxxxxxxxxxxx") + + self.assertEqual(string_at(result), b"xxxxxxxxxxxxxxxx") + self.assertEqual(string_at(a), b"xxxxxxxxxxxxxxxx") + self.assertEqual(string_at(a, 20), b"xxxxxxxxxxxxxxxx\0\0\0\0") + + def test_cast(self): + a = (c_ubyte * 32)(*map(ord, "abcdef")) + self.assertEqual(cast(a, c_char_p).value, b"abcdef") + self.assertEqual(cast(a, POINTER(c_byte))[:7], + [97, 98, 99, 100, 101, 102, 0]) + self.assertEqual(cast(a, POINTER(c_byte))[:7:], + [97, 98, 99, 100, 101, 102, 0]) + self.assertEqual(cast(a, POINTER(c_byte))[6:-1:-1], + [0, 102, 101, 100, 99, 98, 97]) + self.assertEqual(cast(a, POINTER(c_byte))[:7:2], + [97, 99, 101, 0]) + self.assertEqual(cast(a, POINTER(c_byte))[:7:7], + [97]) + + @support.refcount_test + def test_string_at(self): + s = string_at(b"foo bar") + # XXX The following may be wrong, depending on how Python + # manages string instances + self.assertEqual(2, sys.getrefcount(s)) + self.assertTrue(s, "foo bar") + + self.assertEqual(string_at(b"foo bar", 7), b"foo bar") + self.assertEqual(string_at(b"foo bar", 3), b"foo") + + def test_wstring_at(self): + p = create_unicode_buffer("Hello, World") + a = create_unicode_buffer(1000000) + result = memmove(a, p, len(p) * sizeof(c_wchar)) + self.assertEqual(a.value, "Hello, World") + + self.assertEqual(wstring_at(a), "Hello, World") + self.assertEqual(wstring_at(a, 5), "Hello") + self.assertEqual(wstring_at(a, 16), "Hello, World\0\0\0\0") + self.assertEqual(wstring_at(a, 0), "") + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_numbers.py b/Lib/test/test_ctypes/test_numbers.py new file mode 100644 index 0000000000..29108a28ec --- /dev/null +++ b/Lib/test/test_ctypes/test_numbers.py @@ -0,0 +1,200 @@ +import array +import struct +import sys +import unittest +from operator import truth +from ctypes import (byref, sizeof, alignment, + c_char, c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint, + c_long, c_ulong, c_longlong, c_ulonglong, + c_float, c_double, c_longdouble, c_bool) + + +def valid_ranges(*types): + # given a sequence of numeric types, collect their _type_ + # attribute, which is a single format character compatible with + # the struct module, use the struct module to calculate the + # minimum and maximum value allowed for this format. + # Returns a list of (min, max) values. + result = [] + for t in types: + fmt = t._type_ + size = struct.calcsize(fmt) + a = struct.unpack(fmt, (b"\x00"*32)[:size])[0] + b = struct.unpack(fmt, (b"\xFF"*32)[:size])[0] + c = struct.unpack(fmt, (b"\x7F"+b"\x00"*32)[:size])[0] + d = struct.unpack(fmt, (b"\x80"+b"\xFF"*32)[:size])[0] + result.append((min(a, b, c, d), max(a, b, c, d))) + return result + + +ArgType = type(byref(c_int(0))) + +unsigned_types = [c_ubyte, c_ushort, c_uint, c_ulong, c_ulonglong] +signed_types = [c_byte, c_short, c_int, c_long, c_longlong] +bool_types = [c_bool] +float_types = [c_double, c_float] + +unsigned_ranges = valid_ranges(*unsigned_types) +signed_ranges = valid_ranges(*signed_types) +bool_values = [True, False, 0, 1, -1, 5000, 'test', [], [1]] + + +class NumberTestCase(unittest.TestCase): + + def test_default_init(self): + # default values are set to zero + for t in signed_types + unsigned_types + float_types: + self.assertEqual(t().value, 0) + + def test_unsigned_values(self): + # the value given to the constructor is available + # as the 'value' attribute + for t, (l, h) in zip(unsigned_types, unsigned_ranges): + self.assertEqual(t(l).value, l) + self.assertEqual(t(h).value, h) + + def test_signed_values(self): + # see above + for t, (l, h) in zip(signed_types, signed_ranges): + self.assertEqual(t(l).value, l) + self.assertEqual(t(h).value, h) + + def test_bool_values(self): + for t, v in zip(bool_types, bool_values): + self.assertEqual(t(v).value, truth(v)) + + def test_typeerror(self): + # Only numbers are allowed in the constructor, + # otherwise TypeError is raised + for t in signed_types + unsigned_types + float_types: + self.assertRaises(TypeError, t, "") + self.assertRaises(TypeError, t, None) + + def test_from_param(self): + # the from_param class method attribute always + # returns PyCArgObject instances + for t in signed_types + unsigned_types + float_types: + self.assertEqual(ArgType, type(t.from_param(0))) + + def test_byref(self): + # calling byref returns also a PyCArgObject instance + for t in signed_types + unsigned_types + float_types + bool_types: + parm = byref(t()) + self.assertEqual(ArgType, type(parm)) + + + def test_floats(self): + # c_float and c_double can be created from + # Python int and float + class FloatLike: + def __float__(self): + return 2.0 + f = FloatLike() + for t in float_types: + self.assertEqual(t(2.0).value, 2.0) + self.assertEqual(t(2).value, 2.0) + self.assertEqual(t(2).value, 2.0) + self.assertEqual(t(f).value, 2.0) + + def test_integers(self): + class FloatLike: + def __float__(self): + return 2.0 + f = FloatLike() + class IntLike: + def __int__(self): + return 2 + d = IntLike() + class IndexLike: + def __index__(self): + return 2 + i = IndexLike() + # integers cannot be constructed from floats, + # but from integer-like objects + for t in signed_types + unsigned_types: + self.assertRaises(TypeError, t, 3.14) + self.assertRaises(TypeError, t, f) + self.assertRaises(TypeError, t, d) + self.assertEqual(t(i).value, 2) + + def test_sizes(self): + for t in signed_types + unsigned_types + float_types + bool_types: + try: + size = struct.calcsize(t._type_) + except struct.error: + continue + # sizeof of the type... + self.assertEqual(sizeof(t), size) + # and sizeof of an instance + self.assertEqual(sizeof(t()), size) + + def test_alignments(self): + for t in signed_types + unsigned_types + float_types: + code = t._type_ # the typecode + align = struct.calcsize("c%c" % code) - struct.calcsize(code) + + # alignment of the type... + self.assertEqual((code, alignment(t)), + (code, align)) + # and alignment of an instance + self.assertEqual((code, alignment(t())), + (code, align)) + + def test_int_from_address(self): + for t in signed_types + unsigned_types: + # the array module doesn't support all format codes + # (no 'q' or 'Q') + try: + array.array(t._type_) + except ValueError: + continue + a = array.array(t._type_, [100]) + + # v now is an integer at an 'external' memory location + v = t.from_address(a.buffer_info()[0]) + self.assertEqual(v.value, a[0]) + self.assertEqual(type(v), t) + + # changing the value at the memory location changes v's value also + a[0] = 42 + self.assertEqual(v.value, a[0]) + + + def test_float_from_address(self): + for t in float_types: + a = array.array(t._type_, [3.14]) + v = t.from_address(a.buffer_info()[0]) + self.assertEqual(v.value, a[0]) + self.assertIs(type(v), t) + a[0] = 2.3456e17 + self.assertEqual(v.value, a[0]) + self.assertIs(type(v), t) + + def test_char_from_address(self): + a = array.array('b', [0]) + a[0] = ord('x') + v = c_char.from_address(a.buffer_info()[0]) + self.assertEqual(v.value, b'x') + self.assertIs(type(v), c_char) + + a[0] = ord('?') + self.assertEqual(v.value, b'?') + + def test_init(self): + # c_int() can be initialized from Python's int, and c_int. + # Not from c_long or so, which seems strange, abc should + # probably be changed: + self.assertRaises(TypeError, c_int, c_long(42)) + + def test_float_overflow(self): + big_int = int(sys.float_info.max) * 2 + for t in float_types + [c_longdouble]: + self.assertRaises(OverflowError, t, big_int) + if (hasattr(t, "__ctype_be__")): + self.assertRaises(OverflowError, t.__ctype_be__, big_int) + if (hasattr(t, "__ctype_le__")): + self.assertRaises(OverflowError, t.__ctype_le__, big_int) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_objects.py b/Lib/test/test_ctypes/test_objects.py new file mode 100644 index 0000000000..fb01421b95 --- /dev/null +++ b/Lib/test/test_ctypes/test_objects.py @@ -0,0 +1,66 @@ +r''' +This tests the '_objects' attribute of ctypes instances. '_objects' +holds references to objects that must be kept alive as long as the +ctypes instance, to make sure that the memory buffer is valid. + +WARNING: The '_objects' attribute is exposed ONLY for debugging ctypes itself, +it MUST NEVER BE MODIFIED! + +'_objects' is initialized to a dictionary on first use, before that it +is None. + +Here is an array of string pointers: + +>>> from ctypes import Structure, c_int, c_char_p +>>> array = (c_char_p * 5)() +>>> print(array._objects) +None +>>> + +The memory block stores pointers to strings, and the strings itself +assigned from Python must be kept. + +>>> array[4] = b'foo bar' +>>> array._objects +{'4': b'foo bar'} +>>> array[4] +b'foo bar' +>>> + +It gets more complicated when the ctypes instance itself is contained +in a 'base' object. + +>>> class X(Structure): +... _fields_ = [("x", c_int), ("y", c_int), ("array", c_char_p * 5)] +... +>>> x = X() +>>> print(x._objects) +None +>>> + +The'array' attribute of the 'x' object shares part of the memory buffer +of 'x' ('_b_base_' is either None, or the root object owning the memory block): + +>>> print(x.array._b_base_) # doctest: +ELLIPSIS + +>>> + +>>> x.array[0] = b'spam spam spam' +>>> x._objects +{'0:2': b'spam spam spam'} +>>> x.array._b_base_._objects +{'0:2': b'spam spam spam'} +>>> +''' + +import doctest +import unittest + + +def load_tests(loader, tests, pattern): + tests.addTest(doctest.DocTestSuite()) + return tests + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_parameters.py b/Lib/test/test_ctypes/test_parameters.py new file mode 100644 index 0000000000..1a6ddb91a7 --- /dev/null +++ b/Lib/test/test_ctypes/test_parameters.py @@ -0,0 +1,288 @@ +import sys +import unittest +import test.support +from ctypes import (CDLL, PyDLL, ArgumentError, + Structure, Array, Union, + _Pointer, _SimpleCData, _CFuncPtr, + POINTER, pointer, byref, + c_void_p, c_char_p, c_wchar_p, py_object, + c_bool, + c_char, c_wchar, + c_byte, c_ubyte, + c_short, c_ushort, + c_int, c_uint, + c_long, c_ulong, + c_longlong, c_ulonglong, + c_float, c_double, c_longdouble) +from test.support import import_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +class SimpleTypesTestCase(unittest.TestCase): + def setUp(self): + try: + from _ctypes import set_conversion_mode + except ImportError: + pass + else: + self.prev_conv_mode = set_conversion_mode("ascii", "strict") + + def tearDown(self): + try: + from _ctypes import set_conversion_mode + except ImportError: + pass + else: + set_conversion_mode(*self.prev_conv_mode) + + def test_subclasses(self): + # ctypes 0.9.5 and before did overwrite from_param in SimpleType_new + class CVOIDP(c_void_p): + def from_param(cls, value): + return value * 2 + from_param = classmethod(from_param) + + class CCHARP(c_char_p): + def from_param(cls, value): + return value * 4 + from_param = classmethod(from_param) + + self.assertEqual(CVOIDP.from_param("abc"), "abcabc") + self.assertEqual(CCHARP.from_param("abc"), "abcabcabcabc") + + def test_subclasses_c_wchar_p(self): + class CWCHARP(c_wchar_p): + def from_param(cls, value): + return value * 3 + from_param = classmethod(from_param) + + self.assertEqual(CWCHARP.from_param("abc"), "abcabcabc") + + # XXX Replace by c_char_p tests + def test_cstrings(self): + # c_char_p.from_param on a Python String packs the string + # into a cparam object + s = b"123" + self.assertIs(c_char_p.from_param(s)._obj, s) + + # new in 0.9.1: convert (encode) unicode to ascii + self.assertEqual(c_char_p.from_param(b"123")._obj, b"123") + self.assertRaises(TypeError, c_char_p.from_param, "123\377") + self.assertRaises(TypeError, c_char_p.from_param, 42) + + # calling c_char_p.from_param with a c_char_p instance + # returns the argument itself: + a = c_char_p(b"123") + self.assertIs(c_char_p.from_param(a), a) + + def test_cw_strings(self): + c_wchar_p.from_param("123") + + self.assertRaises(TypeError, c_wchar_p.from_param, 42) + self.assertRaises(TypeError, c_wchar_p.from_param, b"123\377") + + pa = c_wchar_p.from_param(c_wchar_p("123")) + self.assertEqual(type(pa), c_wchar_p) + + def test_c_char(self): + with self.assertRaises(TypeError) as cm: + c_char.from_param(b"abc") + self.assertEqual(str(cm.exception), + "one character bytes, bytearray or integer expected") + + def test_c_wchar(self): + with self.assertRaises(TypeError) as cm: + c_wchar.from_param("abc") + self.assertEqual(str(cm.exception), + "one character unicode string expected") + + + with self.assertRaises(TypeError) as cm: + c_wchar.from_param(123) + self.assertEqual(str(cm.exception), + "unicode string expected instead of int instance") + + def test_int_pointers(self): + LPINT = POINTER(c_int) + + x = LPINT.from_param(pointer(c_int(42))) + self.assertEqual(x.contents.value, 42) + self.assertEqual(LPINT(c_int(42)).contents.value, 42) + + self.assertEqual(LPINT.from_param(None), None) + + if c_int != c_long: + self.assertRaises(TypeError, LPINT.from_param, pointer(c_long(42))) + self.assertRaises(TypeError, LPINT.from_param, pointer(c_uint(42))) + self.assertRaises(TypeError, LPINT.from_param, pointer(c_short(42))) + + def test_byref_pointer(self): + # The from_param class method of POINTER(typ) classes accepts what is + # returned by byref(obj), it type(obj) == typ + LPINT = POINTER(c_int) + + LPINT.from_param(byref(c_int(42))) + + self.assertRaises(TypeError, LPINT.from_param, byref(c_short(22))) + if c_int != c_long: + self.assertRaises(TypeError, LPINT.from_param, byref(c_long(22))) + self.assertRaises(TypeError, LPINT.from_param, byref(c_uint(22))) + + def test_byref_pointerpointer(self): + # See above + + LPLPINT = POINTER(POINTER(c_int)) + LPLPINT.from_param(byref(pointer(c_int(42)))) + + self.assertRaises(TypeError, LPLPINT.from_param, byref(pointer(c_short(22)))) + if c_int != c_long: + self.assertRaises(TypeError, LPLPINT.from_param, byref(pointer(c_long(22)))) + self.assertRaises(TypeError, LPLPINT.from_param, byref(pointer(c_uint(22)))) + + def test_array_pointers(self): + INTARRAY = c_int * 3 + ia = INTARRAY() + self.assertEqual(len(ia), 3) + self.assertEqual([ia[i] for i in range(3)], [0, 0, 0]) + + # Pointers are only compatible with arrays containing items of + # the same type! + LPINT = POINTER(c_int) + LPINT.from_param((c_int*3)()) + self.assertRaises(TypeError, LPINT.from_param, c_short*3) + self.assertRaises(TypeError, LPINT.from_param, c_long*3) + self.assertRaises(TypeError, LPINT.from_param, c_uint*3) + + def test_noctypes_argtype(self): + func = CDLL(_ctypes_test.__file__)._testfunc_p_p + func.restype = c_void_p + # TypeError: has no from_param method + self.assertRaises(TypeError, setattr, func, "argtypes", (object,)) + + class Adapter: + def from_param(cls, obj): + return None + + func.argtypes = (Adapter(),) + self.assertEqual(func(None), None) + self.assertEqual(func(object()), None) + + class Adapter: + def from_param(cls, obj): + return obj + + func.argtypes = (Adapter(),) + # don't know how to convert parameter 1 + self.assertRaises(ArgumentError, func, object()) + self.assertEqual(func(c_void_p(42)), 42) + + class Adapter: + def from_param(cls, obj): + raise ValueError(obj) + + func.argtypes = (Adapter(),) + # ArgumentError: argument 1: ValueError: 99 + self.assertRaises(ArgumentError, func, 99) + + def test_abstract(self): + self.assertRaises(TypeError, Array.from_param, 42) + self.assertRaises(TypeError, Structure.from_param, 42) + self.assertRaises(TypeError, Union.from_param, 42) + self.assertRaises(TypeError, _CFuncPtr.from_param, 42) + self.assertRaises(TypeError, _Pointer.from_param, 42) + self.assertRaises(TypeError, _SimpleCData.from_param, 42) + + @test.support.cpython_only + def test_issue31311(self): + # __setstate__ should neither raise a SystemError nor crash in case + # of a bad __dict__. + + class BadStruct(Structure): + @property + def __dict__(self): + pass + with self.assertRaises(TypeError): + BadStruct().__setstate__({}, b'foo') + + class WorseStruct(Structure): + @property + def __dict__(self): + 1/0 + with self.assertRaises(ZeroDivisionError): + WorseStruct().__setstate__({}, b'foo') + + def test_parameter_repr(self): + self.assertRegex(repr(c_bool.from_param(True)), r"^$") + self.assertEqual(repr(c_char.from_param(97)), "") + self.assertRegex(repr(c_wchar.from_param('a')), r"^$") + self.assertEqual(repr(c_byte.from_param(98)), "") + self.assertEqual(repr(c_ubyte.from_param(98)), "") + self.assertEqual(repr(c_short.from_param(511)), "") + self.assertEqual(repr(c_ushort.from_param(511)), "") + self.assertRegex(repr(c_int.from_param(20000)), r"^$") + self.assertRegex(repr(c_uint.from_param(20000)), r"^$") + self.assertRegex(repr(c_long.from_param(20000)), r"^$") + self.assertRegex(repr(c_ulong.from_param(20000)), r"^$") + self.assertRegex(repr(c_longlong.from_param(20000)), r"^$") + self.assertRegex(repr(c_ulonglong.from_param(20000)), r"^$") + self.assertEqual(repr(c_float.from_param(1.5)), "") + self.assertEqual(repr(c_double.from_param(1.5)), "") + if sys.float_repr_style == 'short': + self.assertEqual(repr(c_double.from_param(1e300)), "") + self.assertRegex(repr(c_longdouble.from_param(1.5)), r"^$") + self.assertRegex(repr(c_char_p.from_param(b'hihi')), r"^$") + self.assertRegex(repr(c_wchar_p.from_param('hihi')), r"^$") + self.assertRegex(repr(c_void_p.from_param(0x12)), r"^$") + + @test.support.cpython_only + def test_from_param_result_refcount(self): + # Issue #99952 + class X(Structure): + """This struct size is <= sizeof(void*).""" + _fields_ = [("a", c_void_p)] + + def __del__(self): + trace.append(4) + + @classmethod + def from_param(cls, value): + trace.append(2) + return cls() + + PyList_Append = PyDLL(_ctypes_test.__file__)._testfunc_pylist_append + PyList_Append.restype = c_int + PyList_Append.argtypes = [py_object, py_object, X] + + trace = [] + trace.append(1) + PyList_Append(trace, 3, "dummy") + trace.append(5) + + self.assertEqual(trace, [1, 2, 3, 4, 5]) + + class Y(Structure): + """This struct size is > sizeof(void*).""" + _fields_ = [("a", c_void_p), ("b", c_void_p)] + + def __del__(self): + trace.append(4) + + @classmethod + def from_param(cls, value): + trace.append(2) + return cls() + + PyList_Append = PyDLL(_ctypes_test.__file__)._testfunc_pylist_append + PyList_Append.restype = c_int + PyList_Append.argtypes = [py_object, py_object, Y] + + trace = [] + trace.append(1) + PyList_Append(trace, 3, "dummy") + trace.append(5) + + self.assertEqual(trace, [1, 2, 3, 4, 5]) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_pep3118.py b/Lib/test/test_ctypes/test_pep3118.py new file mode 100644 index 0000000000..06b2ccecad --- /dev/null +++ b/Lib/test/test_ctypes/test_pep3118.py @@ -0,0 +1,250 @@ +import re +import sys +import unittest +from ctypes import (CFUNCTYPE, POINTER, sizeof, Union, + Structure, LittleEndianStructure, BigEndianStructure, + c_char, c_byte, c_ubyte, + c_short, c_ushort, c_int, c_uint, + c_long, c_ulong, c_longlong, c_ulonglong, c_uint64, + c_bool, c_float, c_double, c_longdouble, py_object) + + +if sys.byteorder == "little": + THIS_ENDIAN = "<" + OTHER_ENDIAN = ">" +else: + THIS_ENDIAN = ">" + OTHER_ENDIAN = "<" + + +def normalize(format): + # Remove current endian specifier and white space from a format + # string + if format is None: + return "" + format = format.replace(OTHER_ENDIAN, THIS_ENDIAN) + return re.sub(r"\s", "", format) + + +class Test(unittest.TestCase): + def test_native_types(self): + for tp, fmt, shape, itemtp in native_types: + ob = tp() + v = memoryview(ob) + self.assertEqual(normalize(v.format), normalize(fmt)) + if shape: + self.assertEqual(len(v), shape[0]) + else: + self.assertRaises(TypeError, len, v) + self.assertEqual(v.itemsize, sizeof(itemtp)) + self.assertEqual(v.shape, shape) + # XXX Issue #12851: PyCData_NewGetBuffer() must provide strides + # if requested. memoryview currently reconstructs missing + # stride information, so this assert will fail. + # self.assertEqual(v.strides, ()) + + # they are always read/write + self.assertFalse(v.readonly) + + n = 1 + for dim in v.shape: + n = n * dim + self.assertEqual(n * v.itemsize, len(v.tobytes())) + + def test_endian_types(self): + for tp, fmt, shape, itemtp in endian_types: + ob = tp() + v = memoryview(ob) + self.assertEqual(v.format, fmt) + if shape: + self.assertEqual(len(v), shape[0]) + else: + self.assertRaises(TypeError, len, v) + self.assertEqual(v.itemsize, sizeof(itemtp)) + self.assertEqual(v.shape, shape) + # XXX Issue #12851 + # self.assertEqual(v.strides, ()) + + # they are always read/write + self.assertFalse(v.readonly) + + n = 1 + for dim in v.shape: + n = n * dim + self.assertEqual(n * v.itemsize, len(v.tobytes())) + + +# define some structure classes + +class Point(Structure): + _fields_ = [("x", c_long), ("y", c_long)] + +class PackedPoint(Structure): + _pack_ = 2 + _fields_ = [("x", c_long), ("y", c_long)] + +class PointMidPad(Structure): + _fields_ = [("x", c_byte), ("y", c_uint)] + +class PackedPointMidPad(Structure): + _pack_ = 2 + _fields_ = [("x", c_byte), ("y", c_uint64)] + +class PointEndPad(Structure): + _fields_ = [("x", c_uint), ("y", c_byte)] + +class PackedPointEndPad(Structure): + _pack_ = 2 + _fields_ = [("x", c_uint64), ("y", c_byte)] + +class Point2(Structure): + pass +Point2._fields_ = [("x", c_long), ("y", c_long)] + +class EmptyStruct(Structure): + _fields_ = [] + +class aUnion(Union): + _fields_ = [("a", c_int)] + +class StructWithArrays(Structure): + _fields_ = [("x", c_long * 3 * 2), ("y", Point * 4)] + +class Incomplete(Structure): + pass + +class Complete(Structure): + pass +PComplete = POINTER(Complete) +Complete._fields_ = [("a", c_long)] + + +################################################################ +# +# This table contains format strings as they look on little endian +# machines. The test replaces '<' with '>' on big endian machines. +# + +# Platform-specific type codes +s_bool = {1: '?', 2: 'H', 4: 'L', 8: 'Q'}[sizeof(c_bool)] +s_short = {2: 'h', 4: 'l', 8: 'q'}[sizeof(c_short)] +s_ushort = {2: 'H', 4: 'L', 8: 'Q'}[sizeof(c_ushort)] +s_int = {2: 'h', 4: 'i', 8: 'q'}[sizeof(c_int)] +s_uint = {2: 'H', 4: 'I', 8: 'Q'}[sizeof(c_uint)] +s_long = {4: 'l', 8: 'q'}[sizeof(c_long)] +s_ulong = {4: 'L', 8: 'Q'}[sizeof(c_ulong)] +s_longlong = "q" +s_ulonglong = "Q" +s_float = "f" +s_double = "d" +s_longdouble = "g" + +# Alias definitions in ctypes/__init__.py +if c_int is c_long: + s_int = s_long +if c_uint is c_ulong: + s_uint = s_ulong +if c_longlong is c_long: + s_longlong = s_long +if c_ulonglong is c_ulong: + s_ulonglong = s_ulong +if c_longdouble is c_double: + s_longdouble = s_double + + +native_types = [ + # type format shape calc itemsize + + ## simple types + + (c_char, "l:x:>l:y:}".replace('l', s_long), (), BEPoint), + (LEPoint * 1, "T{l:x:>l:y:}".replace('l', s_long), (), POINTER(BEPoint)), + (POINTER(LEPoint), "&T{= 0: + return a + # View the bits in `a` as unsigned instead. + import struct + num_bits = struct.calcsize("P") * 8 # num bits in native machine address + a += 1 << num_bits + assert a >= 0 + return a + + +def c_wbuffer(init): + n = len(init) + 1 + return (c_wchar * n)(*init) + + +class CharPointersTestCase(unittest.TestCase): + def setUp(self): + func = testdll._testfunc_p_p + func.restype = c_long + func.argtypes = None + + def test_paramflags(self): + # function returns c_void_p result, + # and has a required parameter named 'input' + prototype = CFUNCTYPE(c_void_p, c_void_p) + func = prototype(("_testfunc_p_p", testdll), + ((1, "input"),)) + + try: + func() + except TypeError as details: + self.assertEqual(str(details), "required argument 'input' missing") + else: + self.fail("TypeError not raised") + + self.assertEqual(func(None), None) + self.assertEqual(func(input=None), None) + + def test_invalid_paramflags(self): + proto = CFUNCTYPE(c_int, c_char_p) + with self.assertRaises(ValueError): + func = proto(("myprintf", testdll), ((1, "fmt"), (1, "arg1"))) + + def test_invalid_setattr_argtypes(self): + proto = CFUNCTYPE(c_int, c_char_p) + func = proto(("myprintf", testdll), ((1, "fmt"),)) + + with self.assertRaisesRegex(TypeError, "_argtypes_ must be a sequence of types"): + func.argtypes = 123 + self.assertEqual(func.argtypes, (c_char_p,)) + + with self.assertRaisesRegex(ValueError, "paramflags must have the same length as argtypes"): + func.argtypes = (c_char_p, c_int) + self.assertEqual(func.argtypes, (c_char_p,)) + + def test_paramflags_outarg(self): + proto = CFUNCTYPE(c_int, c_char_p, c_int) + with self.assertRaisesRegex(TypeError, "must be a pointer type"): + func = proto(("myprintf", testdll), ((1, "fmt"), (2, "out"))) + + proto = CFUNCTYPE(c_int, c_char_p, c_void_p) + func = proto(("myprintf", testdll), ((1, "fmt"), (2, "out"))) + with self.assertRaisesRegex(TypeError, "must be a pointer type"): + func.argtypes = (c_char_p, c_int) + + def test_int_pointer_arg(self): + func = testdll._testfunc_p_p + if sizeof(c_longlong) == sizeof(c_void_p): + func.restype = c_longlong + else: + func.restype = c_long + self.assertEqual(0, func(0)) + + ci = c_int(0) + + func.argtypes = POINTER(c_int), + self.assertEqual(positive_address(addressof(ci)), + positive_address(func(byref(ci)))) + + func.argtypes = c_char_p, + self.assertRaises(ArgumentError, func, byref(ci)) + + func.argtypes = POINTER(c_short), + self.assertRaises(ArgumentError, func, byref(ci)) + + func.argtypes = POINTER(c_double), + self.assertRaises(ArgumentError, func, byref(ci)) + + def test_POINTER_c_char_arg(self): + func = testdll._testfunc_p_p + func.restype = c_char_p + func.argtypes = POINTER(c_char), + + self.assertEqual(None, func(None)) + self.assertEqual(b"123", func(b"123")) + self.assertEqual(None, func(c_char_p(None))) + self.assertEqual(b"123", func(c_char_p(b"123"))) + + self.assertEqual(b"123", func(create_string_buffer(b"123"))) + ca = c_char(b"a") + self.assertEqual(ord(b"a"), func(pointer(ca))[0]) + self.assertEqual(ord(b"a"), func(byref(ca))[0]) + + def test_c_char_p_arg(self): + func = testdll._testfunc_p_p + func.restype = c_char_p + func.argtypes = c_char_p, + + self.assertEqual(None, func(None)) + self.assertEqual(b"123", func(b"123")) + self.assertEqual(None, func(c_char_p(None))) + self.assertEqual(b"123", func(c_char_p(b"123"))) + + self.assertEqual(b"123", func(create_string_buffer(b"123"))) + ca = c_char(b"a") + self.assertEqual(ord(b"a"), func(pointer(ca))[0]) + self.assertEqual(ord(b"a"), func(byref(ca))[0]) + + def test_c_void_p_arg(self): + func = testdll._testfunc_p_p + func.restype = c_char_p + func.argtypes = c_void_p, + + self.assertEqual(None, func(None)) + self.assertEqual(b"123", func(b"123")) + self.assertEqual(b"123", func(c_char_p(b"123"))) + self.assertEqual(None, func(c_char_p(None))) + + self.assertEqual(b"123", func(create_string_buffer(b"123"))) + ca = c_char(b"a") + self.assertEqual(ord(b"a"), func(pointer(ca))[0]) + self.assertEqual(ord(b"a"), func(byref(ca))[0]) + + func(byref(c_int())) + func(pointer(c_int())) + func((c_int * 3)()) + + def test_c_void_p_arg_with_c_wchar_p(self): + func = testdll._testfunc_p_p + func.restype = c_wchar_p + func.argtypes = c_void_p, + + self.assertEqual(None, func(c_wchar_p(None))) + self.assertEqual("123", func(c_wchar_p("123"))) + + def test_instance(self): + func = testdll._testfunc_p_p + func.restype = c_void_p + + class X: + _as_parameter_ = None + + func.argtypes = c_void_p, + self.assertEqual(None, func(X())) + + func.argtypes = None + self.assertEqual(None, func(X())) + + +class WCharPointersTestCase(unittest.TestCase): + def setUp(self): + func = testdll._testfunc_p_p + func.restype = c_int + func.argtypes = None + + + def test_POINTER_c_wchar_arg(self): + func = testdll._testfunc_p_p + func.restype = c_wchar_p + func.argtypes = POINTER(c_wchar), + + self.assertEqual(None, func(None)) + self.assertEqual("123", func("123")) + self.assertEqual(None, func(c_wchar_p(None))) + self.assertEqual("123", func(c_wchar_p("123"))) + + self.assertEqual("123", func(c_wbuffer("123"))) + ca = c_wchar("a") + self.assertEqual("a", func(pointer(ca))[0]) + self.assertEqual("a", func(byref(ca))[0]) + + def test_c_wchar_p_arg(self): + func = testdll._testfunc_p_p + func.restype = c_wchar_p + func.argtypes = c_wchar_p, + + c_wchar_p.from_param("123") + + self.assertEqual(None, func(None)) + self.assertEqual("123", func("123")) + self.assertEqual(None, func(c_wchar_p(None))) + self.assertEqual("123", func(c_wchar_p("123"))) + + # XXX Currently, these raise TypeErrors, although they shouldn't: + self.assertEqual("123", func(c_wbuffer("123"))) + ca = c_wchar("a") + self.assertEqual("a", func(pointer(ca))[0]) + self.assertEqual("a", func(byref(ca))[0]) + + +class ArrayTest(unittest.TestCase): + def test(self): + func = testdll._testfunc_ai8 + func.restype = POINTER(c_int) + func.argtypes = c_int * 8, + + func((c_int * 8)(1, 2, 3, 4, 5, 6, 7, 8)) + + # This did crash before: + + def func(): pass + CFUNCTYPE(None, c_int * 3)(func) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_python_api.py b/Lib/test/test_ctypes/test_python_api.py new file mode 100644 index 0000000000..1072a10983 --- /dev/null +++ b/Lib/test/test_ctypes/test_python_api.py @@ -0,0 +1,81 @@ +import _ctypes +import sys +import unittest +from test import support +from ctypes import (pythonapi, POINTER, create_string_buffer, sizeof, + py_object, c_char_p, c_char, c_long, c_size_t) + + +class PythonAPITestCase(unittest.TestCase): + def test_PyBytes_FromStringAndSize(self): + PyBytes_FromStringAndSize = pythonapi.PyBytes_FromStringAndSize + + PyBytes_FromStringAndSize.restype = py_object + PyBytes_FromStringAndSize.argtypes = c_char_p, c_size_t + + self.assertEqual(PyBytes_FromStringAndSize(b"abcdefghi", 3), b"abc") + + @support.refcount_test + def test_PyString_FromString(self): + pythonapi.PyBytes_FromString.restype = py_object + pythonapi.PyBytes_FromString.argtypes = (c_char_p,) + + s = b"abc" + refcnt = sys.getrefcount(s) + pyob = pythonapi.PyBytes_FromString(s) + self.assertEqual(sys.getrefcount(s), refcnt) + self.assertEqual(s, pyob) + del pyob + self.assertEqual(sys.getrefcount(s), refcnt) + + @support.refcount_test + def test_PyLong_Long(self): + ref42 = sys.getrefcount(42) + pythonapi.PyLong_FromLong.restype = py_object + self.assertEqual(pythonapi.PyLong_FromLong(42), 42) + + self.assertEqual(sys.getrefcount(42), ref42) + + pythonapi.PyLong_AsLong.argtypes = (py_object,) + pythonapi.PyLong_AsLong.restype = c_long + + res = pythonapi.PyLong_AsLong(42) + # Small int refcnts don't change + self.assertEqual(sys.getrefcount(res), ref42) + del res + self.assertEqual(sys.getrefcount(42), ref42) + + @support.refcount_test + def test_PyObj_FromPtr(self): + s = object() + ref = sys.getrefcount(s) + # id(python-object) is the address + pyobj = _ctypes.PyObj_FromPtr(id(s)) + self.assertIs(s, pyobj) + + self.assertEqual(sys.getrefcount(s), ref + 1) + del pyobj + self.assertEqual(sys.getrefcount(s), ref) + + def test_PyOS_snprintf(self): + PyOS_snprintf = pythonapi.PyOS_snprintf + PyOS_snprintf.argtypes = POINTER(c_char), c_size_t, c_char_p + + buf = create_string_buffer(256) + PyOS_snprintf(buf, sizeof(buf), b"Hello from %s", b"ctypes") + self.assertEqual(buf.value, b"Hello from ctypes") + + PyOS_snprintf(buf, sizeof(buf), b"Hello from %s (%d, %d, %d)", b"ctypes", 1, 2, 3) + self.assertEqual(buf.value, b"Hello from ctypes (1, 2, 3)") + + # not enough arguments + self.assertRaises(TypeError, PyOS_snprintf, buf) + + def test_pyobject_repr(self): + self.assertEqual(repr(py_object()), "py_object()") + self.assertEqual(repr(py_object(42)), "py_object(42)") + self.assertEqual(repr(py_object(object)), "py_object(%r)" % object) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_ctypes/test_random_things.py b/Lib/test/test_ctypes/test_random_things.py new file mode 100644 index 0000000000..630f6ed948 --- /dev/null +++ b/Lib/test/test_ctypes/test_random_things.py @@ -0,0 +1,81 @@ +import _ctypes +import contextlib +import ctypes +import sys +import unittest +from test import support +from ctypes import CFUNCTYPE, c_void_p, c_char_p, c_int, c_double + + +def callback_func(arg): + 42 / arg + raise ValueError(arg) + + +@unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') +class call_function_TestCase(unittest.TestCase): + # _ctypes.call_function is deprecated and private, but used by + # Gary Bishp's readline module. If we have it, we must test it as well. + + def test(self): + kernel32 = ctypes.windll.kernel32 + kernel32.LoadLibraryA.restype = c_void_p + kernel32.GetProcAddress.argtypes = c_void_p, c_char_p + kernel32.GetProcAddress.restype = c_void_p + + hdll = kernel32.LoadLibraryA(b"kernel32") + funcaddr = kernel32.GetProcAddress(hdll, b"GetModuleHandleA") + + self.assertEqual(_ctypes.call_function(funcaddr, (None,)), + kernel32.GetModuleHandleA(None)) + + +class CallbackTracbackTestCase(unittest.TestCase): + # When an exception is raised in a ctypes callback function, the C + # code prints a traceback. + # + # This test makes sure the exception types *and* the exception + # value is printed correctly. + # + # Changed in 0.9.3: No longer is '(in callback)' prepended to the + # error message - instead an additional frame for the C code is + # created, then a full traceback printed. When SystemExit is + # raised in a callback function, the interpreter exits. + + @contextlib.contextmanager + def expect_unraisable(self, exc_type, exc_msg=None): + with support.catch_unraisable_exception() as cm: + yield + + self.assertIsInstance(cm.unraisable.exc_value, exc_type) + if exc_msg is not None: + self.assertEqual(str(cm.unraisable.exc_value), exc_msg) + self.assertEqual(cm.unraisable.err_msg, + f"Exception ignored on calling ctypes " + f"callback function {callback_func!r}") + self.assertIsNone(cm.unraisable.object) + + def test_ValueError(self): + cb = CFUNCTYPE(c_int, c_int)(callback_func) + with self.expect_unraisable(ValueError, '42'): + cb(42) + + def test_IntegerDivisionError(self): + cb = CFUNCTYPE(c_int, c_int)(callback_func) + with self.expect_unraisable(ZeroDivisionError): + cb(0) + + def test_FloatDivisionError(self): + cb = CFUNCTYPE(c_int, c_double)(callback_func) + with self.expect_unraisable(ZeroDivisionError): + cb(0.0) + + def test_TypeErrorDivisionError(self): + cb = CFUNCTYPE(c_int, c_char_p)(callback_func) + err_msg = "unsupported operand type(s) for /: 'int' and 'bytes'" + with self.expect_unraisable(TypeError, err_msg): + cb(b"spam") + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_refcounts.py b/Lib/test/test_ctypes/test_refcounts.py new file mode 100644 index 0000000000..9e87cfc661 --- /dev/null +++ b/Lib/test/test_ctypes/test_refcounts.py @@ -0,0 +1,143 @@ +import ctypes +import gc +import sys +import unittest +from test import support +from test.support import import_helper +from test.support import script_helper +_ctypes_test = import_helper.import_module("_ctypes_test") + + +MyCallback = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int) +OtherCallback = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_ulonglong) + +dll = ctypes.CDLL(_ctypes_test.__file__) + + +class RefcountTestCase(unittest.TestCase): + @support.refcount_test + def test_1(self): + f = dll._testfunc_callback_i_if + f.restype = ctypes.c_int + f.argtypes = [ctypes.c_int, MyCallback] + + def callback(value): + return value + + self.assertEqual(sys.getrefcount(callback), 2) + cb = MyCallback(callback) + + self.assertGreater(sys.getrefcount(callback), 2) + result = f(-10, cb) + self.assertEqual(result, -18) + cb = None + + gc.collect() + + self.assertEqual(sys.getrefcount(callback), 2) + + @support.refcount_test + def test_refcount(self): + def func(*args): + pass + # this is the standard refcount for func + self.assertEqual(sys.getrefcount(func), 2) + + # the CFuncPtr instance holds at least one refcount on func: + f = OtherCallback(func) + self.assertGreater(sys.getrefcount(func), 2) + + # and may release it again + del f + self.assertGreaterEqual(sys.getrefcount(func), 2) + + # but now it must be gone + gc.collect() + self.assertEqual(sys.getrefcount(func), 2) + + class X(ctypes.Structure): + _fields_ = [("a", OtherCallback)] + x = X() + x.a = OtherCallback(func) + + # the CFuncPtr instance holds at least one refcount on func: + self.assertGreater(sys.getrefcount(func), 2) + + # and may release it again + del x + self.assertGreaterEqual(sys.getrefcount(func), 2) + + # and now it must be gone again + gc.collect() + self.assertEqual(sys.getrefcount(func), 2) + + f = OtherCallback(func) + + # the CFuncPtr instance holds at least one refcount on func: + self.assertGreater(sys.getrefcount(func), 2) + + # create a cycle + f.cycle = f + + del f + gc.collect() + self.assertEqual(sys.getrefcount(func), 2) + + +class AnotherLeak(unittest.TestCase): + def test_callback(self): + proto = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int) + def func(a, b): + return a * b * 2 + f = proto(func) + + a = sys.getrefcount(ctypes.c_int) + f(1, 2) + self.assertEqual(sys.getrefcount(ctypes.c_int), a) + + @support.refcount_test + def test_callback_py_object_none_return(self): + # bpo-36880: test that returning None from a py_object callback + # does not decrement the refcount of None. + + for FUNCTYPE in (ctypes.CFUNCTYPE, ctypes.PYFUNCTYPE): + with self.subTest(FUNCTYPE=FUNCTYPE): + @FUNCTYPE(ctypes.py_object) + def func(): + return None + + # Check that calling func does not affect None's refcount. + for _ in range(10000): + func() + + +class ModuleIsolationTest(unittest.TestCase): + def test_finalize(self): + # check if gc_decref() succeeds + script = ( + "import ctypes;" + "import sys;" + "del sys.modules['_ctypes'];" + "import _ctypes;" + "exit()" + ) + script_helper.assert_python_ok("-c", script) + + +class PyObjectRestypeTest(unittest.TestCase): + def test_restype_py_object_with_null_return(self): + # Test that a function which returns a NULL PyObject * + # without setting an exception does not crash. + PyErr_Occurred = ctypes.pythonapi.PyErr_Occurred + PyErr_Occurred.argtypes = [] + PyErr_Occurred.restype = ctypes.py_object + + # At this point, there's no exception set, so PyErr_Occurred + # returns NULL. Given the restype is py_object, the + # ctypes machinery will raise a custom error. + with self.assertRaisesRegex(ValueError, "PyObject is NULL"): + PyErr_Occurred() + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_ctypes/test_repr.py b/Lib/test/test_ctypes/test_repr.py new file mode 100644 index 0000000000..e7587984a9 --- /dev/null +++ b/Lib/test/test_ctypes/test_repr.py @@ -0,0 +1,34 @@ +import unittest +from ctypes import (c_byte, c_short, c_int, c_long, c_longlong, + c_ubyte, c_ushort, c_uint, c_ulong, c_ulonglong, + c_float, c_double, c_longdouble, c_bool, c_char) + + +subclasses = [] +for base in [c_byte, c_short, c_int, c_long, c_longlong, + c_ubyte, c_ushort, c_uint, c_ulong, c_ulonglong, + c_float, c_double, c_longdouble, c_bool]: + class X(base): + pass + subclasses.append(X) + + +class X(c_char): + pass + + +# This test checks if the __repr__ is correct for subclasses of simple types +class ReprTest(unittest.TestCase): + def test_numbers(self): + for typ in subclasses: + base = typ.__bases__[0] + self.assertTrue(repr(base(42)).startswith(base.__name__)) + self.assertEqual(" +# for reference. +# +# Tests also work on POSIX + +import unittest +from ctypes import POINTER, cast, c_int16 +from ctypes import wintypes + + +class WinTypesTest(unittest.TestCase): + def test_variant_bool(self): + # reads 16-bits from memory, anything non-zero is True + for true_value in (1, 32767, 32768, 65535, 65537): + true = POINTER(c_int16)(c_int16(true_value)) + value = cast(true, POINTER(wintypes.VARIANT_BOOL)) + self.assertEqual(repr(value.contents), 'VARIANT_BOOL(True)') + + vb = wintypes.VARIANT_BOOL() + self.assertIs(vb.value, False) + vb.value = True + self.assertIs(vb.value, True) + vb.value = true_value + self.assertIs(vb.value, True) + + for false_value in (0, 65536, 262144, 2**33): + false = POINTER(c_int16)(c_int16(false_value)) + value = cast(false, POINTER(wintypes.VARIANT_BOOL)) + self.assertEqual(repr(value.contents), 'VARIANT_BOOL(False)') + + # allow any bool conversion on assignment to value + for set_value in (65536, 262144, 2**33): + vb = wintypes.VARIANT_BOOL() + vb.value = set_value + self.assertIs(vb.value, True) + + vb = wintypes.VARIANT_BOOL() + vb.value = [2, 3] + self.assertIs(vb.value, True) + vb.value = [] + self.assertIs(vb.value, False) + + def assertIsSigned(self, ctype): + self.assertLess(ctype(-1).value, 0) + + def assertIsUnsigned(self, ctype): + self.assertGreater(ctype(-1).value, 0) + + def test_signedness(self): + for ctype in (wintypes.BYTE, wintypes.WORD, wintypes.DWORD, + wintypes.BOOLEAN, wintypes.UINT, wintypes.ULONG): + with self.subTest(ctype=ctype): + self.assertIsUnsigned(ctype) + + for ctype in (wintypes.BOOL, wintypes.INT, wintypes.LONG): + with self.subTest(ctype=ctype): + self.assertIsSigned(ctype) + + +if __name__ == "__main__": + unittest.main() From 4a352344b6abf257c4050786f2bfd8036da0f715 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 20 Dec 2025 17:46:05 +0900 Subject: [PATCH 10/10] mark failing test_ctypes --- Lib/test/test_ctypes/test_cast.py | 2 ++ Lib/test/test_ctypes/test_dlerror.py | 1 + Lib/test/test_ctypes/test_internals.py | 4 ++++ Lib/test/test_ctypes/test_keeprefs.py | 4 ++++ Lib/test/test_ctypes/test_objects.py | 3 ++- Lib/test/test_ctypes/test_python_api.py | 4 ++++ Lib/test/test_ctypes/test_random_things.py | 2 ++ Lib/test/test_ctypes/test_values.py | 4 ++++ Lib/test/test_ctypes/test_win32.py | 2 ++ Lib/test/test_ctypes/test_win32_com_foreign_func.py | 2 ++ Lib/test/test_exceptions.py | 1 + Lib/test/test_os.py | 3 --- 12 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_ctypes/test_cast.py b/Lib/test/test_ctypes/test_cast.py index 604f44f03d..db6bdc75ef 100644 --- a/Lib/test/test_ctypes/test_cast.py +++ b/Lib/test/test_ctypes/test_cast.py @@ -32,6 +32,8 @@ def test_address2pointer(self): ptr = cast(address, POINTER(c_int)) self.assertEqual([ptr[i] for i in range(3)], [42, 17, 2]) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_p2a_objects(self): array = (c_char_p * 5)() self.assertEqual(array._objects, None) diff --git a/Lib/test/test_ctypes/test_dlerror.py b/Lib/test/test_ctypes/test_dlerror.py index 1c1b2aab3d..cd87bad382 100644 --- a/Lib/test/test_ctypes/test_dlerror.py +++ b/Lib/test/test_ctypes/test_dlerror.py @@ -54,6 +54,7 @@ class TestNullDlsym(unittest.TestCase): this 'dlsym returned NULL -> throw Error' rule. """ + @unittest.expectedFailure # TODO: RUSTPYTHON def test_null_dlsym(self): import subprocess import tempfile diff --git a/Lib/test/test_ctypes/test_internals.py b/Lib/test/test_ctypes/test_internals.py index 778da6573d..292633aaa4 100644 --- a/Lib/test/test_ctypes/test_internals.py +++ b/Lib/test/test_ctypes/test_internals.py @@ -27,6 +27,8 @@ def test_ints(self): self.assertEqual(refcnt, sys.getrefcount(i)) self.assertEqual(ci._objects, None) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_c_char_p(self): s = "Hello, World".encode("ascii") refcnt = sys.getrefcount(s) @@ -62,6 +64,8 @@ class Y(Structure): x1.a, x2.b = 42, 93 self.assertEqual(y._objects, {"0": {}, "1": {}}) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_xxx(self): class X(Structure): _fields_ = [("a", c_char_p), ("b", c_char_p)] diff --git a/Lib/test/test_ctypes/test_keeprefs.py b/Lib/test/test_ctypes/test_keeprefs.py index 23b03b64b4..5aa5b86fa4 100644 --- a/Lib/test/test_ctypes/test_keeprefs.py +++ b/Lib/test/test_ctypes/test_keeprefs.py @@ -12,6 +12,8 @@ def test_cint(self): x = c_int(99) self.assertEqual(x._objects, None) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_ccharp(self): x = c_char_p() self.assertEqual(x._objects, None) @@ -33,6 +35,8 @@ class X(Structure): x.b = 99 self.assertEqual(x._objects, None) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_ccharp_struct(self): class X(Structure): _fields_ = [("a", c_char_p), diff --git a/Lib/test/test_ctypes/test_objects.py b/Lib/test/test_ctypes/test_objects.py index fb01421b95..8db1cd873f 100644 --- a/Lib/test/test_ctypes/test_objects.py +++ b/Lib/test/test_ctypes/test_objects.py @@ -58,7 +58,8 @@ def load_tests(loader, tests, pattern): - tests.addTest(doctest.DocTestSuite()) + # TODO: RUSTPYTHON - doctest disabled due to null terminator in _objects + # tests.addTest(doctest.DocTestSuite()) return tests diff --git a/Lib/test/test_ctypes/test_python_api.py b/Lib/test/test_ctypes/test_python_api.py index 1072a10983..2e68b35f8a 100644 --- a/Lib/test/test_ctypes/test_python_api.py +++ b/Lib/test/test_ctypes/test_python_api.py @@ -7,6 +7,8 @@ class PythonAPITestCase(unittest.TestCase): + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_PyBytes_FromStringAndSize(self): PyBytes_FromStringAndSize = pythonapi.PyBytes_FromStringAndSize @@ -57,6 +59,8 @@ def test_PyObj_FromPtr(self): del pyobj self.assertEqual(sys.getrefcount(s), ref) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_PyOS_snprintf(self): PyOS_snprintf = pythonapi.PyOS_snprintf PyOS_snprintf.argtypes = POINTER(c_char), c_size_t, c_char_p diff --git a/Lib/test/test_ctypes/test_random_things.py b/Lib/test/test_ctypes/test_random_things.py index 630f6ed948..3908eca092 100644 --- a/Lib/test/test_ctypes/test_random_things.py +++ b/Lib/test/test_ctypes/test_random_things.py @@ -70,6 +70,8 @@ def test_FloatDivisionError(self): with self.expect_unraisable(ZeroDivisionError): cb(0.0) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_TypeErrorDivisionError(self): cb = CFUNCTYPE(c_int, c_char_p)(callback_func) err_msg = "unsupported operand type(s) for /: 'int' and 'bytes'" diff --git a/Lib/test/test_ctypes/test_values.py b/Lib/test/test_ctypes/test_values.py index 1b757e020d..e0d200e210 100644 --- a/Lib/test/test_ctypes/test_values.py +++ b/Lib/test/test_ctypes/test_values.py @@ -39,6 +39,8 @@ def test_undefined(self): class PythonValuesTestCase(unittest.TestCase): """This test only works when python itself is a dll/shared library""" + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_optimizeflag(self): # This test accesses the Py_OptimizeFlag integer, which is # exported by the Python dll and should match the sys.flags value @@ -46,6 +48,8 @@ def test_optimizeflag(self): opt = c_int.in_dll(pythonapi, "Py_OptimizeFlag").value self.assertEqual(opt, sys.flags.optimize) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_frozentable(self): # Python exports a PyImport_FrozenModules symbol. This is a # pointer to an array of struct _frozen entries. The end of the diff --git a/Lib/test/test_ctypes/test_win32.py b/Lib/test/test_ctypes/test_win32.py index 3191911867..4de4f0379c 100644 --- a/Lib/test/test_ctypes/test_win32.py +++ b/Lib/test/test_ctypes/test_win32.py @@ -17,6 +17,8 @@ class FunctionCallTestCase(unittest.TestCase): @unittest.skipUnless('MSC' in sys.version, "SEH only supported by MSC") @unittest.skipIf(sys.executable.lower().endswith('_d.exe'), "SEH not enabled in debug builds") + # TODO: RUSTPYTHON - SEH not implemented + @unittest.skipIf("RustPython" in sys.version, "SEH not implemented in RustPython") def test_SEH(self): # Disable faulthandler to prevent logging the warning: # "Windows fatal exception: access violation" diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py index 8d217fc17e..b12e09333b 100644 --- a/Lib/test/test_ctypes/test_win32_com_foreign_func.py +++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py @@ -158,6 +158,8 @@ class IPersist(IUnknown): self.assertEqual(0, ppst.Release()) + # TODO: RUSTPYTHON - COM iid parameter handling not implemented + @unittest.expectedFailure def test_with_paramflags_and_iid(self): class IUnknown(c_void_p): QueryInterface = proto_query_interface(None, IID_IUnknown) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 61f4156dc6..3db9203602 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -430,6 +430,7 @@ def test_WindowsError(self): @unittest.skipUnless(sys.platform == 'win32', 'test specific to Windows') + @unittest.expectedFailure # TODO: RUSTPYTHON def test_windows_message(self): """Should fill in unknown error code in Windows error message""" ctypes = import_module('ctypes') diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 939315379f..bb558524c2 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -939,7 +939,6 @@ def get_file_system(self, path): return buf.value # return None if the filesystem is unknown - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (ModuleNotFoundError: No module named '_ctypes')") def test_large_time(self): # Many filesystems are limited to the year 2038. At least, the test # pass with NTFS filesystem. @@ -2712,12 +2711,10 @@ def _kill(self, sig): os.kill(proc.pid, sig) self.assertEqual(proc.wait(), sig) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (ModuleNotFoundError: No module named '_ctypes')") def test_kill_sigterm(self): # SIGTERM doesn't mean anything special, but make sure it works self._kill(signal.SIGTERM) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (ModuleNotFoundError: No module named '_ctypes')") def test_kill_int(self): # os.kill on Windows can take an int which gets set as the exit code self._kill(100)