Skip to content

Commit cd6ef9d

Browse files
rwgkcursoragent
andcommitted
Expose __cpp_noexcept_function_type to Python tests and use explicit skip guards.
This replaces hasattr-based optional assertions with skipif-gated noexcept-only tests so skipped coverage is visible in pytest output while keeping non-noexcept checks always active. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 3914a28 commit cd6ef9d

4 files changed

Lines changed: 86 additions & 38 deletions

File tree

tests/pybind11_tests.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ PYBIND11_MODULE(pybind11_tests, m, py::mod_gil_not_used()) {
105105
m.attr("detailed_error_messages_enabled") = false;
106106
#endif
107107

108+
#if defined(__cpp_noexcept_function_type)
109+
m.attr("defined___cpp_noexcept_function_type") = true;
110+
#else
111+
m.attr("defined___cpp_noexcept_function_type") = false;
112+
#endif
113+
108114
py::class_<UserType>(m, "UserType", "A `py::class_` type for testing")
109115
.def(py::init<>())
110116
.def(py::init<int>())

tests/test_buffers.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import pytest
88

99
import env
10-
from pybind11_tests import ConstructorStats
10+
from pybind11_tests import ConstructorStats, defined___cpp_noexcept_function_type
1111
from pybind11_tests import buffers as m
1212

1313
np = pytest.importorskip("numpy")
@@ -446,17 +446,26 @@ def test_ref_qualified_def_buffer():
446446
assert carr.shape == (4,)
447447
assert carr.flags["WRITEABLE"] is False
448448

449-
# noexcept ref-qualified forms are available only when noexcept is part of function type
450-
if hasattr(m, "OneDBufferLRefNoexcept"):
451-
nbuf = m.OneDBufferLRefNoexcept(3)
452-
narr = np.frombuffer(nbuf, dtype=np.float32)
453-
assert narr.shape == (3,)
454-
narr[2] = 7.0
455-
narr2 = np.frombuffer(nbuf, dtype=np.float32)
456-
assert narr2[2] == pytest.approx(7.0)
457-
458-
if hasattr(m, "OneDBufferConstLRefNoexcept"):
459-
ncbuf = m.OneDBufferConstLRefNoexcept(2)
460-
ncarr = np.frombuffer(ncbuf, dtype=np.float32)
461-
assert ncarr.shape == (2,)
462-
assert ncarr.flags["WRITEABLE"] is False
449+
450+
@pytest.mark.skipif(
451+
not defined___cpp_noexcept_function_type,
452+
reason="Requires __cpp_noexcept_function_type",
453+
)
454+
def test_ref_qualified_noexcept_def_buffer():
455+
"""Test issue #2234 follow-up: def_buffer with noexcept ref-qualified member pointers.
456+
457+
Covers:
458+
- def_buffer(Return (Class::*)(Args...) & noexcept)
459+
- def_buffer(Return (Class::*)(Args...) const & noexcept)
460+
"""
461+
nbuf = m.OneDBufferLRefNoexcept(3)
462+
narr = np.frombuffer(nbuf, dtype=np.float32)
463+
assert narr.shape == (3,)
464+
narr[2] = 7.0
465+
narr2 = np.frombuffer(nbuf, dtype=np.float32)
466+
assert narr2[2] == pytest.approx(7.0)
467+
468+
ncbuf = m.OneDBufferConstLRefNoexcept(2)
469+
ncarr = np.frombuffer(ncbuf, dtype=np.float32)
470+
assert ncarr.shape == (2,)
471+
assert ncarr.flags["WRITEABLE"] is False

tests/test_methods_and_attributes.py

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pytest
66

77
import env
8-
from pybind11_tests import ConstructorStats
8+
from pybind11_tests import ConstructorStats, defined___cpp_noexcept_function_type
99
from pybind11_tests import methods_and_attributes as m
1010

1111
NO_GETTER_MSG = (
@@ -555,8 +555,6 @@ def test_rvalue_ref_qualified_methods():
555555
Covers:
556556
- Return (Class::*)(Args...) && (take)
557557
- Return (Class::*)(Args...) const && (peek)
558-
- Return (Class::*)(Args...) && noexcept (take_noexcept, C++17 only)
559-
- Return (Class::*)(Args...) const && noexcept (peek_noexcept, C++17 only)
560558
"""
561559
obj = m.RValueRefDerived()
562560
# && moves m_payload: first call gets the value, second gets empty string
@@ -565,14 +563,24 @@ def test_rvalue_ref_qualified_methods():
565563
# const && doesn't move: peek() is stable across calls
566564
assert obj.peek() == 77
567565
assert obj.peek() == 77
568-
# noexcept variants are bound only under C++17; skip gracefully if absent
569-
if hasattr(obj, "take_noexcept"):
570-
obj2 = m.RValueRefDerived()
571-
assert obj2.take_noexcept() == "rref_payload"
572-
assert obj2.take_noexcept() == ""
573-
if hasattr(obj, "peek_noexcept"):
574-
assert obj.peek_noexcept() == 77
575-
assert obj.peek_noexcept() == 77
566+
567+
568+
@pytest.mark.skipif(
569+
not defined___cpp_noexcept_function_type,
570+
reason="Requires __cpp_noexcept_function_type",
571+
)
572+
def test_noexcept_rvalue_ref_qualified_methods():
573+
"""Test noexcept rvalue-ref-qualified methods from an unregistered base.
574+
575+
Covers:
576+
- Return (Class::*)(Args...) && noexcept (take_noexcept)
577+
- Return (Class::*)(Args...) const && noexcept (peek_noexcept)
578+
"""
579+
obj = m.RValueRefDerived()
580+
assert obj.take_noexcept() == "rref_payload"
581+
assert obj.take_noexcept() == ""
582+
assert obj.peek_noexcept() == 77
583+
assert obj.peek_noexcept() == 77
576584

577585

578586
def test_noexcept_overload_cast():
@@ -612,14 +620,25 @@ def test_ref_qualified_overload_cast():
612620
assert obj.method_rref(1.0) == "(float) &&"
613621
assert obj.method_const_rref(1.0) == "(float) const &&"
614622

615-
if hasattr(obj, "method_lref_noexcept"):
616-
assert obj.method_lref_noexcept(1) == "(long) & noexcept"
617-
if hasattr(obj, "method_const_lref_noexcept"):
618-
assert obj.method_const_lref_noexcept(1) == "(long) const & noexcept"
619-
if hasattr(obj, "method_rref_noexcept"):
620-
assert obj.method_rref_noexcept(1.0) == "(double) && noexcept"
621-
if hasattr(obj, "method_const_rref_noexcept"):
622-
assert obj.method_const_rref_noexcept(1.0) == "(double) const && noexcept"
623+
624+
@pytest.mark.skipif(
625+
not defined___cpp_noexcept_function_type,
626+
reason="Requires __cpp_noexcept_function_type",
627+
)
628+
def test_noexcept_ref_qualified_overload_cast():
629+
"""Test issue #2234 follow-up: overload_cast with noexcept ref-qualified member pointers.
630+
631+
Covers:
632+
- overload_cast_impl::operator()(Return (Class::*)(Args...) & noexcept, false_type)
633+
- overload_cast_impl::operator()(Return (Class::*)(Args...) const & noexcept, true_type)
634+
- overload_cast_impl::operator()(Return (Class::*)(Args...) && noexcept, false_type)
635+
- overload_cast_impl::operator()(Return (Class::*)(Args...) const && noexcept, true_type)
636+
"""
637+
obj = m.RefQualifiedOverloaded()
638+
assert obj.method_lref_noexcept(1) == "(long) & noexcept"
639+
assert obj.method_const_lref_noexcept(1) == "(long) const & noexcept"
640+
assert obj.method_rref_noexcept(1.0) == "(double) && noexcept"
641+
assert obj.method_const_rref_noexcept(1.0) == "(double) const && noexcept"
623642

624643

625644
def test_ref_qualified():

tests/test_numpy_vectorize.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import pytest
44

5+
from pybind11_tests import defined___cpp_noexcept_function_type
56
from pybind11_tests import numpy_vectorize as m
67

78
np = pytest.importorskip("numpy")
@@ -261,10 +262,23 @@ def test_ref_qualified_method_vectorization():
261262
assert np.all(o.method_lref(x, y) == [[14, 15], [24, 25]])
262263
assert np.all(o.method_const_lref(x, y) == [[14, 15], [24, 25]])
263264

264-
if hasattr(o, "method_lref_noexcept"):
265-
assert np.all(o.method_lref_noexcept(x, y) == [[14, 15], [24, 25]])
266-
if hasattr(o, "method_const_lref_noexcept"):
267-
assert np.all(o.method_const_lref_noexcept(x, y) == [[14, 15], [24, 25]])
265+
266+
@pytest.mark.skipif(
267+
not defined___cpp_noexcept_function_type,
268+
reason="Requires __cpp_noexcept_function_type",
269+
)
270+
def test_noexcept_ref_qualified_method_vectorization():
271+
"""Test issue #2234 follow-up: vectorize with noexcept lvalue-ref-qualified member pointers.
272+
273+
Covers:
274+
- vectorize(Return (Class::*)(Args...) & noexcept)
275+
- vectorize(Return (Class::*)(Args...) const & noexcept)
276+
"""
277+
o = m.VectorizeTestClass(3)
278+
x = np.array([1, 2], dtype="int")
279+
y = np.array([[10], [20]], dtype="float32")
280+
assert np.all(o.method_lref_noexcept(x, y) == [[14, 15], [24, 25]])
281+
assert np.all(o.method_const_lref_noexcept(x, y) == [[14, 15], [24, 25]])
268282

269283

270284
def test_noexcept_method_vectorization():

0 commit comments

Comments
 (0)