Skip to content

Commit 0e50338

Browse files
Merge #3171
3171: RFC: Simplify version handling of UI tests. r=davidhewitt a=adamreichold Due to checking in error outputs these tests only work reliably on stable and not on intermediate version between our MSRV (currently 1.48) and the current stable version. Hence this simplifies things to run only MSRV-compatible tests for the MSRV builds, anything else for stable builds except for those tests which require the nightly feature, i.e. the `Ungil` being distinct from the `Send` trait. Finally, `not_send3` is disabled when using the nightly feature since while `Rc` is not send, it also not GIL-bound and hence can be passed into `allow_threads` as it does not actually spawn a new thread. Co-authored-by: Adam Reichold <[email protected]>
2 parents 2ed1d70 + 0f628c9 commit 0e50338

12 files changed

+131
-266
lines changed

.github/workflows/build.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ jobs:
5959
name: Prepare minimal package versions (MSRV only)
6060
run: nox -s set-minimal-package-versions
6161

62+
- if: inputs.rust == 'nightly' || inputs.msrv == 'MSRV'
63+
name: Ignore changed error messages when using trybuild
64+
run: echo "TRYBUILD=overwrite" >> "$GITHUB_ENV"
65+
6266
- name: Build docs
6367
run: cargo doc --no-deps --no-default-features --features "full ${{ inputs.extra-features }}"
6468

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ repository = "https://github.com/pyo3/pyo3"
1010
documentation = "https://docs.rs/crate/pyo3/"
1111
categories = ["api-bindings", "development-tools::ffi"]
1212
license = "Apache-2.0"
13-
exclude = ["/.gitignore", ".cargo/config", "/codecov.yml", "/Makefile", "/pyproject.toml", "/noxfile.py"]
13+
exclude = ["/.gitignore", ".cargo/config", "/codecov.yml", "/Makefile", "/pyproject.toml", "/noxfile.py", "/.github", "/tests/test_compile_error.rs", "/tests/ui"]
1414
edition = "2018"
1515

1616
[dependencies]

src/marker.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,48 @@ use std::os::raw::c_int;
140140
/// the GIL is not held.
141141
///
142142
/// See the [module-level documentation](self) for more information.
143+
///
144+
/// # Examples
145+
///
146+
/// This tracking is currently imprecise as it relies on the [`Send`] auto trait on stable Rust.
147+
/// For example, an `Rc` smart pointer should be usable without the GIL, but we currently prevent that:
148+
///
149+
/// ```compile_fail
150+
/// # use pyo3::prelude::*;
151+
/// use std::rc::Rc;
152+
///
153+
/// Python::with_gil(|py| {
154+
/// let rc = Rc::new(42);
155+
///
156+
/// py.allow_threads(|| {
157+
/// println!("{:?}", rc);
158+
/// });
159+
/// });
160+
/// ```
161+
///
162+
/// This also implies that the interplay between `with_gil` and `allow_threads` is unsound, for example
163+
/// one can circumvent this protection using the [`send_wrapper`](https://docs.rs/send_wrapper/) crate:
164+
///
165+
/// ```no_run
166+
/// # use pyo3::prelude::*;
167+
/// # use pyo3::types::PyString;
168+
/// use send_wrapper::SendWrapper;
169+
///
170+
/// Python::with_gil(|py| {
171+
/// let string = PyString::new(py, "foo");
172+
///
173+
/// let wrapped = SendWrapper::new(string);
174+
///
175+
/// py.allow_threads(|| {
176+
/// let sneaky: &PyString = *wrapped;
177+
///
178+
/// println!("{:?}", sneaky);
179+
/// });
180+
/// });
181+
/// ```
182+
///
183+
/// Fixing this loophole on stable Rust has significant ergonomic issues, but it is fixed when using
184+
/// nightly Rust and the `nightly` feature, c.f. [#2141](https://github.com/PyO3/pyo3/issues/2141).
143185
#[cfg_attr(docsrs, doc(cfg(all())))] // Hide the cfg flag
144186
#[cfg(not(feature = "nightly"))]
145187
pub unsafe trait Ungil {}
@@ -156,6 +198,70 @@ unsafe impl<T: Send> Ungil for T {}
156198
/// the GIL is not held.
157199
///
158200
/// See the [module-level documentation](self) for more information.
201+
///
202+
/// # Examples
203+
///
204+
/// Types which are `Ungil` cannot be used in contexts where the GIL was released, e.g.
205+
///
206+
/// ```compile_fail
207+
/// # use pyo3::prelude::*;
208+
/// # use pyo3::types::PyString;
209+
/// Python::with_gil(|py| {
210+
/// let string = PyString::new(py, "foo");
211+
///
212+
/// py.allow_threads(|| {
213+
/// println!("{:?}", string);
214+
/// });
215+
/// });
216+
/// ```
217+
///
218+
/// This applies to the GIL token `Python` itself as well, e.g.
219+
///
220+
/// ```compile_fail
221+
/// # use pyo3::prelude::*;
222+
/// Python::with_gil(|py| {
223+
/// py.allow_threads(|| {
224+
/// drop(py);
225+
/// });
226+
/// });
227+
/// ```
228+
///
229+
/// On nightly Rust, this is not based on the [`Send`] auto trait and hence we are able
230+
/// to prevent incorrectly circumventing it using e.g. the [`send_wrapper`](https://docs.rs/send_wrapper/) crate:
231+
///
232+
/// ```compile_fail
233+
/// # use pyo3::prelude::*;
234+
/// # use pyo3::types::PyString;
235+
/// use send_wrapper::SendWrapper;
236+
///
237+
/// Python::with_gil(|py| {
238+
/// let string = PyString::new(py, "foo");
239+
///
240+
/// let wrapped = SendWrapper::new(string);
241+
///
242+
/// py.allow_threads(|| {
243+
/// let sneaky: &PyString = *wrapped;
244+
///
245+
/// println!("{:?}", sneaky);
246+
/// });
247+
/// });
248+
/// ```
249+
///
250+
/// This also enables using non-[`Send`] types in `allow_threads`,
251+
/// at least if they are not also bound to the GIL:
252+
///
253+
/// ```rust
254+
/// # use pyo3::prelude::*;
255+
/// use std::rc::Rc;
256+
///
257+
/// Python::with_gil(|py| {
258+
/// let rc = Rc::new(42);
259+
///
260+
/// py.allow_threads(|| {
261+
/// println!("{:?}", rc);
262+
/// });
263+
/// });
264+
/// ```
159265
#[cfg(feature = "nightly")]
160266
pub unsafe auto trait Ungil {}
161267

