diff --git a/src/conversions/chrono.rs b/src/conversions/chrono.rs index 5ea8904fca2..bd97d3409fc 100644 --- a/src/conversions/chrono.rs +++ b/src/conversions/chrono.rs @@ -593,7 +593,7 @@ fn py_time_to_naive_time(py_time: &Bound<'_, PyAny>) -> PyResult { #[cfg(test)] mod tests { use super::*; - use crate::{types::PyTuple, BoundObject}; + use crate::{test_utils::assert_warnings, types::PyTuple, BoundObject}; use std::{cmp::Ordering, panic}; #[test] @@ -875,7 +875,6 @@ mod tests { check_utc("regular", 2014, 5, 6, 7, 8, 9, 999_999, 999_999); - #[cfg(not(Py_GIL_DISABLED))] assert_warnings!( py, check_utc("leap second", 2014, 5, 6, 7, 8, 59, 1_999_999, 999_999), @@ -915,7 +914,6 @@ mod tests { check_fixed_offset("regular", 2014, 5, 6, 7, 8, 9, 999_999, 999_999); - #[cfg(not(Py_GIL_DISABLED))] assert_warnings!( py, check_fixed_offset("leap second", 2014, 5, 6, 7, 8, 59, 1_999_999, 999_999), @@ -1101,7 +1099,6 @@ mod tests { check_time("regular", 3, 5, 7, 999_999, 999_999); - #[cfg(not(Py_GIL_DISABLED))] assert_warnings!( py, check_time("leap second", 3, 5, 59, 1_999_999, 999_999), @@ -1163,10 +1160,10 @@ mod tests { .unwrap() } - #[cfg(not(any(target_arch = "wasm32", Py_GIL_DISABLED)))] + #[cfg(not(any(target_arch = "wasm32")))] mod proptests { use super::*; - use crate::tests::common::CatchWarnings; + use crate::test_utils::CatchWarnings; use crate::types::IntoPyDict; use proptest::prelude::*; use std::ffi::CString; diff --git a/src/conversions/num_bigint.rs b/src/conversions/num_bigint.rs index f9b4f11fa69..0f8924bbf12 100644 --- a/src/conversions/num_bigint.rs +++ b/src/conversions/num_bigint.rs @@ -317,7 +317,7 @@ fn int_n_bits(long: &Bound<'_, PyInt>) -> PyResult { #[cfg(test)] mod tests { use super::*; - use crate::tests::common::generate_unique_module_name; + use crate::test_utils::generate_unique_module_name; use crate::types::{PyAnyMethods as _, PyDict, PyModule}; use indoc::indoc; use pyo3_ffi::c_str; diff --git a/src/conversions/num_complex.rs b/src/conversions/num_complex.rs index edcc0ef2d54..cdb7ad3fc42 100644 --- a/src/conversions/num_complex.rs +++ b/src/conversions/num_complex.rs @@ -196,7 +196,7 @@ complex_conversion!(f64); #[cfg(test)] mod tests { use super::*; - use crate::tests::common::generate_unique_module_name; + use crate::test_utils::generate_unique_module_name; use crate::types::PyAnyMethods as _; use crate::types::{complex::PyComplexMethods, PyModule}; use crate::IntoPyObject; diff --git a/src/err/mod.rs b/src/err/mod.rs index 16265865283..fe8d3755d4a 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -853,6 +853,7 @@ mod tests { use super::PyErrState; use crate::exceptions::{self, PyTypeError, PyValueError}; use crate::impl_::pyclass::{value_of, IsSend, IsSync}; + use crate::test_utils::assert_warnings; use crate::{ffi, PyErr, PyTypeInfo, Python}; #[test] @@ -1052,7 +1053,6 @@ mod tests { warnings.call_method0("resetwarnings").unwrap(); // First, test the warning is emitted - #[cfg(not(Py_GIL_DISABLED))] assert_warnings!( py, { PyErr::warn(py, &cls, ffi::c_str!("I am warning you"), 0).unwrap() }, @@ -1072,7 +1072,6 @@ mod tests { .unwrap(); // This has the wrong module and will not raise, just be emitted - #[cfg(not(Py_GIL_DISABLED))] assert_warnings!( py, { PyErr::warn(py, &cls, ffi::c_str!("I am warning you"), 0).unwrap() }, diff --git a/src/instance.rs b/src/instance.rs index 3bbbb4729b5..f9347f2b2ca 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -2171,7 +2171,7 @@ impl Py { #[cfg(test)] mod tests { use super::{Bound, IntoPyObject, Py}; - use crate::tests::common::generate_unique_module_name; + use crate::test_utils::generate_unique_module_name; use crate::types::{dict::IntoPyDict, PyAnyMethods, PyCapsule, PyDict, PyString}; use crate::{ffi, Borrowed, PyAny, PyResult, Python}; use pyo3_ffi::c_str; diff --git a/src/lib.rs b/src/lib.rs index 34b4a6ae156..bdf0dd7cd5c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -410,7 +410,8 @@ pub use inventory; // Re-exported for `#[pyclass]` and `#[pymethods]` with `mult /// Tests and helpers which reside inside PyO3's main library. Declared first so that macros /// are available in unit tests. #[cfg(test)] -#[macro_use] +mod test_utils; +#[cfg(test)] mod tests; // Macro dependencies, also contains macros exported for use across the codebase and diff --git a/src/test_utils.rs b/src/test_utils.rs new file mode 100644 index 00000000000..32b74997bcb --- /dev/null +++ b/src/test_utils.rs @@ -0,0 +1,6 @@ +// Brings in `test_utils` from the `tests` directory +// +// to make that file function (lots of references to `pyo3` within it) need +// re-bind `crate` as pyo3 +use crate as pyo3; +include!("../tests/test_utils/mod.rs"); diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 136236d7db0..3b72764301c 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,10 +1,3 @@ -#[macro_use] -pub(crate) mod common { - #[cfg(not(Py_GIL_DISABLED))] - use crate as pyo3; - include!("./common.rs"); -} - /// Test macro hygiene - this is in the crate since we won't have /// `pyo3` available in the crate root. #[cfg(all(test, feature = "macros"))] diff --git a/src/types/any.rs b/src/types/any.rs index fb3d97b55ce..665fb607480 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -1624,7 +1624,7 @@ mod tests { use crate::{ basic::CompareOp, ffi, - tests::common::generate_unique_module_name, + test_utils::generate_unique_module_name, types::{IntoPyDict, PyAny, PyAnyMethods, PyBool, PyInt, PyList, PyModule, PyTypeMethods}, Bound, BoundObject, IntoPyObject, PyTypeInfo, Python, }; diff --git a/src/types/typeobject.rs b/src/types/typeobject.rs index 58c94d46cf4..953a78bc6aa 100644 --- a/src/types/typeobject.rs +++ b/src/types/typeobject.rs @@ -244,7 +244,7 @@ impl<'py> PyTypeMethods<'py> for Bound<'py, PyType> { #[cfg(test)] mod tests { - use crate::tests::common::generate_unique_module_name; + use crate::test_utils::generate_unique_module_name; use crate::types::{PyAnyMethods, PyBool, PyInt, PyModule, PyTuple, PyType, PyTypeMethods}; use crate::PyAny; use crate::Python; diff --git a/tests/test_arithmetics.rs b/tests/test_arithmetics.rs index 3e0e7712ba4..bf3f99c3462 100644 --- a/tests/test_arithmetics.rs +++ b/tests/test_arithmetics.rs @@ -4,8 +4,7 @@ use pyo3::class::basic::CompareOp; use pyo3::py_run; use pyo3::{prelude::*, BoundObject}; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass] struct UnaryArithmetic { diff --git a/tests/test_buffer.rs b/tests/test_buffer.rs index 4d975f36479..7ffa3e58864 100644 --- a/tests/test_buffer.rs +++ b/tests/test_buffer.rs @@ -9,8 +9,7 @@ use std::{ }; #[macro_use] -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; enum TestGetBufferError { NullShape, diff --git a/tests/test_buffer_protocol.rs b/tests/test_buffer_protocol.rs index d5f50d02e5d..5a2baa33d05 100644 --- a/tests/test_buffer_protocol.rs +++ b/tests/test_buffer_protocol.rs @@ -13,8 +13,7 @@ use std::ptr; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass] struct TestBufferClass { @@ -98,8 +97,8 @@ fn test_buffer_referenced() { #[test] #[cfg(all(Py_3_8, not(Py_GIL_DISABLED)))] // sys.unraisablehook not available until Python 3.8 fn test_releasebuffer_unraisable_error() { - use common::UnraisableCapture; use pyo3::exceptions::PyValueError; + use test_utils::UnraisableCapture; #[pyclass] struct ReleaseBufferError {} diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs index 7df25c2fe58..0caaf2a37ff 100644 --- a/tests/test_bytes.rs +++ b/tests/test_bytes.rs @@ -3,8 +3,7 @@ use pyo3::prelude::*; use pyo3::types::PyBytes; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyfunction] fn bytes_pybytes_conversion(bytes: &[u8]) -> &[u8] { diff --git a/tests/test_class_attributes.rs b/tests/test_class_attributes.rs index b1a019c0457..e8d104d9d10 100644 --- a/tests/test_class_attributes.rs +++ b/tests/test_class_attributes.rs @@ -3,8 +3,7 @@ use pyo3::prelude::*; use pyo3::py_run; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass] struct Foo { @@ -154,8 +153,8 @@ fn recursive_class_attributes() { #[test] #[cfg(all(Py_3_8, not(Py_GIL_DISABLED)))] // sys.unraisablehook not available until Python 3.8 fn test_fallible_class_attribute() { - use common::UnraisableCapture; use pyo3::exceptions::PyValueError; + use test_utils::UnraisableCapture; #[pyclass] struct BrokenClass; diff --git a/tests/test_class_basics.rs b/tests/test_class_basics.rs index 027f050320e..1dda580c2a4 100644 --- a/tests/test_class_basics.rs +++ b/tests/test_class_basics.rs @@ -4,8 +4,7 @@ use pyo3::prelude::*; use pyo3::types::PyType; use pyo3::{py_run, PyClass}; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass] struct EmptyClass {} @@ -619,12 +618,12 @@ fn access_frozen_class_without_gil() { #[cfg(all(Py_3_8, not(Py_GIL_DISABLED)))] // sys.unraisablehook not available until Python 3.8 #[cfg_attr(target_arch = "wasm32", ignore)] fn drop_unsendable_elsewhere() { - use common::UnraisableCapture; use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, }; use std::thread::spawn; + use test_utils::UnraisableCapture; #[pyclass(unsendable)] struct Unsendable { diff --git a/tests/test_class_comparisons.rs b/tests/test_class_comparisons.rs index 2f716ca431a..e8509d1e266 100644 --- a/tests/test_class_comparisons.rs +++ b/tests/test_class_comparisons.rs @@ -2,8 +2,7 @@ use pyo3::prelude::*; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass(eq)] #[derive(Debug, Clone, PartialEq)] diff --git a/tests/test_class_conversion.rs b/tests/test_class_conversion.rs index c451a37ac35..2ff0f56a9b3 100644 --- a/tests/test_class_conversion.rs +++ b/tests/test_class_conversion.rs @@ -3,8 +3,7 @@ use pyo3::prelude::*; #[macro_use] -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass] #[derive(Clone, Debug, PartialEq)] diff --git a/tests/test_class_formatting.rs b/tests/test_class_formatting.rs index 976e6da4e5e..1b4add063d4 100644 --- a/tests/test_class_formatting.rs +++ b/tests/test_class_formatting.rs @@ -4,8 +4,7 @@ use pyo3::prelude::*; use pyo3::py_run; use std::fmt::{Display, Formatter}; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass(eq, str)] #[derive(Debug, PartialEq)] diff --git a/tests/test_coroutine.rs b/tests/test_coroutine.rs index 868caf7e69b..24e007ab5a3 100644 --- a/tests/test_coroutine.rs +++ b/tests/test_coroutine.rs @@ -14,8 +14,7 @@ use pyo3::{ #[cfg(target_has_atomic = "64")] use std::sync::atomic::{AtomicBool, Ordering}; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; fn handle_windows(test: &str) -> String { let set_event_loop_policy = r#" diff --git a/tests/test_declarative_module.rs b/tests/test_declarative_module.rs index f3a01001952..57800cdce2e 100644 --- a/tests/test_declarative_module.rs +++ b/tests/test_declarative_module.rs @@ -7,8 +7,7 @@ use pyo3::exceptions::PyException; use pyo3::prelude::*; use pyo3::sync::OnceLockExt; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; mod some_module { use pyo3::create_exception; diff --git a/tests/test_default_impls.rs b/tests/test_default_impls.rs index e5735f5dd2a..19dc0773a80 100644 --- a/tests/test_default_impls.rs +++ b/tests/test_default_impls.rs @@ -2,8 +2,7 @@ use pyo3::prelude::*; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; // Test default generated __repr__. #[pyclass(eq, eq_int)] diff --git a/tests/test_enum.rs b/tests/test_enum.rs index f1a2ab3eff6..cdc9935a6df 100644 --- a/tests/test_enum.rs +++ b/tests/test_enum.rs @@ -4,8 +4,7 @@ use pyo3::prelude::*; use pyo3::py_run; use pyo3::types::PyString; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass(eq, eq_int)] #[derive(Debug, PartialEq, Eq, Clone)] diff --git a/tests/test_exceptions.rs b/tests/test_exceptions.rs index 081041ed12d..97b5466d205 100644 --- a/tests/test_exceptions.rs +++ b/tests/test_exceptions.rs @@ -7,8 +7,7 @@ use std::fmt; #[cfg(not(target_os = "windows"))] use std::fs::File; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyfunction] #[cfg(not(target_os = "windows"))] @@ -101,9 +100,9 @@ fn test_exception_nosegfault() { #[test] #[cfg(all(Py_3_8, not(Py_GIL_DISABLED)))] fn test_write_unraisable() { - use common::UnraisableCapture; use pyo3::{exceptions::PyRuntimeError, ffi, types::PyNotImplemented}; use std::ptr; + use test_utils::UnraisableCapture; Python::attach(|py| { let capture = UnraisableCapture::install(py); diff --git a/tests/test_frompy_intopy_roundtrip.rs b/tests/test_frompy_intopy_roundtrip.rs index e8e41c14ef2..c4b120c5292 100644 --- a/tests/test_frompy_intopy_roundtrip.rs +++ b/tests/test_frompy_intopy_roundtrip.rs @@ -6,8 +6,7 @@ use std::collections::HashMap; use std::hash::Hash; #[macro_use] -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[derive(Debug, Clone, IntoPyObject, IntoPyObjectRef, FromPyObject)] pub struct A<'py> { diff --git a/tests/test_frompyobject.rs b/tests/test_frompyobject.rs index 35cc813a603..9a72e57284c 100644 --- a/tests/test_frompyobject.rs +++ b/tests/test_frompyobject.rs @@ -5,8 +5,7 @@ use pyo3::prelude::*; use pyo3::types::{IntoPyDict, PyDict, PyList, PyString, PyTuple}; #[macro_use] -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; /// Helper function that concatenates the error message from /// each error in the traceback into a single string that can diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 8f39c1baca1..f97868fec9f 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -13,8 +13,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Once; use std::sync::{Arc, Mutex}; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass(freelist = 2)] struct ClassWithFreelist {} diff --git a/tests/test_getter_setter.rs b/tests/test_getter_setter.rs index 4d15627ea51..582cfd89c5f 100644 --- a/tests/test_getter_setter.rs +++ b/tests/test_getter_setter.rs @@ -7,8 +7,7 @@ use pyo3::py_run; use pyo3::types::PyString; use pyo3::types::{IntoPyDict, PyList}; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass] struct ClassWithProperties { diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index 0500c0c2f83..99c26ae1041 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -6,8 +6,7 @@ use pyo3::py_run; use pyo3::ffi; use pyo3::types::IntoPyDict; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass(subclass)] struct BaseClass { diff --git a/tests/test_intopyobject.rs b/tests/test_intopyobject.rs index 58795d7c7f6..72ecc0f234d 100644 --- a/tests/test_intopyobject.rs +++ b/tests/test_intopyobject.rs @@ -6,8 +6,7 @@ use std::collections::HashMap; use std::hash::Hash; #[macro_use] -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[derive(Debug, IntoPyObject)] pub struct A<'py> { diff --git a/tests/test_macro_docs.rs b/tests/test_macro_docs.rs index fec11e51904..e27277a9326 100644 --- a/tests/test_macro_docs.rs +++ b/tests/test_macro_docs.rs @@ -4,8 +4,7 @@ use pyo3::prelude::*; use pyo3::types::IntoPyDict; #[macro_use] -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass] /// The MacroDocs class. diff --git a/tests/test_macros.rs b/tests/test_macros.rs index 72b5b45ef7f..e7b1997145a 100644 --- a/tests/test_macros.rs +++ b/tests/test_macros.rs @@ -5,8 +5,7 @@ use pyo3::prelude::*; #[macro_use] -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; macro_rules! make_struct_using_macro { // Ensure that one doesn't need to fall back on the escape type: tt diff --git a/tests/test_mapping.rs b/tests/test_mapping.rs index bab7c0a418a..df52372d143 100644 --- a/tests/test_mapping.rs +++ b/tests/test_mapping.rs @@ -10,8 +10,7 @@ use pyo3::types::PyList; use pyo3::types::PyMapping; use pyo3::types::PySequence; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass(mapping)] struct Mapping { diff --git a/tests/test_methods.rs b/tests/test_methods.rs index 06544f221e9..7fbc1a138b6 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -2,7 +2,6 @@ #[cfg(not(Py_LIMITED_API))] use pyo3::exceptions::PyWarning; -#[cfg(not(Py_GIL_DISABLED))] use pyo3::exceptions::{PyFutureWarning, PyUserWarning}; use pyo3::prelude::*; use pyo3::py_run; @@ -11,8 +10,7 @@ use pyo3::types::{IntoPyDict, PyDict, PyList, PySet, PyString, PyTuple, PyType}; use pyo3::BoundObject; use pyo3_macros::pyclass; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass] struct InstanceMethod { @@ -1223,7 +1221,6 @@ impl UserDefinedWarning { } #[test] -#[cfg(not(Py_GIL_DISABLED))] // FIXME: enable once `warnings` is thread-safe fn test_pymethods_warn() { // We do not test #[classattr] nor __traverse__ // because it doesn't make sense to implement deprecated methods for them. @@ -1424,7 +1421,6 @@ fn test_pymethods_warn() { } #[test] -#[cfg(not(Py_GIL_DISABLED))] // FIXME: enable once `warnings` is thread-safe fn test_py_methods_multiple_warn() { #[pyclass] struct MultipleWarnContainer {} diff --git a/tests/test_module.rs b/tests/test_module.rs index 2f5766aadaf..5e6244420e0 100644 --- a/tests/test_module.rs +++ b/tests/test_module.rs @@ -8,8 +8,7 @@ use pyo3::types::{IntoPyDict, PyDict, PyTuple}; use pyo3::BoundObject; use pyo3_ffi::c_str; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass] struct AnonClass {} @@ -168,7 +167,7 @@ fn test_module_from_code_bound() { py, c_str!("def add(a,b):\n\treturn a+b"), c_str!("adder_mod.py"), - &common::generate_unique_module_name("adder_mod"), + &test_utils::generate_unique_module_name("adder_mod"), ) .expect("Module code should be loaded"); diff --git a/tests/test_multiple_pymethods.rs b/tests/test_multiple_pymethods.rs index ccbda42b865..1a788425113 100644 --- a/tests/test_multiple_pymethods.rs +++ b/tests/test_multiple_pymethods.rs @@ -4,8 +4,7 @@ use pyo3::prelude::*; use pyo3::types::PyType; #[macro_use] -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass] struct PyClassWithMultiplePyMethods {} diff --git a/tests/test_proto_methods.rs b/tests/test_proto_methods.rs index e8df772faf1..94a8ddd368a 100644 --- a/tests/test_proto_methods.rs +++ b/tests/test_proto_methods.rs @@ -6,8 +6,7 @@ use pyo3::{prelude::*, py_run}; use std::iter; use std::sync::Mutex; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass] struct EmptyClass; diff --git a/tests/test_pyfunction.rs b/tests/test_pyfunction.rs index 54447b92940..07bd9f5482c 100644 --- a/tests/test_pyfunction.rs +++ b/tests/test_pyfunction.rs @@ -7,7 +7,6 @@ use std::collections::HashMap; use pyo3::buffer::PyBuffer; #[cfg(not(Py_LIMITED_API))] use pyo3::exceptions::PyWarning; -#[cfg(not(Py_GIL_DISABLED))] use pyo3::exceptions::{PyFutureWarning, PyUserWarning}; use pyo3::ffi::c_str; use pyo3::prelude::*; @@ -18,8 +17,7 @@ use pyo3::types::PyFunction; use pyo3::types::{self, PyCFunction}; use pyo3_macros::pyclass; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyfunction(name = "struct")] fn struct_function() {} @@ -669,7 +667,6 @@ impl UserDefinedWarning { } #[test] -#[cfg(not(Py_GIL_DISABLED))] // FIXME: enable once `warnings` is thread-safe fn test_pyfunction_warn() { #[pyfunction] #[pyo3(warn(message = "this function raises warning"))] @@ -724,7 +721,6 @@ fn test_pyfunction_warn() { } #[test] -#[cfg(not(Py_GIL_DISABLED))] // FIXME: enable once `warnings` is thread-safe fn test_pyfunction_multiple_warnings() { #[pyfunction] #[pyo3(warn(message = "this function raises warning"))] diff --git a/tests/test_pyself.rs b/tests/test_pyself.rs index 3f7d3844fce..b9b9b0ea4d2 100644 --- a/tests/test_pyself.rs +++ b/tests/test_pyself.rs @@ -5,8 +5,7 @@ use pyo3::prelude::*; use pyo3::types::{PyBytes, PyString}; use std::collections::HashMap; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; /// Assumes it's a file reader or so. /// Inspired by https://github.com/jothan/cordoba, thanks. diff --git a/tests/test_sequence.rs b/tests/test_sequence.rs index ca1b15ae9a5..970f143503f 100644 --- a/tests/test_sequence.rs +++ b/tests/test_sequence.rs @@ -6,8 +6,7 @@ use pyo3::{ffi, prelude::*}; use pyo3::py_run; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass] struct ByteSequence { diff --git a/tests/test_static_slots.rs b/tests/test_static_slots.rs index b11ecd08fb9..c00a8815270 100644 --- a/tests/test_static_slots.rs +++ b/tests/test_static_slots.rs @@ -6,8 +6,7 @@ use pyo3::types::IntoPyDict; use pyo3::py_run; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass] struct Count5(); diff --git a/tests/test_string.rs b/tests/test_string.rs index 6ac6615d8a2..1648e067760 100644 --- a/tests/test_string.rs +++ b/tests/test_string.rs @@ -2,8 +2,7 @@ use pyo3::prelude::*; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyfunction] fn take_str(_s: &str) {} diff --git a/tests/test_text_signature.rs b/tests/test_text_signature.rs index d1decc9bf3b..88057b05b6a 100644 --- a/tests/test_text_signature.rs +++ b/tests/test_text_signature.rs @@ -4,8 +4,7 @@ use pyo3::prelude::*; use pyo3::types::{PyDict, PyTuple}; use pyo3::{types::PyType, wrap_pymodule}; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[test] fn class_without_docs_or_signature() { diff --git a/src/tests/common.rs b/tests/test_utils/mod.rs similarity index 75% rename from src/tests/common.rs rename to tests/test_utils/mod.rs index df5af81c247..523bc1ede6f 100644 --- a/src/tests/common.rs +++ b/tests/test_utils/mod.rs @@ -1,21 +1,28 @@ +// Common macros and helpers for tests +// +// This file is used in two different ways, which makes it a bit of a pain to build: +// - as a module `include!`-ed from `src/test_utils.rs` +// - as a module `mod test_utils` in various integration tests +// // the inner mod enables the #![allow(dead_code)] to -// be applied - `test_utils.rs` uses `include!` to pull in this file +// be applied - `src/test_utils.rs` uses `include!` to pull in this file -/// Common macros and helpers for tests -#[allow(dead_code)] // many tests do not use the complete set of functionality offered here +#[allow(dead_code, unused_macros)] // many tests do not use the complete set of functionality offered here #[allow(missing_docs)] // only used in tests #[macro_use] mod inner { - #[allow(unused_imports)] // pulls in `use crate as pyo3` in `test_utils.rs` + #[allow(unused_imports)] + // pulls in `use crate as pyo3` in `src/test_utils.rs`, no function in integration tests use super::*; - #[cfg(not(Py_GIL_DISABLED))] use pyo3::prelude::*; - #[cfg(not(Py_GIL_DISABLED))] + use pyo3::sync::MutexExt; use pyo3::types::{IntoPyDict, PyList}; + use std::sync::{Mutex, PoisonError}; + use uuid::Uuid; #[macro_export] @@ -76,31 +83,22 @@ mod inner { py_expect_warning!($py, *d, $code, [$(($warning_msg, $warning_category)),+]) }}; ($py:expr, *$dict:expr, $code:expr, [$(($warning_msg:literal, $warning_category:path)),+] $(,)?) => {{ - let code_lines: Vec<&str> = $code.lines().collect(); - let indented_code: String = code_lines.iter() - .map(|line| format!(" {}", line)) // add 4 spaces indentation - .collect::>() - .join("\n"); - - let wrapped_code = format!(r#" -import warnings -with warnings.catch_warnings(record=True) as warning_record: -{} -"#, indented_code); - - $py.run(&std::ffi::CString::new(wrapped_code).unwrap(), None, Some(&$dict.as_borrowed())).expect("Failed to run warning testing code"); - let expected_warnings = [$(($warning_msg, <$warning_category as pyo3::PyTypeInfo>::type_object($py))),+]; - let warning_record: Bound<'_, pyo3::types::PyList> = $dict.get_item("warning_record").expect("Failed to capture warnings").expect("Failed to downcast to PyList").extract().unwrap(); - - assert_eq!(warning_record.len(), expected_warnings.len(), "Expecting {} warnings but got {}", expected_warnings.len(), warning_record.len()); - - for ((index, warning), (msg, category)) in warning_record.iter().enumerate().zip(expected_warnings.iter()) { - let actual_msg = warning.getattr("message").unwrap().str().unwrap().to_string_lossy().to_string(); - let actual_category = warning.getattr("category").unwrap(); - - assert_eq!(actual_msg, msg.to_string(), "Warning message mismatch at index {}, expecting `{}` but got `{}`", index, msg, actual_msg); - assert!(actual_category.is(category), "Warning category mismatch at index {}, expecting {:?} but got {:?}", index, category, actual_category); - } + $crate::test_utils::CatchWarnings::enter($py, |warning_record| { + $py.run(&std::ffi::CString::new($code).unwrap(), None, Some(&$dict.as_borrowed())).expect("Failed to run warning testing code"); + let expected_warnings = [$(($warning_msg, <$warning_category as pyo3::PyTypeInfo>::type_object($py))),+]; + + assert_eq!(warning_record.len(), expected_warnings.len(), "Expecting {} warnings but got {}", expected_warnings.len(), warning_record.len()); + + for ((index, warning), (msg, category)) in warning_record.iter().enumerate().zip(expected_warnings.iter()) { + let actual_msg = warning.getattr("message").unwrap().str().unwrap().to_string_lossy().to_string(); + let actual_category = warning.getattr("category").unwrap(); + + assert_eq!(actual_msg, msg.to_string(), "Warning message mismatch at index {}, expecting `{}` but got `{}`", index, msg, actual_msg); + assert!(actual_category.is(category), "Warning category mismatch at index {}, expecting {:?} but got {:?}", index, category, actual_category); + } + + Ok(()) + }).expect("failed to test warnings"); }}; } @@ -166,17 +164,23 @@ with warnings.catch_warnings(record=True) as warning_record: } } - #[cfg(not(Py_GIL_DISABLED))] pub struct CatchWarnings<'py> { catch_warnings: Bound<'py, PyAny>, } - #[cfg(not(Py_GIL_DISABLED))] + /// catch_warnings is not thread-safe, so only one thread can be using this struct at + /// a time. + static CATCH_WARNINGS_MUTEX: Mutex<()> = Mutex::new(()); + impl<'py> CatchWarnings<'py> { pub fn enter( py: Python<'py>, f: impl FnOnce(&Bound<'py, PyList>) -> PyResult, ) -> PyResult { + // NB this is best-effort, other tests could always call the warnings API directly. + let _mutex_guard = CATCH_WARNINGS_MUTEX + .lock_py_attached(py) + .unwrap_or_else(PoisonError::into_inner); let warnings = py.import("warnings")?; let kwargs = [("record", true)].into_py_dict(py)?; let catch_warnings = warnings @@ -188,7 +192,6 @@ with warnings.catch_warnings(record=True) as warning_record: } } - #[cfg(not(Py_GIL_DISABLED))] impl Drop for CatchWarnings<'_> { fn drop(&mut self) { let py = self.catch_warnings.py(); @@ -198,11 +201,9 @@ with warnings.catch_warnings(record=True) as warning_record: } } - #[cfg(not(Py_GIL_DISABLED))] - #[macro_export] macro_rules! assert_warnings { ($py:expr, $body:expr, [$(($category:ty, $message:literal)),+] $(,)? ) => {{ - $crate::tests::common::CatchWarnings::enter($py, |w| { + $crate::test_utils::CatchWarnings::enter($py, |w| { use $crate::types::{PyListMethods, PyStringMethods}; $body; let expected_warnings = [$((<$category as $crate::type_object::PyTypeInfo>::type_object($py), $message)),+]; @@ -222,6 +223,8 @@ with warnings.catch_warnings(record=True) as warning_record: }}; } + pub(crate) use assert_warnings; + pub fn generate_unique_module_name(base: &str) -> std::ffi::CString { let uuid = Uuid::new_v4().simple().to_string(); std::ffi::CString::new(format!("{base}_{uuid}")).unwrap() diff --git a/tests/test_variable_arguments.rs b/tests/test_variable_arguments.rs index 2c7851914aa..d186e8def3b 100644 --- a/tests/test_variable_arguments.rs +++ b/tests/test_variable_arguments.rs @@ -3,8 +3,7 @@ use pyo3::prelude::*; use pyo3::types::{PyDict, PyTuple}; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass] struct MyClass {} diff --git a/tests/test_various.rs b/tests/test_various.rs index c0ed072f18e..cf2ae82b5ff 100644 --- a/tests/test_various.rs +++ b/tests/test_various.rs @@ -6,8 +6,7 @@ use pyo3::types::PyTuple; use std::fmt; -#[path = "../src/tests/common.rs"] -mod common; +mod test_utils; #[pyclass] struct MutRefArg {