tests/test_compile_error.rs

Lines changed: 20 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,8 @@
11
#![cfg(feature = "macros")]
22

3-
#[rustversion::not(nightly)]
43
#[cfg(not(target_arch = "wasm32"))] // Not possible to invoke compiler from wasm
54
#[test]
65
fn test_compile_errors() {
7-
// stable - require all tests to pass
8-
_test_compile_errors()
9-
}
10-
11-
#[cfg(not(feature = "nightly"))]
12-
#[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
13-
#[rustversion::nightly]
14-
#[test]
15-
fn test_compile_errors() {
16-
// nightly - don't care if test output is potentially wrong, to avoid churn in PyO3's CI thanks
17-
// to diagnostics changing on nightly.
18-
let _ = std::panic::catch_unwind(_test_compile_errors);
19-
}
20-
21-
#[cfg(feature = "nightly")]
22-
#[cfg(not(target_arch = "wasm32"))] // Not possible to invoke compiler from wasm
23-
#[rustversion::nightly]
24-
#[test]
25-
fn test_compile_errors() {
26-
// nightly - don't care if test output is potentially wrong, to avoid churn in PyO3's CI thanks
27-
// to diagnostics changing on nightly.
28-
_test_compile_errors()
29-
}
30-
31-
#[cfg(not(feature = "nightly"))]
32-
fn _test_compile_errors() {
336
let t = trybuild::TestCases::new();
347

358
t.compile_fail("tests/ui/invalid_macro_args.rs");
@@ -45,91 +18,24 @@ fn _test_compile_errors() {
4518
t.compile_fail("tests/ui/invalid_pymethod_names.rs");
4619
t.compile_fail("tests/ui/invalid_pymodule_args.rs");
4720
t.compile_fail("tests/ui/reject_generics.rs");
48-
49-
tests_rust_1_49(&t);
50-
tests_rust_1_56(&t);
51-
tests_rust_1_57(&t);
52-
tests_rust_1_58(&t);
53-
tests_rust_1_60(&t);
54-
tests_rust_1_62(&t);
55-
tests_rust_1_63(&t);
56-
57-
#[rustversion::since(1.49)]
58-
fn tests_rust_1_49(t: &trybuild::TestCases) {
59-
t.compile_fail("tests/ui/deprecations.rs");
60-
}
61-
#[rustversion::before(1.49)]
62-
fn tests_rust_1_49(_t: &trybuild::TestCases) {}
63-
64-
#[rustversion::since(1.56)]
65-
fn tests_rust_1_56(t: &trybuild::TestCases) {
66-
t.compile_fail("tests/ui/invalid_closure.rs");
67-
68-
t.compile_fail("tests/ui/pyclass_send.rs");
69-
}
70-
71-
#[rustversion::before(1.56)]
72-
fn tests_rust_1_56(_t: &trybuild::TestCases) {}
73-
74-
#[rustversion::since(1.57)]
75-
fn tests_rust_1_57(t: &trybuild::TestCases) {
76-
t.compile_fail("tests/ui/invalid_argument_attributes.rs");
77-
t.compile_fail("tests/ui/invalid_frompy_derive.rs");
78-
t.compile_fail("tests/ui/static_ref.rs");
79-
t.compile_fail("tests/ui/wrong_aspyref_lifetimes.rs");
80-
}
81-
82-
#[rustversion::before(1.57)]
83-
fn tests_rust_1_57(_t: &trybuild::TestCases) {}
84-
85-
#[rustversion::since(1.58)]
86-
fn tests_rust_1_58(t: &trybuild::TestCases) {
87-
t.compile_fail("tests/ui/invalid_pyfunctions.rs");
88-
t.compile_fail("tests/ui/invalid_pymethods.rs");
89-
#[cfg(Py_LIMITED_API)]
90-
t.compile_fail("tests/ui/abi3_nativetype_inheritance.rs");
91-
}
92-
93-
#[rustversion::before(1.58)]
94-
fn tests_rust_1_58(_t: &trybuild::TestCases) {}
95-
96-
#[rustversion::since(1.60)]
97-
fn tests_rust_1_60(t: &trybuild::TestCases) {
98-
t.compile_fail("tests/ui/invalid_intern_arg.rs");
99-
t.compile_fail("tests/ui/invalid_frozen_pyclass_borrow.rs");
100-
}
101-
102-
#[rustversion::before(1.60)]
103-
fn tests_rust_1_60(_t: &trybuild::TestCases) {}
104-
105-
#[rustversion::since(1.62)]
106-
fn tests_rust_1_62(t: &trybuild::TestCases) {
107-
t.compile_fail("tests/ui/invalid_pymethod_receiver.rs");
108-
t.compile_fail("tests/ui/missing_intopy.rs");
109-
}
110-
111-
#[rustversion::before(1.62)]
112-
fn tests_rust_1_62(_t: &trybuild::TestCases) {}
113-
114-
#[rustversion::since(1.63)]
115-
fn tests_rust_1_63(t: &trybuild::TestCases) {
116-
t.compile_fail("tests/ui/invalid_result_conversion.rs");
117-
t.compile_fail("tests/ui/not_send.rs");
118-
t.compile_fail("tests/ui/not_send2.rs");
119-
t.compile_fail("tests/ui/not_send3.rs");
120-
t.compile_fail("tests/ui/get_set_all.rs");
121-
t.compile_fail("tests/ui/traverse_bare_self.rs");
122-
}
123-
124-
#[rustversion::before(1.63)]
125-
fn tests_rust_1_63(_t: &trybuild::TestCases) {}
126-
}
127-
128-
#[cfg(feature = "nightly")]
129-
fn _test_compile_errors() {
130-
let t = trybuild::TestCases::new();
131-
132-
t.compile_fail("tests/ui/not_send_auto_trait.rs");
133-
t.compile_fail("tests/ui/not_send_auto_trait2.rs");
134-
t.compile_fail("tests/ui/send_wrapper.rs");
21+
t.compile_fail("tests/ui/deprecations.rs");
22+
t.compile_fail("tests/ui/invalid_closure.rs");
23+
t.compile_fail("tests/ui/pyclass_send.rs");
24+
t.compile_fail("tests/ui/invalid_argument_attributes.rs");
25+
t.compile_fail("tests/ui/invalid_frompy_derive.rs");
26+
t.compile_fail("tests/ui/static_ref.rs");
27+
t.compile_fail("tests/ui/wrong_aspyref_lifetimes.rs");
28+
t.compile_fail("tests/ui/invalid_pyfunctions.rs");
29+
t.compile_fail("tests/ui/invalid_pymethods.rs");
30+
#[cfg(Py_LIMITED_API)]
31+
t.compile_fail("tests/ui/abi3_nativetype_inheritance.rs");
32+
t.compile_fail("tests/ui/invalid_intern_arg.rs");
33+
t.compile_fail("tests/ui/invalid_frozen_pyclass_borrow.rs");
34+
t.compile_fail("tests/ui/invalid_pymethod_receiver.rs");
35+
t.compile_fail("tests/ui/missing_intopy.rs");
36+
t.compile_fail("tests/ui/invalid_result_conversion.rs");
37+
t.compile_fail("tests/ui/not_send.rs");
38+
t.compile_fail("tests/ui/not_send2.rs");
39+
t.compile_fail("tests/ui/get_set_all.rs");
40+
t.compile_fail("tests/ui/traverse_bare_self.rs");
13541
}

tests/ui/not_send3.rs

Lines changed: 0 additions & 12 deletions
This file was deleted.

tests/ui/not_send3.stderr

Lines changed: 0 additions & 24 deletions
This file was deleted.

tests/ui/not_send_auto_trait.rs

Lines changed: 0 additions & 11 deletions
This file was deleted.

tests/ui/not_send_auto_trait.stderr

Lines changed: 0 additions & 21 deletions
This file was deleted.

tests/ui/not_send_auto_trait2.rs

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)