From b65155581ef83e68a984f349be2ea42f66db5dfa Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 14:21:44 +0000 Subject: [PATCH 01/40] test: remove unused historical snapshot references --- tests/stubs/python-3.10/pybind11-v2.11 | 1 - tests/stubs/python-3.10/pybind11-v2.12 | 1 - .../pybind11-v3.0/numpy-array-use-type-var | 1 - .../demo/__init__.pyi | 41 ----- .../demo/_bindings/__init__.pyi | 35 ---- .../demo/_bindings/aliases/__init__.pyi | 52 ------ .../demo/_bindings/aliases/foreign_arg.pyi | 7 - .../demo/_bindings/aliases/foreign_attr.pyi | 6 - .../aliases/foreign_class_member.pyi | 12 -- .../_bindings/aliases/foreign_method_arg.pyi | 8 - .../aliases/foreign_method_return.pyi | 9 - .../demo/_bindings/aliases/foreign_return.pyi | 7 - .../_bindings/aliases/missing_self_arg.pyi | 8 - .../demo/_bindings/classes.pyi | 80 --------- .../demo/_bindings/eigen.pyi | 101 ----------- .../demo/_bindings/enum.pyi | 71 -------- .../demo/_bindings/flawed_bindings.pyi | 23 --- .../demo/_bindings/functions.pyi | 84 --------- .../demo/_bindings/issues.pyi | 43 ----- .../demo/_bindings/methods.pyi | 8 - .../demo/_bindings/numpy.pyi | 21 --- .../demo/_bindings/properties.pyi | 106 ------------ .../demo/_bindings/stl.pyi | 21 --- .../demo/_bindings/stl_bind.pyi | 141 --------------- .../demo/_bindings/typing.pyi | 10 -- .../demo/_bindings/values.pyi | 36 ---- .../demo/core.pyi | 35 ---- .../demo/pure_python/__init__.pyi | 11 -- .../demo/pure_python/classes.pyi | 21 --- .../demo/pure_python/functions.pyi | 31 ---- .../demo/pure_python/functions_3_8_plus.pyi | 30 ---- .../demo/pure_python/functions_3_9_plus.pyi | 5 - .../demo/pure_python/values.pyi | 4 - .../demo/__init__.pyi | 41 ----- .../demo/_bindings/__init__.pyi | 35 ---- .../demo/_bindings/aliases/__init__.pyi | 52 ------ .../demo/_bindings/aliases/foreign_arg.pyi | 7 - .../demo/_bindings/aliases/foreign_attr.pyi | 6 - .../aliases/foreign_class_member.pyi | 12 -- .../_bindings/aliases/foreign_method_arg.pyi | 8 - .../aliases/foreign_method_return.pyi | 9 - .../demo/_bindings/aliases/foreign_return.pyi | 7 - .../_bindings/aliases/missing_self_arg.pyi | 8 - .../demo/_bindings/classes.pyi | 80 --------- .../demo/_bindings/eigen.pyi | 101 ----------- .../demo/_bindings/enum.pyi | 71 -------- .../demo/_bindings/flawed_bindings.pyi | 23 --- .../demo/_bindings/functions.pyi | 86 ---------- .../demo/_bindings/issues.pyi | 43 ----- .../demo/_bindings/methods.pyi | 8 - .../demo/_bindings/numpy.pyi | 21 --- .../demo/_bindings/properties.pyi | 106 ------------ .../demo/_bindings/stl.pyi | 21 --- .../demo/_bindings/stl_bind.pyi | 141 --------------- .../demo/_bindings/typing.pyi | 10 -- .../demo/_bindings/values.pyi | 36 ---- .../demo/core.pyi | 35 ---- .../demo/pure_python/__init__.pyi | 11 -- .../demo/pure_python/classes.pyi | 21 --- .../demo/pure_python/functions.pyi | 31 ---- .../demo/pure_python/functions_3_8_plus.pyi | 30 ---- .../demo/pure_python/functions_3_9_plus.pyi | 5 - .../demo/pure_python/values.pyi | 4 - .../demo/__init__.pyi | 41 ----- .../demo/_bindings/__init__.pyi | 35 ---- .../demo/_bindings/aliases/__init__.pyi | 52 ------ .../demo/_bindings/aliases/foreign_arg.pyi | 7 - .../demo/_bindings/aliases/foreign_attr.pyi | 6 - .../aliases/foreign_class_member.pyi | 12 -- .../_bindings/aliases/foreign_method_arg.pyi | 8 - .../aliases/foreign_method_return.pyi | 9 - .../demo/_bindings/aliases/foreign_return.pyi | 7 - .../_bindings/aliases/missing_self_arg.pyi | 8 - .../demo/_bindings/classes.pyi | 80 --------- .../demo/_bindings/eigen.pyi | 70 -------- .../demo/_bindings/enum.pyi | 71 -------- .../demo/_bindings/flawed_bindings.pyi | 23 --- .../demo/_bindings/functions.pyi | 86 ---------- .../demo/_bindings/issues.pyi | 43 ----- .../demo/_bindings/methods.pyi | 8 - .../demo/_bindings/numpy.pyi | 23 --- .../demo/_bindings/properties.pyi | 106 ------------ .../demo/_bindings/stl.pyi | 21 --- .../demo/_bindings/stl_bind.pyi | 141 --------------- .../demo/_bindings/typing.pyi | 10 -- .../demo/_bindings/values.pyi | 36 ---- .../numpy-array-use-type-var/demo/core.pyi | 35 ---- .../demo/pure_python/__init__.pyi | 11 -- .../demo/pure_python/classes.pyi | 21 --- .../demo/pure_python/functions.pyi | 31 ---- .../demo/pure_python/functions_3_8_plus.pyi | 30 ---- .../demo/pure_python/functions_3_9_plus.pyi | 5 - .../demo/pure_python/values.pyi | 4 - .../demo/__init__.pyi | 41 ----- .../demo/_bindings/__init__.pyi | 35 ---- .../demo/_bindings/aliases/__init__.pyi | 52 ------ .../demo/_bindings/aliases/foreign_arg.pyi | 7 - .../demo/_bindings/aliases/foreign_attr.pyi | 6 - .../aliases/foreign_class_member.pyi | 12 -- .../_bindings/aliases/foreign_method_arg.pyi | 8 - .../aliases/foreign_method_return.pyi | 9 - .../demo/_bindings/aliases/foreign_return.pyi | 7 - .../_bindings/aliases/missing_self_arg.pyi | 8 - .../demo/_bindings/classes.pyi | 80 --------- .../demo/_bindings/eigen.pyi | 101 ----------- .../demo/_bindings/enum.pyi | 71 -------- .../demo/_bindings/flawed_bindings.pyi | 23 --- .../demo/_bindings/functions.pyi | 82 --------- .../demo/_bindings/issues.pyi | 43 ----- .../demo/_bindings/methods.pyi | 8 - .../demo/_bindings/numpy.pyi | 21 --- .../demo/_bindings/properties.pyi | 106 ------------ .../demo/_bindings/stl.pyi | 17 -- .../demo/_bindings/stl_bind.pyi | 141 --------------- .../demo/_bindings/typing.pyi | 10 -- .../demo/_bindings/values.pyi | 36 ---- .../demo/core.pyi | 35 ---- .../demo/pure_python/__init__.pyi | 11 -- .../demo/pure_python/classes.pyi | 21 --- .../demo/pure_python/functions.pyi | 31 ---- .../demo/pure_python/functions_3_8_plus.pyi | 30 ---- .../demo/pure_python/functions_3_9_plus.pyi | 5 - .../demo/pure_python/values.pyi | 4 - .../demo/__init__.pyi | 41 ----- .../demo/_bindings/__init__.pyi | 35 ---- .../demo/_bindings/aliases/__init__.pyi | 52 ------ .../demo/_bindings/aliases/foreign_arg.pyi | 7 - .../demo/_bindings/aliases/foreign_attr.pyi | 6 - .../aliases/foreign_class_member.pyi | 12 -- .../_bindings/aliases/foreign_method_arg.pyi | 8 - .../aliases/foreign_method_return.pyi | 9 - .../demo/_bindings/aliases/foreign_return.pyi | 7 - .../_bindings/aliases/missing_self_arg.pyi | 8 - .../demo/_bindings/classes.pyi | 94 ---------- .../demo/_bindings/eigen.pyi | 71 -------- .../demo/_bindings/enum.pyi | 73 -------- .../demo/_bindings/flawed_bindings.pyi | 27 --- .../demo/_bindings/functions.pyi | 127 -------------- .../demo/_bindings/issues.pyi | 45 ----- .../demo/_bindings/methods.pyi | 12 -- .../demo/_bindings/numpy.pyi | 24 --- .../demo/_bindings/properties.pyi | 116 ------------- .../demo/_bindings/stl.pyi | 34 ---- .../demo/_bindings/stl_bind.pyi | 162 ------------------ .../demo/_bindings/typing.pyi | 10 -- .../demo/_bindings/values.pyi | 36 ---- .../numpy-array-use-type-var/demo/core.pyi | 35 ---- .../demo/pure_python/__init__.pyi | 11 -- .../demo/pure_python/classes.pyi | 21 --- .../demo/pure_python/functions.pyi | 31 ---- .../demo/pure_python/functions_3_8_plus.pyi | 30 ---- .../demo/pure_python/functions_3_9_plus.pyi | 5 - .../demo/pure_python/values.pyi | 4 - .../demo/_bindings/__init__.pyi | 37 ---- .../demo/_bindings/hidden_builtins.pyi | 6 - 155 files changed, 5436 deletions(-) delete mode 120000 tests/stubs/python-3.10/pybind11-v2.11 delete mode 120000 tests/stubs/python-3.10/pybind11-v2.12 delete mode 120000 tests/stubs/python-3.10/pybind11-v3.0/numpy-array-use-type-var delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/core.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/core.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_attr.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_class_member.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_return.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_return.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/missing_self_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/classes.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/eigen.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/enum.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/flawed_bindings.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/functions.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/issues.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/methods.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/numpy.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/properties.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/stl.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/stl_bind.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/typing.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/values.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/core.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/classes.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions_3_8_plus.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions_3_9_plus.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/values.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/core.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_attr.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_class_member.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_return.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_return.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/missing_self_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/classes.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/eigen.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/enum.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/flawed_bindings.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/functions.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/issues.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/methods.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/numpy.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/properties.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/stl.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/stl_bind.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/typing.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/values.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/core.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/classes.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions_3_8_plus.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions_3_9_plus.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/values.pyi delete mode 100644 tests/stubs/python-3.8/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi delete mode 100644 tests/stubs/python-3.8/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi diff --git a/tests/stubs/python-3.10/pybind11-v2.11 b/tests/stubs/python-3.10/pybind11-v2.11 deleted file mode 120000 index 03f1084f..00000000 --- a/tests/stubs/python-3.10/pybind11-v2.11 +++ /dev/null @@ -1 +0,0 @@ -../python-3.11/pybind11-v2.11 \ No newline at end of file diff --git a/tests/stubs/python-3.10/pybind11-v2.12 b/tests/stubs/python-3.10/pybind11-v2.12 deleted file mode 120000 index 89e32d30..00000000 --- a/tests/stubs/python-3.10/pybind11-v2.12 +++ /dev/null @@ -1 +0,0 @@ -../python-3.11/pybind11-v2.12 \ No newline at end of file diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-use-type-var b/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-use-type-var deleted file mode 120000 index da8c572d..00000000 --- a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-use-type-var +++ /dev/null @@ -1 +0,0 @@ -../../python-3.11/pybind11-v3.0/numpy-array-use-type-var \ No newline at end of file diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/__init__.pyi deleted file mode 100644 index 86fb6b84..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/__init__.pyi +++ /dev/null @@ -1,41 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -from . import _bindings, core, pure_python - -__all__: list[str] = [ - "aliases", - "classes", - "core", - "eigen", - "enum", - "flawed_bindings", - "functions", - "issues", - "methods", - "numpy", - "properties", - "pure_python", - "stl", - "stl_bind", - "typing", - "values", - "version", -] -version: str = "0.0.0" diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi deleted file mode 100644 index c263078f..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -from . import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi deleted file mode 100644 index d8c8d8ac..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.enum -import numpy -from demo._bindings.aliases.foreign_method_arg import Bar2 as foreign_type_alias -from demo._bindings.aliases.foreign_return import get_foo as foreign_class_alias -from numpy import random - -from . import ( - foreign_arg, - foreign_attr, - foreign_class_member, - foreign_method_arg, - foreign_method_return, - foreign_return, - missing_self_arg, -) - -__all__: list[str] = [ - "Color", - "Dummy", - "foreign_arg", - "foreign_attr", - "foreign_class_alias", - "foreign_class_member", - "foreign_enum_default", - "foreign_method_arg", - "foreign_method_return", - "foreign_return", - "foreign_type_alias", - "func", - "local_func_alias", - "local_type_alias", - "missing_self_arg", - "random", -] - -class Dummy: - linalg = numpy.linalg - -class Color: - pass - -def foreign_enum_default( - color: typing.Any = demo._bindings.enum.ConsoleForegroundColor.Blue, -) -> None: ... -def func(arg0: int) -> int: ... - -local_type_alias = Color -local_func_alias = func diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi deleted file mode 100644 index de75e6fb..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["set_foo"] - -def set_foo(arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi deleted file mode 100644 index 10c4a712..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["value"] -value: demo._bindings.classes.Foo # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi deleted file mode 100644 index 497a4e2a..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.classes - -__all__: list[str] = ["Bar1"] - -class Bar1: - foo: typing.ClassVar[ - demo._bindings.classes.Foo - ] # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi deleted file mode 100644 index 8d4ed4ac..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar2"] - -class Bar2: - def set_foo(self, arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi deleted file mode 100644 index 95d218e1..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar3"] - -class Bar3: - @staticmethod - def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi deleted file mode 100644 index 7b5b428f..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["get_foo"] - -def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi deleted file mode 100644 index ee1a09ee..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar4"] - -class Bar4: - def set_foo(self: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi deleted file mode 100644 index 73c6e950..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "CppException", - "Derived", - "Foo", - "MyBase", - "Outer", - "ParIter", - "ParIterBase", - "ParticleContainer", -] - -class Outer: - class Inner: - class NestedEnum: - """ - Members: - - ONE - - TWO - """ - - ONE: typing.ClassVar[Outer.Inner.NestedEnum] # value = - TWO: typing.ClassVar[Outer.Inner.NestedEnum] # value = - __members__: typing.ClassVar[ - dict[str, Outer.Inner.NestedEnum] - ] # value = {'ONE': , 'TWO': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - - value: Outer.Inner.NestedEnum - - inner: Outer.Inner - -class MyBase: - class Inner: - pass - - name: str - -class Derived(MyBase): - count: int - -class Foo: - class FooChild: - def __init__(self) -> None: ... - def g(self) -> None: ... - - def __init__(self) -> None: ... - def f(self) -> None: ... - -class ParIterBase: - level: int - -class ParIter(ParIterBase): - def __init__(self, particle_container: ParticleContainer, level: int) -> None: ... - -class ParticleContainer: - name: str - Iterator = ParIter - def process(self, arg0: ParIter) -> None: ... - -class CppException(Exception): - pass diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi deleted file mode 100644 index 795a711b..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi +++ /dev/null @@ -1,101 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy -import scipy.sparse - -import pybind11_stubgen.typing_ext - -__all__: list[str] = [ - "accept_matrix_int", - "accept_vector_float64", - "dense_matrix_c", - "dense_matrix_r", - "fixed_mutator_a", - "fixed_mutator_c", - "fixed_mutator_r", - "four_col_matrix_r", - "four_row_matrix_r", - "get_matrix_int", - "get_vector_float64", - "sparse_matrix_c", - "sparse_matrix_r", -] - -def accept_matrix_int( - arg0: typing.Annotated[ - numpy.ndarray, numpy.int32, pybind11_stubgen.typing_ext.FixedSize(3, 3) - ], -) -> None: ... -def accept_vector_float64( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float64, pybind11_stubgen.typing_ext.FixedSize(3, 1) - ], -) -> None: ... -def dense_matrix_c( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") -]: ... -def dense_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") -]: ... -def fixed_mutator_a( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - ], -) -> None: ... -def fixed_mutator_c( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - numpy.ndarray.flags.f_contiguous, - ], -) -> None: ... -def fixed_mutator_r( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - numpy.ndarray.flags.c_contiguous, - ], -) -> None: ... -def four_col_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", 4) - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", 4) -]: ... -def four_row_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize(4, "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize(4, "n") -]: ... -def get_matrix_int() -> typing.Annotated[ - numpy.ndarray, numpy.int32, pybind11_stubgen.typing_ext.FixedSize(3, 3) -]: ... -def get_vector_float64() -> typing.Annotated[ - numpy.ndarray, numpy.float64, pybind11_stubgen.typing_ext.FixedSize(3, 1) -]: ... -def sparse_matrix_c( - arg0: typing.Annotated[scipy.sparse.csc_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csc_matrix, numpy.float32]: ... -def sparse_matrix_r( - arg0: typing.Annotated[scipy.sparse.csr_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csr_matrix, numpy.float32]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi deleted file mode 100644 index 57aba5b9..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Blue", - "ConsoleForegroundColor", - "Green", - "Magenta", - "None_", - "Yellow", - "accept_defaulted_enum", -] - -class ConsoleForegroundColor: - """ - Members: - - Green - - Yellow - - Blue - - Magenta - - None_ - """ - - Blue: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Green: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Magenta: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - None_: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Yellow: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - __members__: typing.ClassVar[ - dict[str, ConsoleForegroundColor] - ] # value = {'Green': , 'Yellow': , 'Blue': , 'Magenta': , 'None_': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - -def accept_defaulted_enum( - color: ConsoleForegroundColor = ConsoleForegroundColor.None_, -) -> None: ... - -Blue: ConsoleForegroundColor # value = -Green: ConsoleForegroundColor # value = -Magenta: ConsoleForegroundColor # value = -None_: ConsoleForegroundColor # value = -Yellow: ConsoleForegroundColor # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi deleted file mode 100644 index 96774842..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = [ - "Enum", - "Unbound", - "accept_unbound_enum", - "accept_unbound_enum_defaulted", - "accept_unbound_type", - "accept_unbound_type_defaulted", - "get_unbound_type", -] - -class Unbound: - pass - -class Enum: - pass - -def accept_unbound_enum(arg0: ...) -> int: ... -def accept_unbound_enum_defaulted(x: Enum = ...) -> int: ... -def accept_unbound_type(arg0: tuple[..., int]) -> int: ... -def accept_unbound_type_defaulted(x: Unbound = ...) -> int: ... -def get_unbound_type() -> ...: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi deleted file mode 100644 index 264c8011..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi +++ /dev/null @@ -1,84 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Foo", - "U", - "accept_callable", - "accept_frozenset", - "accept_py_handle", - "accept_py_object", - "accept_set", - "add", - "default_custom_arg", - "default_int_arg", - "default_list_arg", - "default_optional_arg", - "func_w_anon_args", - "func_w_named_pos_args", - "generic", - "mul", - "nested_types", - "pass_callback", - "passthrough1", - "passthrough2", - "passthrough3", - "passthrough_backwards", - "pos_kw_only_mix", - "pos_kw_only_variadic_mix", -] - -class Foo: - def __init__(self, arg0: int) -> None: ... - -def accept_callable(arg0: typing.Callable) -> typing.Any: ... -def accept_frozenset(arg0: frozenset) -> None: ... -def accept_py_handle(arg0: typing.Any) -> str: ... -def accept_py_object(arg0: typing.Any) -> str: ... -def accept_set(arg0: set) -> None: ... -def add(arg0: int, arg1: int) -> int: ... -def default_custom_arg(foo: Foo = Foo(5)) -> None: ... -def default_int_arg(n: int = 5) -> None: ... -def default_list_arg(l: list = [1, 2, 6, 18]) -> None: ... -def default_optional_arg(n: int | None = None) -> None: ... -def func_w_anon_args(arg0: int, arg1: int, arg2: int) -> None: ... -def func_w_named_pos_args(x: int, y: int, z: int) -> None: ... -def generic(*args, **kwargs) -> None: ... -@typing.overload -def mul(x: int, y: int) -> int: - """ - Multiply x and y (int) - """ - -@typing.overload -def mul(p: float, q: float) -> float: - """ - Multiply p and q (double) - """ - -def nested_types(arg0: list[Foo] | Foo) -> list[Foo] | Foo: ... -def pass_callback(arg0: typing.Callable[[Foo], Foo]) -> Foo: ... -def passthrough1(*args, **kwargs): - """ - passthrough1[T](obj: T) -> T - """ - -@typing.overload -def passthrough2() -> None: - """ - 2. passthrough2[T](obj: T) -> T - """ - -@typing.overload -def passthrough3() -> tuple[None, None]: - """ - 2. passthrough3[T](obj: T) -> tuple[T, None] - 3. passthrough3[T1, T2](obj1: T1, obj2: T2) -> tuple[T1, T2] - """ - -def passthrough_backwards(obj: U) -> U: ... -def pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple: ... -def pos_kw_only_variadic_mix(i: int, /, j: int, *args, k: int, **kwargs) -> tuple: ... - -U: typing.TypeVar # value = ~U diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi deleted file mode 100644 index a537a958..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "backslashes_should_be_escaped", - "issue_51_catastrophic_regex", - "issue_73_utf8_doc_chars", - "issue_274_docstring_leading_newline", -] - -def backslashes_should_be_escaped() -> None: - """ - \\brief A brief description of this function. - - A detailed description of this function. - - Here's some reStructuredText: :math:`x = [x, y, \\theta]^T` - """ - -def issue_274_docstring_leading_newline() -> None: - """ - This is a docstring - """ - -def issue_51_catastrophic_regex(arg0: int, arg1: int) -> None: - """ - Use-case: - issue_51(os.get_handle_inheritable, os.set_handle_inheritable) - """ - -def issue_73_utf8_doc_chars() -> None: - """ - Construct a Ramsete unicycle controller. - - Tuning parameter (b > 0 rad²/m²) for which larger values make - - convergence more aggressive like a proportional term. - Tuning parameter (0 rad⁻¹ < zeta < 1 rad⁻¹) for which larger - values provide more damping in response. - """ - -_cleanup: typing.Any # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi deleted file mode 100644 index 6c115cd1..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["Dummy"] - -class Dummy: - @staticmethod - def static_method(arg0: int) -> int: ... - def regular_method(self, arg0: int) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi deleted file mode 100644 index 34943048..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy - -__all__: list[str] = [ - "accept_ndarray_float64", - "accept_ndarray_int", - "get_ndarray_float64", - "get_ndarray_int", - "return_dtype", -] - -def accept_ndarray_float64( - arg0: typing.Annotated[numpy.ndarray, numpy.float64], -) -> None: ... -def accept_ndarray_int(arg0: typing.Annotated[numpy.ndarray, numpy.int32]) -> None: ... -def get_ndarray_float64() -> typing.Annotated[numpy.ndarray, numpy.float64]: ... -def get_ndarray_int() -> typing.Annotated[numpy.ndarray, numpy.int32]: ... -def return_dtype() -> numpy.dtype[typing.Any]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi deleted file mode 100644 index 556f558f..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi +++ /dev/null @@ -1,106 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "WithGetterSetterDoc", - "WithPropAndGetterSetterDoc", - "WithPropDoc", - "WithoutDoc", -] - -class WithoutDoc: - """ - No user docstring provided - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - def_property: int - def_readwrite: int - @property - def def_property_readonly(self) -> int: ... - @property - def def_readonly(self) -> int: ... - -class WithPropDoc: - """ - User docstring provided only to `def_` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - prop doc token - """ - - @def_property.setter - def def_property(self, arg1: int) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ - - @property - def def_readonly(self) -> int: - """ - prop doc token - """ - - @property - def def_readwrite(self) -> int: - """ - prop doc token - """ - - @def_readwrite.setter - def def_readwrite(self, arg0: int) -> None: ... - -class WithGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters, but NOT to `def_*(..., doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - getter doc token - """ - - @def_property.setter - def def_property(self, arg1: int) -> None: - """ - setter doc token - """ - - @property - def def_property_readonly(self) -> int: - """ - getter doc token - """ - -class WithPropAndGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters and to `def_*(, doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - prop doc token - """ - - @def_property.setter - def def_property(self, arg1: int) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi deleted file mode 100644 index e0faec55..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -import typing - -import pybind11_stubgen.typing_ext - -__all__: list[str] = [ - "std_array", - "std_map", - "std_optional", - "std_variant", - "std_vector", -] - -def std_array( - arg0: typing.Annotated[list[int], pybind11_stubgen.typing_ext.FixedSize(3)], -) -> typing.Annotated[list[int], pybind11_stubgen.typing_ext.FixedSize(3)]: ... -def std_map() -> dict[int, complex]: ... -def std_optional(arg0: int | None) -> None: ... -def std_variant(arg0: int | float | tuple[int, int]) -> None: ... -def std_vector() -> list[tuple[int, float]]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi deleted file mode 100644 index 7f70740d..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi +++ /dev/null @@ -1,141 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "MapStringComplex", - "VectorPairStringDouble", - "get_complex_map", - "get_vector_of_pairs", -] - -class VectorPairStringDouble: - __hash__: typing.ClassVar[None] = None - def __bool__(self) -> bool: - """ - Check whether the list is nonempty - """ - - def __contains__(self, x: tuple[str, float]) -> bool: - """ - Return true the container contains ``x`` - """ - - @typing.overload - def __delitem__(self, arg0: int) -> None: - """ - Delete the list elements at index ``i`` - """ - - @typing.overload - def __delitem__(self, arg0: slice) -> None: - """ - Delete list elements using a slice object - """ - - def __eq__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __getitem__(self, s: slice) -> VectorPairStringDouble: - """ - Retrieve list elements using a slice object - """ - - @typing.overload - def __getitem__(self, arg0: int) -> tuple[str, float]: ... - @typing.overload - def __init__(self) -> None: ... - @typing.overload - def __init__(self, arg0: VectorPairStringDouble) -> None: - """ - Copy constructor - """ - - @typing.overload - def __init__(self, arg0: typing.Iterable) -> None: ... - def __iter__(self) -> typing.Iterator: ... - def __len__(self) -> int: ... - def __ne__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __setitem__(self, arg0: int, arg1: tuple[str, float]) -> None: ... - @typing.overload - def __setitem__(self, arg0: slice, arg1: VectorPairStringDouble) -> None: - """ - Assign list elements using a slice object - """ - - def append(self, x: tuple[str, float]) -> None: - """ - Add an item to the end of the list - """ - - def clear(self) -> None: - """ - Clear the contents - """ - - def count(self, x: tuple[str, float]) -> int: - """ - Return the number of times ``x`` appears in the list - """ - - @typing.overload - def extend(self, L: VectorPairStringDouble) -> None: - """ - Extend the list by appending all the items in the given list - """ - - @typing.overload - def extend(self, L: typing.Iterable) -> None: - """ - Extend the list by appending all the items in the given list - """ - - def insert(self, i: int, x: tuple[str, float]) -> None: - """ - Insert an item at a given position. - """ - - @typing.overload - def pop(self) -> tuple[str, float]: - """ - Remove and return the last item - """ - - @typing.overload - def pop(self, i: int) -> tuple[str, float]: - """ - Remove and return the item at index ``i`` - """ - - def remove(self, x: tuple[str, float]) -> None: - """ - Remove the first item from the list whose value is x. It is an error if there is no such item. - """ - -class MapStringComplex: - def __bool__(self) -> bool: - """ - Check whether the map is nonempty - """ - - @typing.overload - def __contains__(self, arg0: str) -> bool: ... - @typing.overload - def __contains__(self, arg0: typing.Any) -> bool: ... - def __delitem__(self, arg0: str) -> None: ... - def __getitem__(self, arg0: str) -> complex: ... - def __init__(self) -> None: ... - def __iter__(self) -> typing.Iterator: ... - def __len__(self) -> int: ... - def __repr__(self) -> str: - """ - Return the canonical string representation of this map. - """ - - def __setitem__(self, arg0: str, arg1: complex) -> None: ... - def items(self) -> typing.ItemsView[str, complex]: ... - def keys(self) -> typing.KeysView[str]: ... - def values(self) -> typing.ValuesView[complex]: ... - -def get_complex_map() -> MapStringComplex: ... -def get_vector_of_pairs() -> VectorPairStringDouble: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi deleted file mode 100644 index f2c4ab95..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import annotations - -import typing - -import typing_extensions - -__all__: list[str] = ["get_buffer", "get_sequence"] - -def get_buffer(arg0: typing_extensions.Buffer) -> typing_extensions.Buffer: ... -def get_sequence(arg0: typing.Sequence) -> typing.Sequence: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi deleted file mode 100644 index 7c36d3fd..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -import datetime - -import numpy -from numpy import random - -__all__: list[str] = [ - "Dummy", - "Foo", - "add_day", - "foolist", - "foovar", - "list_with_none", - "none", - "random", - "t_10ms", - "t_20ns", - "t_30s", -] - -class Dummy: - linalg = numpy.linalg - -class Foo: - pass - -def add_day(arg0: datetime.datetime) -> datetime.datetime: ... - -foolist: list # value = [, ] -foovar: Foo # value = -list_with_none: list = [None, 2, {}] -none = None -t_10ms: datetime.timedelta # value = datetime.timedelta(microseconds=10000) -t_20ns: datetime.timedelta # value = datetime.timedelta(0) -t_30s: datetime.timedelta # value = datetime.timedelta(seconds=30) diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/core.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/core.pyi deleted file mode 100644 index 95c16467..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/core.pyi +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi deleted file mode 100644 index 10d343e0..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from . import classes, functions, functions_3_8_plus, functions_3_9_plus, values - -__all__: list[str] = [ - "classes", - "functions", - "functions_3_8_plus", - "functions_3_9_plus", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi deleted file mode 100644 index 32aa068b..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["A", "B", "C", "X"] - -class A: - """ - A - """ - -class B(A): - """ - B - """ - -class C(B): - """ - C - """ - -class X: - pass diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi deleted file mode 100644 index 5ed9ef3c..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import annotations - -import sys as sys -import typing as typing - -from demo.pure_python.functions_3_8_plus import args_mix -from demo.pure_python.functions_3_9_plus import generic_alias_annotation - -__all__: list[str] = [ - "accept_frozenset", - "args_mix", - "builtin_function_as_default_arg", - "function_as_default_arg", - "generic_alias_annotation", - "lambda_as_default_arg", - "search", - "static_method_as_default_arg", - "sys", - "typing", -] - -class _Dummy: - @staticmethod - def foo(): ... - -def accept_frozenset(arg: frozenset[int | float]) -> int | None: ... -def builtin_function_as_default_arg(func: type(len) = len): ... -def function_as_default_arg(func: type(search) = search): ... -def lambda_as_default_arg(callback=...): ... -def search(a: int, b: list[int]) -> int: ... -def static_method_as_default_arg(callback=_Dummy.foo): ... diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi deleted file mode 100644 index c55aed1d..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import typing as typing - -__all__: list[str] = [ - "Expression", - "Token", - "args_mix", - "nested_current_module_annotations", - "typing", -] - -class Token: - pass - -class Expression: - pass - -def args_mix( - a: int, - b: float = 0.5, - c: str = "", - *args: int, - x: int = 1, - y=int, - **kwargs: dict[int, str], -): ... -def nested_current_module_annotations( - tokens: list[Token], expr: Expression | None = None -) -> dict[str, Expression]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi deleted file mode 100644 index 59b9b80c..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["generic_alias_annotation"] - -def generic_alias_annotation(a: list[tuple[int]], b: dict[int, str]) -> list[float]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi b/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi deleted file mode 100644 index 07913391..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["callables_dict"] -callables_dict: dict = {"len": len, "int": int} diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/__init__.pyi deleted file mode 100644 index 86fb6b84..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/__init__.pyi +++ /dev/null @@ -1,41 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -from . import _bindings, core, pure_python - -__all__: list[str] = [ - "aliases", - "classes", - "core", - "eigen", - "enum", - "flawed_bindings", - "functions", - "issues", - "methods", - "numpy", - "properties", - "pure_python", - "stl", - "stl_bind", - "typing", - "values", - "version", -] -version: str = "0.0.0" diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi deleted file mode 100644 index c263078f..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -from . import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi deleted file mode 100644 index d8c8d8ac..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.enum -import numpy -from demo._bindings.aliases.foreign_method_arg import Bar2 as foreign_type_alias -from demo._bindings.aliases.foreign_return import get_foo as foreign_class_alias -from numpy import random - -from . import ( - foreign_arg, - foreign_attr, - foreign_class_member, - foreign_method_arg, - foreign_method_return, - foreign_return, - missing_self_arg, -) - -__all__: list[str] = [ - "Color", - "Dummy", - "foreign_arg", - "foreign_attr", - "foreign_class_alias", - "foreign_class_member", - "foreign_enum_default", - "foreign_method_arg", - "foreign_method_return", - "foreign_return", - "foreign_type_alias", - "func", - "local_func_alias", - "local_type_alias", - "missing_self_arg", - "random", -] - -class Dummy: - linalg = numpy.linalg - -class Color: - pass - -def foreign_enum_default( - color: typing.Any = demo._bindings.enum.ConsoleForegroundColor.Blue, -) -> None: ... -def func(arg0: int) -> int: ... - -local_type_alias = Color -local_func_alias = func diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi deleted file mode 100644 index de75e6fb..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["set_foo"] - -def set_foo(arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi deleted file mode 100644 index 10c4a712..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["value"] -value: demo._bindings.classes.Foo # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi deleted file mode 100644 index 497a4e2a..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.classes - -__all__: list[str] = ["Bar1"] - -class Bar1: - foo: typing.ClassVar[ - demo._bindings.classes.Foo - ] # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi deleted file mode 100644 index 8d4ed4ac..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar2"] - -class Bar2: - def set_foo(self, arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi deleted file mode 100644 index 95d218e1..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar3"] - -class Bar3: - @staticmethod - def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi deleted file mode 100644 index 7b5b428f..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["get_foo"] - -def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi deleted file mode 100644 index ee1a09ee..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar4"] - -class Bar4: - def set_foo(self: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi deleted file mode 100644 index 73c6e950..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "CppException", - "Derived", - "Foo", - "MyBase", - "Outer", - "ParIter", - "ParIterBase", - "ParticleContainer", -] - -class Outer: - class Inner: - class NestedEnum: - """ - Members: - - ONE - - TWO - """ - - ONE: typing.ClassVar[Outer.Inner.NestedEnum] # value = - TWO: typing.ClassVar[Outer.Inner.NestedEnum] # value = - __members__: typing.ClassVar[ - dict[str, Outer.Inner.NestedEnum] - ] # value = {'ONE': , 'TWO': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - - value: Outer.Inner.NestedEnum - - inner: Outer.Inner - -class MyBase: - class Inner: - pass - - name: str - -class Derived(MyBase): - count: int - -class Foo: - class FooChild: - def __init__(self) -> None: ... - def g(self) -> None: ... - - def __init__(self) -> None: ... - def f(self) -> None: ... - -class ParIterBase: - level: int - -class ParIter(ParIterBase): - def __init__(self, particle_container: ParticleContainer, level: int) -> None: ... - -class ParticleContainer: - name: str - Iterator = ParIter - def process(self, arg0: ParIter) -> None: ... - -class CppException(Exception): - pass diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi deleted file mode 100644 index 795a711b..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi +++ /dev/null @@ -1,101 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy -import scipy.sparse - -import pybind11_stubgen.typing_ext - -__all__: list[str] = [ - "accept_matrix_int", - "accept_vector_float64", - "dense_matrix_c", - "dense_matrix_r", - "fixed_mutator_a", - "fixed_mutator_c", - "fixed_mutator_r", - "four_col_matrix_r", - "four_row_matrix_r", - "get_matrix_int", - "get_vector_float64", - "sparse_matrix_c", - "sparse_matrix_r", -] - -def accept_matrix_int( - arg0: typing.Annotated[ - numpy.ndarray, numpy.int32, pybind11_stubgen.typing_ext.FixedSize(3, 3) - ], -) -> None: ... -def accept_vector_float64( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float64, pybind11_stubgen.typing_ext.FixedSize(3, 1) - ], -) -> None: ... -def dense_matrix_c( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") -]: ... -def dense_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") -]: ... -def fixed_mutator_a( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - ], -) -> None: ... -def fixed_mutator_c( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - numpy.ndarray.flags.f_contiguous, - ], -) -> None: ... -def fixed_mutator_r( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - numpy.ndarray.flags.c_contiguous, - ], -) -> None: ... -def four_col_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", 4) - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", 4) -]: ... -def four_row_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize(4, "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize(4, "n") -]: ... -def get_matrix_int() -> typing.Annotated[ - numpy.ndarray, numpy.int32, pybind11_stubgen.typing_ext.FixedSize(3, 3) -]: ... -def get_vector_float64() -> typing.Annotated[ - numpy.ndarray, numpy.float64, pybind11_stubgen.typing_ext.FixedSize(3, 1) -]: ... -def sparse_matrix_c( - arg0: typing.Annotated[scipy.sparse.csc_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csc_matrix, numpy.float32]: ... -def sparse_matrix_r( - arg0: typing.Annotated[scipy.sparse.csr_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csr_matrix, numpy.float32]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi deleted file mode 100644 index 57aba5b9..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Blue", - "ConsoleForegroundColor", - "Green", - "Magenta", - "None_", - "Yellow", - "accept_defaulted_enum", -] - -class ConsoleForegroundColor: - """ - Members: - - Green - - Yellow - - Blue - - Magenta - - None_ - """ - - Blue: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Green: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Magenta: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - None_: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Yellow: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - __members__: typing.ClassVar[ - dict[str, ConsoleForegroundColor] - ] # value = {'Green': , 'Yellow': , 'Blue': , 'Magenta': , 'None_': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - -def accept_defaulted_enum( - color: ConsoleForegroundColor = ConsoleForegroundColor.None_, -) -> None: ... - -Blue: ConsoleForegroundColor # value = -Green: ConsoleForegroundColor # value = -Magenta: ConsoleForegroundColor # value = -None_: ConsoleForegroundColor # value = -Yellow: ConsoleForegroundColor # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi deleted file mode 100644 index 96774842..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = [ - "Enum", - "Unbound", - "accept_unbound_enum", - "accept_unbound_enum_defaulted", - "accept_unbound_type", - "accept_unbound_type_defaulted", - "get_unbound_type", -] - -class Unbound: - pass - -class Enum: - pass - -def accept_unbound_enum(arg0: ...) -> int: ... -def accept_unbound_enum_defaulted(x: Enum = ...) -> int: ... -def accept_unbound_type(arg0: tuple[..., int]) -> int: ... -def accept_unbound_type_defaulted(x: Unbound = ...) -> int: ... -def get_unbound_type() -> ...: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi deleted file mode 100644 index df1fc8f1..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi +++ /dev/null @@ -1,86 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Foo", - "U", - "accept_annotated_callable", - "accept_callable", - "accept_frozenset", - "accept_py_handle", - "accept_py_object", - "accept_set", - "add", - "default_custom_arg", - "default_int_arg", - "default_list_arg", - "default_optional_arg", - "func_w_anon_args", - "func_w_named_pos_args", - "generic", - "mul", - "nested_types", - "pass_callback", - "passthrough1", - "passthrough2", - "passthrough3", - "passthrough_backwards", - "pos_kw_only_mix", - "pos_kw_only_variadic_mix", -] - -class Foo: - def __init__(self, arg0: int) -> None: ... - -def accept_annotated_callable(arg0: typing.Callable[[int, int], int]) -> typing.Any: ... -def accept_callable(arg0: typing.Callable) -> typing.Any: ... -def accept_frozenset(arg0: frozenset) -> None: ... -def accept_py_handle(arg0: typing.Any) -> str: ... -def accept_py_object(arg0: typing.Any) -> str: ... -def accept_set(arg0: set) -> None: ... -def add(arg0: int, arg1: int) -> int: ... -def default_custom_arg(foo: Foo = Foo(5)) -> None: ... -def default_int_arg(n: int = 5) -> None: ... -def default_list_arg(l: list = [1, 2, 6, 18]) -> None: ... -def default_optional_arg(n: int | None = None) -> None: ... -def func_w_anon_args(arg0: int, arg1: int, arg2: int) -> None: ... -def func_w_named_pos_args(x: int, y: int, z: int) -> None: ... -def generic(*args, **kwargs) -> None: ... -@typing.overload -def mul(x: int, y: int) -> int: - """ - Multiply x and y (int) - """ - -@typing.overload -def mul(p: float, q: float) -> float: - """ - Multiply p and q (double) - """ - -def nested_types(arg0: list[Foo] | Foo) -> list[Foo] | Foo: ... -def pass_callback(arg0: typing.Callable[[Foo], Foo]) -> Foo: ... -def passthrough1(*args, **kwargs): - """ - passthrough1[T](obj: T) -> T - """ - -@typing.overload -def passthrough2() -> None: - """ - 2. passthrough2[T](obj: T) -> T - """ - -@typing.overload -def passthrough3() -> tuple[None, None]: - """ - 2. passthrough3[T](obj: T) -> tuple[T, None] - 3. passthrough3[T1, T2](obj1: T1, obj2: T2) -> tuple[T1, T2] - """ - -def passthrough_backwards(obj: U) -> U: ... -def pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple: ... -def pos_kw_only_variadic_mix(i: int, /, j: int, *args, k: int, **kwargs) -> tuple: ... - -U: typing.TypeVar # value = ~U diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi deleted file mode 100644 index a537a958..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "backslashes_should_be_escaped", - "issue_51_catastrophic_regex", - "issue_73_utf8_doc_chars", - "issue_274_docstring_leading_newline", -] - -def backslashes_should_be_escaped() -> None: - """ - \\brief A brief description of this function. - - A detailed description of this function. - - Here's some reStructuredText: :math:`x = [x, y, \\theta]^T` - """ - -def issue_274_docstring_leading_newline() -> None: - """ - This is a docstring - """ - -def issue_51_catastrophic_regex(arg0: int, arg1: int) -> None: - """ - Use-case: - issue_51(os.get_handle_inheritable, os.set_handle_inheritable) - """ - -def issue_73_utf8_doc_chars() -> None: - """ - Construct a Ramsete unicycle controller. - - Tuning parameter (b > 0 rad²/m²) for which larger values make - - convergence more aggressive like a proportional term. - Tuning parameter (0 rad⁻¹ < zeta < 1 rad⁻¹) for which larger - values provide more damping in response. - """ - -_cleanup: typing.Any # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi deleted file mode 100644 index 6c115cd1..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["Dummy"] - -class Dummy: - @staticmethod - def static_method(arg0: int) -> int: ... - def regular_method(self, arg0: int) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi deleted file mode 100644 index 34943048..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy - -__all__: list[str] = [ - "accept_ndarray_float64", - "accept_ndarray_int", - "get_ndarray_float64", - "get_ndarray_int", - "return_dtype", -] - -def accept_ndarray_float64( - arg0: typing.Annotated[numpy.ndarray, numpy.float64], -) -> None: ... -def accept_ndarray_int(arg0: typing.Annotated[numpy.ndarray, numpy.int32]) -> None: ... -def get_ndarray_float64() -> typing.Annotated[numpy.ndarray, numpy.float64]: ... -def get_ndarray_int() -> typing.Annotated[numpy.ndarray, numpy.int32]: ... -def return_dtype() -> numpy.dtype[typing.Any]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi deleted file mode 100644 index 556f558f..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi +++ /dev/null @@ -1,106 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "WithGetterSetterDoc", - "WithPropAndGetterSetterDoc", - "WithPropDoc", - "WithoutDoc", -] - -class WithoutDoc: - """ - No user docstring provided - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - def_property: int - def_readwrite: int - @property - def def_property_readonly(self) -> int: ... - @property - def def_readonly(self) -> int: ... - -class WithPropDoc: - """ - User docstring provided only to `def_` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - prop doc token - """ - - @def_property.setter - def def_property(self, arg1: int) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ - - @property - def def_readonly(self) -> int: - """ - prop doc token - """ - - @property - def def_readwrite(self) -> int: - """ - prop doc token - """ - - @def_readwrite.setter - def def_readwrite(self, arg0: int) -> None: ... - -class WithGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters, but NOT to `def_*(..., doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - getter doc token - """ - - @def_property.setter - def def_property(self, arg1: int) -> None: - """ - setter doc token - """ - - @property - def def_property_readonly(self) -> int: - """ - getter doc token - """ - -class WithPropAndGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters and to `def_*(, doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - prop doc token - """ - - @def_property.setter - def def_property(self, arg1: int) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi deleted file mode 100644 index e0faec55..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -import typing - -import pybind11_stubgen.typing_ext - -__all__: list[str] = [ - "std_array", - "std_map", - "std_optional", - "std_variant", - "std_vector", -] - -def std_array( - arg0: typing.Annotated[list[int], pybind11_stubgen.typing_ext.FixedSize(3)], -) -> typing.Annotated[list[int], pybind11_stubgen.typing_ext.FixedSize(3)]: ... -def std_map() -> dict[int, complex]: ... -def std_optional(arg0: int | None) -> None: ... -def std_variant(arg0: int | float | tuple[int, int]) -> None: ... -def std_vector() -> list[tuple[int, float]]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi deleted file mode 100644 index 851d1a67..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi +++ /dev/null @@ -1,141 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "MapStringComplex", - "VectorPairStringDouble", - "get_complex_map", - "get_vector_of_pairs", -] - -class VectorPairStringDouble: - __hash__: typing.ClassVar[None] = None - def __bool__(self) -> bool: - """ - Check whether the list is nonempty - """ - - def __contains__(self, x: tuple[str, float]) -> bool: - """ - Return true the container contains ``x`` - """ - - @typing.overload - def __delitem__(self, arg0: int) -> None: - """ - Delete the list elements at index ``i`` - """ - - @typing.overload - def __delitem__(self, arg0: slice) -> None: - """ - Delete list elements using a slice object - """ - - def __eq__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __getitem__(self, s: slice) -> VectorPairStringDouble: - """ - Retrieve list elements using a slice object - """ - - @typing.overload - def __getitem__(self, arg0: int) -> tuple[str, float]: ... - @typing.overload - def __init__(self) -> None: ... - @typing.overload - def __init__(self, arg0: VectorPairStringDouble) -> None: - """ - Copy constructor - """ - - @typing.overload - def __init__(self, arg0: typing.Iterable) -> None: ... - def __iter__(self) -> typing.Iterator[tuple[str, float]]: ... - def __len__(self) -> int: ... - def __ne__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __setitem__(self, arg0: int, arg1: tuple[str, float]) -> None: ... - @typing.overload - def __setitem__(self, arg0: slice, arg1: VectorPairStringDouble) -> None: - """ - Assign list elements using a slice object - """ - - def append(self, x: tuple[str, float]) -> None: - """ - Add an item to the end of the list - """ - - def clear(self) -> None: - """ - Clear the contents - """ - - def count(self, x: tuple[str, float]) -> int: - """ - Return the number of times ``x`` appears in the list - """ - - @typing.overload - def extend(self, L: VectorPairStringDouble) -> None: - """ - Extend the list by appending all the items in the given list - """ - - @typing.overload - def extend(self, L: typing.Iterable) -> None: - """ - Extend the list by appending all the items in the given list - """ - - def insert(self, i: int, x: tuple[str, float]) -> None: - """ - Insert an item at a given position. - """ - - @typing.overload - def pop(self) -> tuple[str, float]: - """ - Remove and return the last item - """ - - @typing.overload - def pop(self, i: int) -> tuple[str, float]: - """ - Remove and return the item at index ``i`` - """ - - def remove(self, x: tuple[str, float]) -> None: - """ - Remove the first item from the list whose value is x. It is an error if there is no such item. - """ - -class MapStringComplex: - def __bool__(self) -> bool: - """ - Check whether the map is nonempty - """ - - @typing.overload - def __contains__(self, arg0: str) -> bool: ... - @typing.overload - def __contains__(self, arg0: typing.Any) -> bool: ... - def __delitem__(self, arg0: str) -> None: ... - def __getitem__(self, arg0: str) -> complex: ... - def __init__(self) -> None: ... - def __iter__(self) -> typing.Iterator[str]: ... - def __len__(self) -> int: ... - def __repr__(self) -> str: - """ - Return the canonical string representation of this map. - """ - - def __setitem__(self, arg0: str, arg1: complex) -> None: ... - def items(self) -> typing.ItemsView: ... - def keys(self) -> typing.KeysView: ... - def values(self) -> typing.ValuesView: ... - -def get_complex_map() -> MapStringComplex: ... -def get_vector_of_pairs() -> VectorPairStringDouble: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi deleted file mode 100644 index f2c4ab95..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import annotations - -import typing - -import typing_extensions - -__all__: list[str] = ["get_buffer", "get_sequence"] - -def get_buffer(arg0: typing_extensions.Buffer) -> typing_extensions.Buffer: ... -def get_sequence(arg0: typing.Sequence) -> typing.Sequence: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi deleted file mode 100644 index 7c36d3fd..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -import datetime - -import numpy -from numpy import random - -__all__: list[str] = [ - "Dummy", - "Foo", - "add_day", - "foolist", - "foovar", - "list_with_none", - "none", - "random", - "t_10ms", - "t_20ns", - "t_30s", -] - -class Dummy: - linalg = numpy.linalg - -class Foo: - pass - -def add_day(arg0: datetime.datetime) -> datetime.datetime: ... - -foolist: list # value = [, ] -foovar: Foo # value = -list_with_none: list = [None, 2, {}] -none = None -t_10ms: datetime.timedelta # value = datetime.timedelta(microseconds=10000) -t_20ns: datetime.timedelta # value = datetime.timedelta(0) -t_30s: datetime.timedelta # value = datetime.timedelta(seconds=30) diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/core.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/core.pyi deleted file mode 100644 index 95c16467..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/core.pyi +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi deleted file mode 100644 index 10d343e0..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from . import classes, functions, functions_3_8_plus, functions_3_9_plus, values - -__all__: list[str] = [ - "classes", - "functions", - "functions_3_8_plus", - "functions_3_9_plus", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi deleted file mode 100644 index 32aa068b..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["A", "B", "C", "X"] - -class A: - """ - A - """ - -class B(A): - """ - B - """ - -class C(B): - """ - C - """ - -class X: - pass diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi deleted file mode 100644 index 5ed9ef3c..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import annotations - -import sys as sys -import typing as typing - -from demo.pure_python.functions_3_8_plus import args_mix -from demo.pure_python.functions_3_9_plus import generic_alias_annotation - -__all__: list[str] = [ - "accept_frozenset", - "args_mix", - "builtin_function_as_default_arg", - "function_as_default_arg", - "generic_alias_annotation", - "lambda_as_default_arg", - "search", - "static_method_as_default_arg", - "sys", - "typing", -] - -class _Dummy: - @staticmethod - def foo(): ... - -def accept_frozenset(arg: frozenset[int | float]) -> int | None: ... -def builtin_function_as_default_arg(func: type(len) = len): ... -def function_as_default_arg(func: type(search) = search): ... -def lambda_as_default_arg(callback=...): ... -def search(a: int, b: list[int]) -> int: ... -def static_method_as_default_arg(callback=_Dummy.foo): ... diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi deleted file mode 100644 index c55aed1d..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import typing as typing - -__all__: list[str] = [ - "Expression", - "Token", - "args_mix", - "nested_current_module_annotations", - "typing", -] - -class Token: - pass - -class Expression: - pass - -def args_mix( - a: int, - b: float = 0.5, - c: str = "", - *args: int, - x: int = 1, - y=int, - **kwargs: dict[int, str], -): ... -def nested_current_module_annotations( - tokens: list[Token], expr: Expression | None = None -) -> dict[str, Expression]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi deleted file mode 100644 index 59b9b80c..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["generic_alias_annotation"] - -def generic_alias_annotation(a: list[tuple[int]], b: dict[int, str]) -> list[float]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi b/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi deleted file mode 100644 index 07913391..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["callables_dict"] -callables_dict: dict = {"len": len, "int": int} diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/__init__.pyi deleted file mode 100644 index 86fb6b84..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/__init__.pyi +++ /dev/null @@ -1,41 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -from . import _bindings, core, pure_python - -__all__: list[str] = [ - "aliases", - "classes", - "core", - "eigen", - "enum", - "flawed_bindings", - "functions", - "issues", - "methods", - "numpy", - "properties", - "pure_python", - "stl", - "stl_bind", - "typing", - "values", - "version", -] -version: str = "0.0.0" diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/__init__.pyi deleted file mode 100644 index c263078f..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/__init__.pyi +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -from . import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/__init__.pyi deleted file mode 100644 index d8c8d8ac..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/__init__.pyi +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.enum -import numpy -from demo._bindings.aliases.foreign_method_arg import Bar2 as foreign_type_alias -from demo._bindings.aliases.foreign_return import get_foo as foreign_class_alias -from numpy import random - -from . import ( - foreign_arg, - foreign_attr, - foreign_class_member, - foreign_method_arg, - foreign_method_return, - foreign_return, - missing_self_arg, -) - -__all__: list[str] = [ - "Color", - "Dummy", - "foreign_arg", - "foreign_attr", - "foreign_class_alias", - "foreign_class_member", - "foreign_enum_default", - "foreign_method_arg", - "foreign_method_return", - "foreign_return", - "foreign_type_alias", - "func", - "local_func_alias", - "local_type_alias", - "missing_self_arg", - "random", -] - -class Dummy: - linalg = numpy.linalg - -class Color: - pass - -def foreign_enum_default( - color: typing.Any = demo._bindings.enum.ConsoleForegroundColor.Blue, -) -> None: ... -def func(arg0: int) -> int: ... - -local_type_alias = Color -local_func_alias = func diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_arg.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_arg.pyi deleted file mode 100644 index de75e6fb..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_arg.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["set_foo"] - -def set_foo(arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_attr.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_attr.pyi deleted file mode 100644 index 10c4a712..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_attr.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["value"] -value: demo._bindings.classes.Foo # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_class_member.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_class_member.pyi deleted file mode 100644 index 497a4e2a..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_class_member.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.classes - -__all__: list[str] = ["Bar1"] - -class Bar1: - foo: typing.ClassVar[ - demo._bindings.classes.Foo - ] # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_arg.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_arg.pyi deleted file mode 100644 index 8d4ed4ac..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar2"] - -class Bar2: - def set_foo(self, arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_return.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_return.pyi deleted file mode 100644 index 95d218e1..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_return.pyi +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar3"] - -class Bar3: - @staticmethod - def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_return.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_return.pyi deleted file mode 100644 index 7b5b428f..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_return.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["get_foo"] - -def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/missing_self_arg.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/missing_self_arg.pyi deleted file mode 100644 index ee1a09ee..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/missing_self_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar4"] - -class Bar4: - def set_foo(self: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/classes.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/classes.pyi deleted file mode 100644 index 73c6e950..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/classes.pyi +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "CppException", - "Derived", - "Foo", - "MyBase", - "Outer", - "ParIter", - "ParIterBase", - "ParticleContainer", -] - -class Outer: - class Inner: - class NestedEnum: - """ - Members: - - ONE - - TWO - """ - - ONE: typing.ClassVar[Outer.Inner.NestedEnum] # value = - TWO: typing.ClassVar[Outer.Inner.NestedEnum] # value = - __members__: typing.ClassVar[ - dict[str, Outer.Inner.NestedEnum] - ] # value = {'ONE': , 'TWO': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - - value: Outer.Inner.NestedEnum - - inner: Outer.Inner - -class MyBase: - class Inner: - pass - - name: str - -class Derived(MyBase): - count: int - -class Foo: - class FooChild: - def __init__(self) -> None: ... - def g(self) -> None: ... - - def __init__(self) -> None: ... - def f(self) -> None: ... - -class ParIterBase: - level: int - -class ParIter(ParIterBase): - def __init__(self, particle_container: ParticleContainer, level: int) -> None: ... - -class ParticleContainer: - name: str - Iterator = ParIter - def process(self, arg0: ParIter) -> None: ... - -class CppException(Exception): - pass diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/eigen.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/eigen.pyi deleted file mode 100644 index 0bb75933..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/eigen.pyi +++ /dev/null @@ -1,70 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy -import scipy.sparse - -__all__: list[str] = [ - "accept_matrix_int", - "accept_vector_float64", - "dense_matrix_c", - "dense_matrix_r", - "fixed_mutator_a", - "fixed_mutator_c", - "fixed_mutator_r", - "four_col_matrix_r", - "four_row_matrix_r", - "get_matrix_int", - "get_vector_float64", - "sparse_matrix_c", - "sparse_matrix_r", -] -M = typing.TypeVar("M", bound=int) -N = typing.TypeVar("N", bound=int) - -def accept_matrix_int( - arg0: numpy.ndarray[ - tuple[typing.Literal[3], typing.Literal[3]], numpy.dtype[numpy.int32] - ], -) -> None: ... -def accept_vector_float64( - arg0: numpy.ndarray[ - tuple[typing.Literal[3], typing.Literal[1]], numpy.dtype[numpy.float64] - ], -) -> None: ... -def dense_matrix_c( - arg0: numpy.ndarray[tuple[M, N], numpy.dtype[numpy.float32]], -) -> numpy.ndarray[tuple[M, N], numpy.dtype[numpy.float32]]: ... -def dense_matrix_r( - arg0: numpy.ndarray[tuple[M, N], numpy.dtype[numpy.float32]], -) -> numpy.ndarray[tuple[M, N], numpy.dtype[numpy.float32]]: ... -def fixed_mutator_a( - arg0: numpy.ndarray[ - tuple[typing.Literal[5], typing.Literal[6]], numpy.dtype[numpy.float32] - ], -) -> None: ... -def fixed_mutator_c( - arg0: numpy.ndarray[ - tuple[typing.Literal[5], typing.Literal[6]], numpy.dtype[numpy.float32] - ], -) -> None: ... -def fixed_mutator_r( - arg0: numpy.ndarray[ - tuple[typing.Literal[5], typing.Literal[6]], numpy.dtype[numpy.float32] - ], -) -> None: ... -def four_col_matrix_r( - arg0: numpy.ndarray[tuple[M, typing.Literal[4]], numpy.dtype[numpy.float32]], -) -> numpy.ndarray[tuple[M, typing.Literal[4]], numpy.dtype[numpy.float32]]: ... -def four_row_matrix_r( - arg0: numpy.ndarray[tuple[typing.Literal[4], N], numpy.dtype[numpy.float32]], -) -> numpy.ndarray[tuple[typing.Literal[4], N], numpy.dtype[numpy.float32]]: ... -def get_matrix_int() -> numpy.ndarray[ - tuple[typing.Literal[3], typing.Literal[3]], numpy.dtype[numpy.int32] -]: ... -def get_vector_float64() -> numpy.ndarray[ - tuple[typing.Literal[3], typing.Literal[1]], numpy.dtype[numpy.float64] -]: ... -def sparse_matrix_c(arg0: scipy.sparse.csc_matrix) -> scipy.sparse.csc_matrix: ... -def sparse_matrix_r(arg0: scipy.sparse.csr_matrix) -> scipy.sparse.csr_matrix: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/enum.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/enum.pyi deleted file mode 100644 index 57aba5b9..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/enum.pyi +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Blue", - "ConsoleForegroundColor", - "Green", - "Magenta", - "None_", - "Yellow", - "accept_defaulted_enum", -] - -class ConsoleForegroundColor: - """ - Members: - - Green - - Yellow - - Blue - - Magenta - - None_ - """ - - Blue: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Green: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Magenta: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - None_: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Yellow: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - __members__: typing.ClassVar[ - dict[str, ConsoleForegroundColor] - ] # value = {'Green': , 'Yellow': , 'Blue': , 'Magenta': , 'None_': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - -def accept_defaulted_enum( - color: ConsoleForegroundColor = ConsoleForegroundColor.None_, -) -> None: ... - -Blue: ConsoleForegroundColor # value = -Green: ConsoleForegroundColor # value = -Magenta: ConsoleForegroundColor # value = -None_: ConsoleForegroundColor # value = -Yellow: ConsoleForegroundColor # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/flawed_bindings.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/flawed_bindings.pyi deleted file mode 100644 index 96774842..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/flawed_bindings.pyi +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = [ - "Enum", - "Unbound", - "accept_unbound_enum", - "accept_unbound_enum_defaulted", - "accept_unbound_type", - "accept_unbound_type_defaulted", - "get_unbound_type", -] - -class Unbound: - pass - -class Enum: - pass - -def accept_unbound_enum(arg0: ...) -> int: ... -def accept_unbound_enum_defaulted(x: Enum = ...) -> int: ... -def accept_unbound_type(arg0: tuple[..., int]) -> int: ... -def accept_unbound_type_defaulted(x: Unbound = ...) -> int: ... -def get_unbound_type() -> ...: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/functions.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/functions.pyi deleted file mode 100644 index df1fc8f1..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/functions.pyi +++ /dev/null @@ -1,86 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Foo", - "U", - "accept_annotated_callable", - "accept_callable", - "accept_frozenset", - "accept_py_handle", - "accept_py_object", - "accept_set", - "add", - "default_custom_arg", - "default_int_arg", - "default_list_arg", - "default_optional_arg", - "func_w_anon_args", - "func_w_named_pos_args", - "generic", - "mul", - "nested_types", - "pass_callback", - "passthrough1", - "passthrough2", - "passthrough3", - "passthrough_backwards", - "pos_kw_only_mix", - "pos_kw_only_variadic_mix", -] - -class Foo: - def __init__(self, arg0: int) -> None: ... - -def accept_annotated_callable(arg0: typing.Callable[[int, int], int]) -> typing.Any: ... -def accept_callable(arg0: typing.Callable) -> typing.Any: ... -def accept_frozenset(arg0: frozenset) -> None: ... -def accept_py_handle(arg0: typing.Any) -> str: ... -def accept_py_object(arg0: typing.Any) -> str: ... -def accept_set(arg0: set) -> None: ... -def add(arg0: int, arg1: int) -> int: ... -def default_custom_arg(foo: Foo = Foo(5)) -> None: ... -def default_int_arg(n: int = 5) -> None: ... -def default_list_arg(l: list = [1, 2, 6, 18]) -> None: ... -def default_optional_arg(n: int | None = None) -> None: ... -def func_w_anon_args(arg0: int, arg1: int, arg2: int) -> None: ... -def func_w_named_pos_args(x: int, y: int, z: int) -> None: ... -def generic(*args, **kwargs) -> None: ... -@typing.overload -def mul(x: int, y: int) -> int: - """ - Multiply x and y (int) - """ - -@typing.overload -def mul(p: float, q: float) -> float: - """ - Multiply p and q (double) - """ - -def nested_types(arg0: list[Foo] | Foo) -> list[Foo] | Foo: ... -def pass_callback(arg0: typing.Callable[[Foo], Foo]) -> Foo: ... -def passthrough1(*args, **kwargs): - """ - passthrough1[T](obj: T) -> T - """ - -@typing.overload -def passthrough2() -> None: - """ - 2. passthrough2[T](obj: T) -> T - """ - -@typing.overload -def passthrough3() -> tuple[None, None]: - """ - 2. passthrough3[T](obj: T) -> tuple[T, None] - 3. passthrough3[T1, T2](obj1: T1, obj2: T2) -> tuple[T1, T2] - """ - -def passthrough_backwards(obj: U) -> U: ... -def pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple: ... -def pos_kw_only_variadic_mix(i: int, /, j: int, *args, k: int, **kwargs) -> tuple: ... - -U: typing.TypeVar # value = ~U diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/issues.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/issues.pyi deleted file mode 100644 index a537a958..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/issues.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "backslashes_should_be_escaped", - "issue_51_catastrophic_regex", - "issue_73_utf8_doc_chars", - "issue_274_docstring_leading_newline", -] - -def backslashes_should_be_escaped() -> None: - """ - \\brief A brief description of this function. - - A detailed description of this function. - - Here's some reStructuredText: :math:`x = [x, y, \\theta]^T` - """ - -def issue_274_docstring_leading_newline() -> None: - """ - This is a docstring - """ - -def issue_51_catastrophic_regex(arg0: int, arg1: int) -> None: - """ - Use-case: - issue_51(os.get_handle_inheritable, os.set_handle_inheritable) - """ - -def issue_73_utf8_doc_chars() -> None: - """ - Construct a Ramsete unicycle controller. - - Tuning parameter (b > 0 rad²/m²) for which larger values make - - convergence more aggressive like a proportional term. - Tuning parameter (0 rad⁻¹ < zeta < 1 rad⁻¹) for which larger - values provide more damping in response. - """ - -_cleanup: typing.Any # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/methods.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/methods.pyi deleted file mode 100644 index 6c115cd1..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/methods.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["Dummy"] - -class Dummy: - @staticmethod - def static_method(arg0: int) -> int: ... - def regular_method(self, arg0: int) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/numpy.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/numpy.pyi deleted file mode 100644 index 19245c5d..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/numpy.pyi +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy - -__all__: list[str] = [ - "accept_ndarray_float64", - "accept_ndarray_int", - "get_ndarray_float64", - "get_ndarray_int", - "return_dtype", -] - -def accept_ndarray_float64( - arg0: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]], -) -> None: ... -def accept_ndarray_int( - arg0: numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]], -) -> None: ... -def get_ndarray_float64() -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: ... -def get_ndarray_int() -> numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]]: ... -def return_dtype() -> numpy.dtype[typing.Any]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/properties.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/properties.pyi deleted file mode 100644 index 556f558f..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/properties.pyi +++ /dev/null @@ -1,106 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "WithGetterSetterDoc", - "WithPropAndGetterSetterDoc", - "WithPropDoc", - "WithoutDoc", -] - -class WithoutDoc: - """ - No user docstring provided - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - def_property: int - def_readwrite: int - @property - def def_property_readonly(self) -> int: ... - @property - def def_readonly(self) -> int: ... - -class WithPropDoc: - """ - User docstring provided only to `def_` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - prop doc token - """ - - @def_property.setter - def def_property(self, arg1: int) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ - - @property - def def_readonly(self) -> int: - """ - prop doc token - """ - - @property - def def_readwrite(self) -> int: - """ - prop doc token - """ - - @def_readwrite.setter - def def_readwrite(self, arg0: int) -> None: ... - -class WithGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters, but NOT to `def_*(..., doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - getter doc token - """ - - @def_property.setter - def def_property(self, arg1: int) -> None: - """ - setter doc token - """ - - @property - def def_property_readonly(self) -> int: - """ - getter doc token - """ - -class WithPropAndGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters and to `def_*(, doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - prop doc token - """ - - @def_property.setter - def def_property(self, arg1: int) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/stl.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/stl.pyi deleted file mode 100644 index e0faec55..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/stl.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -import typing - -import pybind11_stubgen.typing_ext - -__all__: list[str] = [ - "std_array", - "std_map", - "std_optional", - "std_variant", - "std_vector", -] - -def std_array( - arg0: typing.Annotated[list[int], pybind11_stubgen.typing_ext.FixedSize(3)], -) -> typing.Annotated[list[int], pybind11_stubgen.typing_ext.FixedSize(3)]: ... -def std_map() -> dict[int, complex]: ... -def std_optional(arg0: int | None) -> None: ... -def std_variant(arg0: int | float | tuple[int, int]) -> None: ... -def std_vector() -> list[tuple[int, float]]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/stl_bind.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/stl_bind.pyi deleted file mode 100644 index 851d1a67..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/stl_bind.pyi +++ /dev/null @@ -1,141 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "MapStringComplex", - "VectorPairStringDouble", - "get_complex_map", - "get_vector_of_pairs", -] - -class VectorPairStringDouble: - __hash__: typing.ClassVar[None] = None - def __bool__(self) -> bool: - """ - Check whether the list is nonempty - """ - - def __contains__(self, x: tuple[str, float]) -> bool: - """ - Return true the container contains ``x`` - """ - - @typing.overload - def __delitem__(self, arg0: int) -> None: - """ - Delete the list elements at index ``i`` - """ - - @typing.overload - def __delitem__(self, arg0: slice) -> None: - """ - Delete list elements using a slice object - """ - - def __eq__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __getitem__(self, s: slice) -> VectorPairStringDouble: - """ - Retrieve list elements using a slice object - """ - - @typing.overload - def __getitem__(self, arg0: int) -> tuple[str, float]: ... - @typing.overload - def __init__(self) -> None: ... - @typing.overload - def __init__(self, arg0: VectorPairStringDouble) -> None: - """ - Copy constructor - """ - - @typing.overload - def __init__(self, arg0: typing.Iterable) -> None: ... - def __iter__(self) -> typing.Iterator[tuple[str, float]]: ... - def __len__(self) -> int: ... - def __ne__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __setitem__(self, arg0: int, arg1: tuple[str, float]) -> None: ... - @typing.overload - def __setitem__(self, arg0: slice, arg1: VectorPairStringDouble) -> None: - """ - Assign list elements using a slice object - """ - - def append(self, x: tuple[str, float]) -> None: - """ - Add an item to the end of the list - """ - - def clear(self) -> None: - """ - Clear the contents - """ - - def count(self, x: tuple[str, float]) -> int: - """ - Return the number of times ``x`` appears in the list - """ - - @typing.overload - def extend(self, L: VectorPairStringDouble) -> None: - """ - Extend the list by appending all the items in the given list - """ - - @typing.overload - def extend(self, L: typing.Iterable) -> None: - """ - Extend the list by appending all the items in the given list - """ - - def insert(self, i: int, x: tuple[str, float]) -> None: - """ - Insert an item at a given position. - """ - - @typing.overload - def pop(self) -> tuple[str, float]: - """ - Remove and return the last item - """ - - @typing.overload - def pop(self, i: int) -> tuple[str, float]: - """ - Remove and return the item at index ``i`` - """ - - def remove(self, x: tuple[str, float]) -> None: - """ - Remove the first item from the list whose value is x. It is an error if there is no such item. - """ - -class MapStringComplex: - def __bool__(self) -> bool: - """ - Check whether the map is nonempty - """ - - @typing.overload - def __contains__(self, arg0: str) -> bool: ... - @typing.overload - def __contains__(self, arg0: typing.Any) -> bool: ... - def __delitem__(self, arg0: str) -> None: ... - def __getitem__(self, arg0: str) -> complex: ... - def __init__(self) -> None: ... - def __iter__(self) -> typing.Iterator[str]: ... - def __len__(self) -> int: ... - def __repr__(self) -> str: - """ - Return the canonical string representation of this map. - """ - - def __setitem__(self, arg0: str, arg1: complex) -> None: ... - def items(self) -> typing.ItemsView: ... - def keys(self) -> typing.KeysView: ... - def values(self) -> typing.ValuesView: ... - -def get_complex_map() -> MapStringComplex: ... -def get_vector_of_pairs() -> VectorPairStringDouble: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/typing.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/typing.pyi deleted file mode 100644 index f2c4ab95..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/typing.pyi +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import annotations - -import typing - -import typing_extensions - -__all__: list[str] = ["get_buffer", "get_sequence"] - -def get_buffer(arg0: typing_extensions.Buffer) -> typing_extensions.Buffer: ... -def get_sequence(arg0: typing.Sequence) -> typing.Sequence: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/values.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/values.pyi deleted file mode 100644 index 7c36d3fd..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/values.pyi +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -import datetime - -import numpy -from numpy import random - -__all__: list[str] = [ - "Dummy", - "Foo", - "add_day", - "foolist", - "foovar", - "list_with_none", - "none", - "random", - "t_10ms", - "t_20ns", - "t_30s", -] - -class Dummy: - linalg = numpy.linalg - -class Foo: - pass - -def add_day(arg0: datetime.datetime) -> datetime.datetime: ... - -foolist: list # value = [, ] -foovar: Foo # value = -list_with_none: list = [None, 2, {}] -none = None -t_10ms: datetime.timedelta # value = datetime.timedelta(microseconds=10000) -t_20ns: datetime.timedelta # value = datetime.timedelta(0) -t_30s: datetime.timedelta # value = datetime.timedelta(seconds=30) diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/core.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/core.pyi deleted file mode 100644 index 95c16467..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/core.pyi +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/__init__.pyi deleted file mode 100644 index 10d343e0..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/__init__.pyi +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from . import classes, functions, functions_3_8_plus, functions_3_9_plus, values - -__all__: list[str] = [ - "classes", - "functions", - "functions_3_8_plus", - "functions_3_9_plus", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/classes.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/classes.pyi deleted file mode 100644 index 32aa068b..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/classes.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["A", "B", "C", "X"] - -class A: - """ - A - """ - -class B(A): - """ - B - """ - -class C(B): - """ - C - """ - -class X: - pass diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions.pyi deleted file mode 100644 index 5ed9ef3c..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions.pyi +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import annotations - -import sys as sys -import typing as typing - -from demo.pure_python.functions_3_8_plus import args_mix -from demo.pure_python.functions_3_9_plus import generic_alias_annotation - -__all__: list[str] = [ - "accept_frozenset", - "args_mix", - "builtin_function_as_default_arg", - "function_as_default_arg", - "generic_alias_annotation", - "lambda_as_default_arg", - "search", - "static_method_as_default_arg", - "sys", - "typing", -] - -class _Dummy: - @staticmethod - def foo(): ... - -def accept_frozenset(arg: frozenset[int | float]) -> int | None: ... -def builtin_function_as_default_arg(func: type(len) = len): ... -def function_as_default_arg(func: type(search) = search): ... -def lambda_as_default_arg(callback=...): ... -def search(a: int, b: list[int]) -> int: ... -def static_method_as_default_arg(callback=_Dummy.foo): ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions_3_8_plus.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions_3_8_plus.pyi deleted file mode 100644 index c55aed1d..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions_3_8_plus.pyi +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import typing as typing - -__all__: list[str] = [ - "Expression", - "Token", - "args_mix", - "nested_current_module_annotations", - "typing", -] - -class Token: - pass - -class Expression: - pass - -def args_mix( - a: int, - b: float = 0.5, - c: str = "", - *args: int, - x: int = 1, - y=int, - **kwargs: dict[int, str], -): ... -def nested_current_module_annotations( - tokens: list[Token], expr: Expression | None = None -) -> dict[str, Expression]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions_3_9_plus.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions_3_9_plus.pyi deleted file mode 100644 index 59b9b80c..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions_3_9_plus.pyi +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["generic_alias_annotation"] - -def generic_alias_annotation(a: list[tuple[int]], b: dict[int, str]) -> list[float]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/values.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/values.pyi deleted file mode 100644 index 07913391..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/values.pyi +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["callables_dict"] -callables_dict: dict = {"len": len, "int": int} diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/__init__.pyi deleted file mode 100644 index 86fb6b84..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/__init__.pyi +++ /dev/null @@ -1,41 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -from . import _bindings, core, pure_python - -__all__: list[str] = [ - "aliases", - "classes", - "core", - "eigen", - "enum", - "flawed_bindings", - "functions", - "issues", - "methods", - "numpy", - "properties", - "pure_python", - "stl", - "stl_bind", - "typing", - "values", - "version", -] -version: str = "0.0.0" diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi deleted file mode 100644 index c263078f..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -from . import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi deleted file mode 100644 index d8c8d8ac..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.enum -import numpy -from demo._bindings.aliases.foreign_method_arg import Bar2 as foreign_type_alias -from demo._bindings.aliases.foreign_return import get_foo as foreign_class_alias -from numpy import random - -from . import ( - foreign_arg, - foreign_attr, - foreign_class_member, - foreign_method_arg, - foreign_method_return, - foreign_return, - missing_self_arg, -) - -__all__: list[str] = [ - "Color", - "Dummy", - "foreign_arg", - "foreign_attr", - "foreign_class_alias", - "foreign_class_member", - "foreign_enum_default", - "foreign_method_arg", - "foreign_method_return", - "foreign_return", - "foreign_type_alias", - "func", - "local_func_alias", - "local_type_alias", - "missing_self_arg", - "random", -] - -class Dummy: - linalg = numpy.linalg - -class Color: - pass - -def foreign_enum_default( - color: typing.Any = demo._bindings.enum.ConsoleForegroundColor.Blue, -) -> None: ... -def func(arg0: int) -> int: ... - -local_type_alias = Color -local_func_alias = func diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi deleted file mode 100644 index de75e6fb..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["set_foo"] - -def set_foo(arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi deleted file mode 100644 index 10c4a712..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["value"] -value: demo._bindings.classes.Foo # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi deleted file mode 100644 index 497a4e2a..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.classes - -__all__: list[str] = ["Bar1"] - -class Bar1: - foo: typing.ClassVar[ - demo._bindings.classes.Foo - ] # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi deleted file mode 100644 index 8d4ed4ac..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar2"] - -class Bar2: - def set_foo(self, arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi deleted file mode 100644 index 95d218e1..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar3"] - -class Bar3: - @staticmethod - def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi deleted file mode 100644 index 7b5b428f..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["get_foo"] - -def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi deleted file mode 100644 index ee1a09ee..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar4"] - -class Bar4: - def set_foo(self: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi deleted file mode 100644 index 73c6e950..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "CppException", - "Derived", - "Foo", - "MyBase", - "Outer", - "ParIter", - "ParIterBase", - "ParticleContainer", -] - -class Outer: - class Inner: - class NestedEnum: - """ - Members: - - ONE - - TWO - """ - - ONE: typing.ClassVar[Outer.Inner.NestedEnum] # value = - TWO: typing.ClassVar[Outer.Inner.NestedEnum] # value = - __members__: typing.ClassVar[ - dict[str, Outer.Inner.NestedEnum] - ] # value = {'ONE': , 'TWO': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - - value: Outer.Inner.NestedEnum - - inner: Outer.Inner - -class MyBase: - class Inner: - pass - - name: str - -class Derived(MyBase): - count: int - -class Foo: - class FooChild: - def __init__(self) -> None: ... - def g(self) -> None: ... - - def __init__(self) -> None: ... - def f(self) -> None: ... - -class ParIterBase: - level: int - -class ParIter(ParIterBase): - def __init__(self, particle_container: ParticleContainer, level: int) -> None: ... - -class ParticleContainer: - name: str - Iterator = ParIter - def process(self, arg0: ParIter) -> None: ... - -class CppException(Exception): - pass diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi deleted file mode 100644 index 795a711b..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi +++ /dev/null @@ -1,101 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy -import scipy.sparse - -import pybind11_stubgen.typing_ext - -__all__: list[str] = [ - "accept_matrix_int", - "accept_vector_float64", - "dense_matrix_c", - "dense_matrix_r", - "fixed_mutator_a", - "fixed_mutator_c", - "fixed_mutator_r", - "four_col_matrix_r", - "four_row_matrix_r", - "get_matrix_int", - "get_vector_float64", - "sparse_matrix_c", - "sparse_matrix_r", -] - -def accept_matrix_int( - arg0: typing.Annotated[ - numpy.ndarray, numpy.int32, pybind11_stubgen.typing_ext.FixedSize(3, 3) - ], -) -> None: ... -def accept_vector_float64( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float64, pybind11_stubgen.typing_ext.FixedSize(3, 1) - ], -) -> None: ... -def dense_matrix_c( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") -]: ... -def dense_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") -]: ... -def fixed_mutator_a( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - ], -) -> None: ... -def fixed_mutator_c( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - numpy.ndarray.flags.f_contiguous, - ], -) -> None: ... -def fixed_mutator_r( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - numpy.ndarray.flags.c_contiguous, - ], -) -> None: ... -def four_col_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", 4) - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", 4) -]: ... -def four_row_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize(4, "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize(4, "n") -]: ... -def get_matrix_int() -> typing.Annotated[ - numpy.ndarray, numpy.int32, pybind11_stubgen.typing_ext.FixedSize(3, 3) -]: ... -def get_vector_float64() -> typing.Annotated[ - numpy.ndarray, numpy.float64, pybind11_stubgen.typing_ext.FixedSize(3, 1) -]: ... -def sparse_matrix_c( - arg0: typing.Annotated[scipy.sparse.csc_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csc_matrix, numpy.float32]: ... -def sparse_matrix_r( - arg0: typing.Annotated[scipy.sparse.csr_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csr_matrix, numpy.float32]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi deleted file mode 100644 index 57aba5b9..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Blue", - "ConsoleForegroundColor", - "Green", - "Magenta", - "None_", - "Yellow", - "accept_defaulted_enum", -] - -class ConsoleForegroundColor: - """ - Members: - - Green - - Yellow - - Blue - - Magenta - - None_ - """ - - Blue: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Green: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Magenta: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - None_: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Yellow: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - __members__: typing.ClassVar[ - dict[str, ConsoleForegroundColor] - ] # value = {'Green': , 'Yellow': , 'Blue': , 'Magenta': , 'None_': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - -def accept_defaulted_enum( - color: ConsoleForegroundColor = ConsoleForegroundColor.None_, -) -> None: ... - -Blue: ConsoleForegroundColor # value = -Green: ConsoleForegroundColor # value = -Magenta: ConsoleForegroundColor # value = -None_: ConsoleForegroundColor # value = -Yellow: ConsoleForegroundColor # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi deleted file mode 100644 index 96774842..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = [ - "Enum", - "Unbound", - "accept_unbound_enum", - "accept_unbound_enum_defaulted", - "accept_unbound_type", - "accept_unbound_type_defaulted", - "get_unbound_type", -] - -class Unbound: - pass - -class Enum: - pass - -def accept_unbound_enum(arg0: ...) -> int: ... -def accept_unbound_enum_defaulted(x: Enum = ...) -> int: ... -def accept_unbound_type(arg0: tuple[..., int]) -> int: ... -def accept_unbound_type_defaulted(x: Unbound = ...) -> int: ... -def get_unbound_type() -> ...: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi deleted file mode 100644 index 63de1c09..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi +++ /dev/null @@ -1,82 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Foo", - "U", - "accept_callable", - "accept_py_handle", - "accept_py_object", - "accept_set", - "add", - "default_custom_arg", - "default_int_arg", - "default_list_arg", - "default_optional_arg", - "func_w_anon_args", - "func_w_named_pos_args", - "generic", - "mul", - "nested_types", - "pass_callback", - "passthrough1", - "passthrough2", - "passthrough3", - "passthrough_backwards", - "pos_kw_only_mix", - "pos_kw_only_variadic_mix", -] - -class Foo: - def __init__(self, arg0: int) -> None: ... - -def accept_callable(arg0: typing.Callable) -> typing.Any: ... -def accept_py_handle(arg0: typing.Any) -> str: ... -def accept_py_object(arg0: typing.Any) -> str: ... -def accept_set(arg0: set) -> None: ... -def add(arg0: int, arg1: int) -> int: ... -def default_custom_arg(foo: Foo = Foo(5)) -> None: ... -def default_int_arg(n: int = 5) -> None: ... -def default_list_arg(l: list = [1, 2, 6, 18]) -> None: ... -def default_optional_arg(n: int | None = None) -> None: ... -def func_w_anon_args(arg0: int, arg1: int, arg2: int) -> None: ... -def func_w_named_pos_args(x: int, y: int, z: int) -> None: ... -def generic(*args, **kwargs) -> None: ... -@typing.overload -def mul(x: int, y: int) -> int: - """ - Multiply x and y (int) - """ - -@typing.overload -def mul(p: float, q: float) -> float: - """ - Multiply p and q (double) - """ - -def nested_types(arg0: list[Foo] | Foo) -> list[Foo] | Foo: ... -def pass_callback(arg0: typing.Callable[[Foo], Foo]) -> Foo: ... -def passthrough1(*args, **kwargs): - """ - passthrough1[T](obj: T) -> T - """ - -@typing.overload -def passthrough2() -> None: - """ - 2. passthrough2[T](obj: T) -> T - """ - -@typing.overload -def passthrough3() -> tuple[None, None]: - """ - 2. passthrough3[T](obj: T) -> tuple[T, None] - 3. passthrough3[T1, T2](obj1: T1, obj2: T2) -> tuple[T1, T2] - """ - -def passthrough_backwards(obj: U) -> U: ... -def pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple: ... -def pos_kw_only_variadic_mix(i: int, /, j: int, *args, k: int, **kwargs) -> tuple: ... - -U: typing.TypeVar # value = ~U diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi deleted file mode 100644 index a537a958..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "backslashes_should_be_escaped", - "issue_51_catastrophic_regex", - "issue_73_utf8_doc_chars", - "issue_274_docstring_leading_newline", -] - -def backslashes_should_be_escaped() -> None: - """ - \\brief A brief description of this function. - - A detailed description of this function. - - Here's some reStructuredText: :math:`x = [x, y, \\theta]^T` - """ - -def issue_274_docstring_leading_newline() -> None: - """ - This is a docstring - """ - -def issue_51_catastrophic_regex(arg0: int, arg1: int) -> None: - """ - Use-case: - issue_51(os.get_handle_inheritable, os.set_handle_inheritable) - """ - -def issue_73_utf8_doc_chars() -> None: - """ - Construct a Ramsete unicycle controller. - - Tuning parameter (b > 0 rad²/m²) for which larger values make - - convergence more aggressive like a proportional term. - Tuning parameter (0 rad⁻¹ < zeta < 1 rad⁻¹) for which larger - values provide more damping in response. - """ - -_cleanup: typing.Any # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi deleted file mode 100644 index 6c115cd1..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["Dummy"] - -class Dummy: - @staticmethod - def static_method(arg0: int) -> int: ... - def regular_method(self, arg0: int) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi deleted file mode 100644 index 34943048..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy - -__all__: list[str] = [ - "accept_ndarray_float64", - "accept_ndarray_int", - "get_ndarray_float64", - "get_ndarray_int", - "return_dtype", -] - -def accept_ndarray_float64( - arg0: typing.Annotated[numpy.ndarray, numpy.float64], -) -> None: ... -def accept_ndarray_int(arg0: typing.Annotated[numpy.ndarray, numpy.int32]) -> None: ... -def get_ndarray_float64() -> typing.Annotated[numpy.ndarray, numpy.float64]: ... -def get_ndarray_int() -> typing.Annotated[numpy.ndarray, numpy.int32]: ... -def return_dtype() -> numpy.dtype[typing.Any]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi deleted file mode 100644 index 556f558f..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi +++ /dev/null @@ -1,106 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "WithGetterSetterDoc", - "WithPropAndGetterSetterDoc", - "WithPropDoc", - "WithoutDoc", -] - -class WithoutDoc: - """ - No user docstring provided - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - def_property: int - def_readwrite: int - @property - def def_property_readonly(self) -> int: ... - @property - def def_readonly(self) -> int: ... - -class WithPropDoc: - """ - User docstring provided only to `def_` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - prop doc token - """ - - @def_property.setter - def def_property(self, arg1: int) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ - - @property - def def_readonly(self) -> int: - """ - prop doc token - """ - - @property - def def_readwrite(self) -> int: - """ - prop doc token - """ - - @def_readwrite.setter - def def_readwrite(self, arg0: int) -> None: ... - -class WithGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters, but NOT to `def_*(..., doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - getter doc token - """ - - @def_property.setter - def def_property(self, arg1: int) -> None: - """ - setter doc token - """ - - @property - def def_property_readonly(self) -> int: - """ - getter doc token - """ - -class WithPropAndGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters and to `def_*(, doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - prop doc token - """ - - @def_property.setter - def def_property(self, arg1: int) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi deleted file mode 100644 index 5df5cea8..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "std_array", - "std_map", - "std_optional", - "std_variant", - "std_vector", -] - -def std_array(arg0: list[int[3]]) -> list[int[3]]: ... -def std_map() -> dict[int, complex]: ... -def std_optional(arg0: int | None) -> None: ... -def std_variant(arg0: int | float | tuple[int, int]) -> None: ... -def std_vector() -> list[tuple[int, float]]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi deleted file mode 100644 index d80b1706..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi +++ /dev/null @@ -1,141 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "MapStringComplex", - "VectorPairStringDouble", - "get_complex_map", - "get_vector_of_pairs", -] - -class VectorPairStringDouble: - __hash__: typing.ClassVar[None] = None - def __bool__(self) -> bool: - """ - Check whether the list is nonempty - """ - - def __contains__(self, x: tuple[str, float]) -> bool: - """ - Return true the container contains ``x`` - """ - - @typing.overload - def __delitem__(self, arg0: int) -> None: - """ - Delete the list elements at index ``i`` - """ - - @typing.overload - def __delitem__(self, arg0: slice) -> None: - """ - Delete list elements using a slice object - """ - - def __eq__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __getitem__(self, s: slice) -> VectorPairStringDouble: - """ - Retrieve list elements using a slice object - """ - - @typing.overload - def __getitem__(self, arg0: int) -> tuple[str, float]: ... - @typing.overload - def __init__(self) -> None: ... - @typing.overload - def __init__(self, arg0: VectorPairStringDouble) -> None: - """ - Copy constructor - """ - - @typing.overload - def __init__(self, arg0: typing.Iterable) -> None: ... - def __iter__(self) -> typing.Iterator: ... - def __len__(self) -> int: ... - def __ne__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __setitem__(self, arg0: int, arg1: tuple[str, float]) -> None: ... - @typing.overload - def __setitem__(self, arg0: slice, arg1: VectorPairStringDouble) -> None: - """ - Assign list elements using a slice object - """ - - def append(self, x: tuple[str, float]) -> None: - """ - Add an item to the end of the list - """ - - def clear(self) -> None: - """ - Clear the contents - """ - - def count(self, x: tuple[str, float]) -> int: - """ - Return the number of times ``x`` appears in the list - """ - - @typing.overload - def extend(self, L: VectorPairStringDouble) -> None: - """ - Extend the list by appending all the items in the given list - """ - - @typing.overload - def extend(self, L: typing.Iterable) -> None: - """ - Extend the list by appending all the items in the given list - """ - - def insert(self, i: int, x: tuple[str, float]) -> None: - """ - Insert an item at a given position. - """ - - @typing.overload - def pop(self) -> tuple[str, float]: - """ - Remove and return the last item - """ - - @typing.overload - def pop(self, i: int) -> tuple[str, float]: - """ - Remove and return the item at index ``i`` - """ - - def remove(self, x: tuple[str, float]) -> None: - """ - Remove the first item from the list whose value is x. It is an error if there is no such item. - """ - -class MapStringComplex: - def __bool__(self) -> bool: - """ - Check whether the map is nonempty - """ - - @typing.overload - def __contains__(self, arg0: str) -> bool: ... - @typing.overload - def __contains__(self, arg0: typing.Any) -> bool: ... - def __delitem__(self, arg0: str) -> None: ... - def __getitem__(self, arg0: str) -> complex: ... - def __init__(self) -> None: ... - def __iter__(self) -> typing.Iterator: ... - def __len__(self) -> int: ... - def __repr__(self) -> str: - """ - Return the canonical string representation of this map. - """ - - def __setitem__(self, arg0: str, arg1: complex) -> None: ... - def items(self) -> typing.ItemsView[MapStringComplex]: ... - def keys(self) -> typing.KeysView[MapStringComplex]: ... - def values(self) -> typing.ValuesView[MapStringComplex]: ... - -def get_complex_map() -> MapStringComplex: ... -def get_vector_of_pairs() -> VectorPairStringDouble: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi deleted file mode 100644 index f2c4ab95..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import annotations - -import typing - -import typing_extensions - -__all__: list[str] = ["get_buffer", "get_sequence"] - -def get_buffer(arg0: typing_extensions.Buffer) -> typing_extensions.Buffer: ... -def get_sequence(arg0: typing.Sequence) -> typing.Sequence: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi deleted file mode 100644 index 7c36d3fd..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -import datetime - -import numpy -from numpy import random - -__all__: list[str] = [ - "Dummy", - "Foo", - "add_day", - "foolist", - "foovar", - "list_with_none", - "none", - "random", - "t_10ms", - "t_20ns", - "t_30s", -] - -class Dummy: - linalg = numpy.linalg - -class Foo: - pass - -def add_day(arg0: datetime.datetime) -> datetime.datetime: ... - -foolist: list # value = [, ] -foovar: Foo # value = -list_with_none: list = [None, 2, {}] -none = None -t_10ms: datetime.timedelta # value = datetime.timedelta(microseconds=10000) -t_20ns: datetime.timedelta # value = datetime.timedelta(0) -t_30s: datetime.timedelta # value = datetime.timedelta(seconds=30) diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/core.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/core.pyi deleted file mode 100644 index 95c16467..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/core.pyi +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi deleted file mode 100644 index 10d343e0..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from . import classes, functions, functions_3_8_plus, functions_3_9_plus, values - -__all__: list[str] = [ - "classes", - "functions", - "functions_3_8_plus", - "functions_3_9_plus", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi deleted file mode 100644 index 32aa068b..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["A", "B", "C", "X"] - -class A: - """ - A - """ - -class B(A): - """ - B - """ - -class C(B): - """ - C - """ - -class X: - pass diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi deleted file mode 100644 index 5ed9ef3c..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import annotations - -import sys as sys -import typing as typing - -from demo.pure_python.functions_3_8_plus import args_mix -from demo.pure_python.functions_3_9_plus import generic_alias_annotation - -__all__: list[str] = [ - "accept_frozenset", - "args_mix", - "builtin_function_as_default_arg", - "function_as_default_arg", - "generic_alias_annotation", - "lambda_as_default_arg", - "search", - "static_method_as_default_arg", - "sys", - "typing", -] - -class _Dummy: - @staticmethod - def foo(): ... - -def accept_frozenset(arg: frozenset[int | float]) -> int | None: ... -def builtin_function_as_default_arg(func: type(len) = len): ... -def function_as_default_arg(func: type(search) = search): ... -def lambda_as_default_arg(callback=...): ... -def search(a: int, b: list[int]) -> int: ... -def static_method_as_default_arg(callback=_Dummy.foo): ... diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi deleted file mode 100644 index c55aed1d..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import typing as typing - -__all__: list[str] = [ - "Expression", - "Token", - "args_mix", - "nested_current_module_annotations", - "typing", -] - -class Token: - pass - -class Expression: - pass - -def args_mix( - a: int, - b: float = 0.5, - c: str = "", - *args: int, - x: int = 1, - y=int, - **kwargs: dict[int, str], -): ... -def nested_current_module_annotations( - tokens: list[Token], expr: Expression | None = None -) -> dict[str, Expression]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi deleted file mode 100644 index 59b9b80c..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["generic_alias_annotation"] - -def generic_alias_annotation(a: list[tuple[int]], b: dict[int, str]) -> list[float]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi b/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi deleted file mode 100644 index 07913391..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["callables_dict"] -callables_dict: dict = {"len": len, "int": int} diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/__init__.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/__init__.pyi deleted file mode 100644 index 86fb6b84..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/__init__.pyi +++ /dev/null @@ -1,41 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -from . import _bindings, core, pure_python - -__all__: list[str] = [ - "aliases", - "classes", - "core", - "eigen", - "enum", - "flawed_bindings", - "functions", - "issues", - "methods", - "numpy", - "properties", - "pure_python", - "stl", - "stl_bind", - "typing", - "values", - "version", -] -version: str = "0.0.0" diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/__init__.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/__init__.pyi deleted file mode 100644 index c263078f..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/__init__.pyi +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -from . import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/__init__.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/__init__.pyi deleted file mode 100644 index c696ef48..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/__init__.pyi +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.enum -import numpy -from demo._bindings.aliases.foreign_method_arg import Bar2 as foreign_type_alias -from demo._bindings.aliases.foreign_return import get_foo as foreign_class_alias -from numpy import random - -from . import ( - foreign_arg, - foreign_attr, - foreign_class_member, - foreign_method_arg, - foreign_method_return, - foreign_return, - missing_self_arg, -) - -__all__: list[str] = [ - "Color", - "Dummy", - "foreign_arg", - "foreign_attr", - "foreign_class_alias", - "foreign_class_member", - "foreign_enum_default", - "foreign_method_arg", - "foreign_method_return", - "foreign_return", - "foreign_type_alias", - "func", - "local_func_alias", - "local_type_alias", - "missing_self_arg", - "random", -] - -class Dummy: - linalg = numpy.linalg - -class Color: - pass - -def foreign_enum_default( - color: typing.Any = demo._bindings.enum.ConsoleForegroundColor.Blue, -) -> None: ... -def func(arg0: typing.SupportsInt | typing.SupportsIndex) -> int: ... - -local_type_alias = Color -local_func_alias = func diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_arg.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_arg.pyi deleted file mode 100644 index de75e6fb..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_arg.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["set_foo"] - -def set_foo(arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_attr.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_attr.pyi deleted file mode 100644 index 10c4a712..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_attr.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["value"] -value: demo._bindings.classes.Foo # value = diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_class_member.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_class_member.pyi deleted file mode 100644 index 497a4e2a..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_class_member.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.classes - -__all__: list[str] = ["Bar1"] - -class Bar1: - foo: typing.ClassVar[ - demo._bindings.classes.Foo - ] # value = diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_arg.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_arg.pyi deleted file mode 100644 index 8d4ed4ac..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar2"] - -class Bar2: - def set_foo(self, arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_return.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_return.pyi deleted file mode 100644 index 95d218e1..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_return.pyi +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar3"] - -class Bar3: - @staticmethod - def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_return.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_return.pyi deleted file mode 100644 index 7b5b428f..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_return.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["get_foo"] - -def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/missing_self_arg.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/missing_self_arg.pyi deleted file mode 100644 index ee1a09ee..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/missing_self_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar4"] - -class Bar4: - def set_foo(self: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/classes.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/classes.pyi deleted file mode 100644 index d85d9249..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/classes.pyi +++ /dev/null @@ -1,94 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "CppException", - "Derived", - "Foo", - "MyBase", - "Outer", - "ParIter", - "ParIterBase", - "ParticleContainer", -] - -class Outer: - class Inner: - class NestedEnum: - """ - Members: - - ONE - - TWO - """ - - ONE: typing.ClassVar[Outer.Inner.NestedEnum] # value = - TWO: typing.ClassVar[Outer.Inner.NestedEnum] # value = - __members__: typing.ClassVar[ - dict[str, Outer.Inner.NestedEnum] - ] # value = {'ONE': , 'TWO': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__( - self, value: typing.SupportsInt | typing.SupportsIndex - ) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__( - self, state: typing.SupportsInt | typing.SupportsIndex - ) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - - value: Outer.Inner.NestedEnum - - inner: Outer.Inner - -class MyBase: - class Inner: - pass - - name: str - -class Derived(MyBase): - @property - def count(self) -> int: ... - @count.setter - def count(self, arg0: typing.SupportsInt | typing.SupportsIndex) -> None: ... - -class Foo: - class FooChild: - def __init__(self) -> None: ... - def g(self) -> None: ... - - def __init__(self) -> None: ... - def f(self) -> None: ... - -class ParIterBase: - @property - def level(self) -> int: ... - @level.setter - def level(self, arg0: typing.SupportsInt | typing.SupportsIndex) -> None: ... - -class ParIter(ParIterBase): - def __init__( - self, - particle_container: ParticleContainer, - level: typing.SupportsInt | typing.SupportsIndex, - ) -> None: ... - -class ParticleContainer: - name: str - Iterator = ParIter - def process(self, arg0: ParIter) -> None: ... - -class CppException(Exception): - pass diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/eigen.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/eigen.pyi deleted file mode 100644 index 2adeb4f1..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/eigen.pyi +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy -import numpy.typing -import scipy.sparse - -__all__: list[str] = [ - "accept_matrix_int", - "accept_vector_float64", - "dense_matrix_c", - "dense_matrix_r", - "fixed_mutator_a", - "fixed_mutator_c", - "fixed_mutator_r", - "four_col_matrix_r", - "four_row_matrix_r", - "get_matrix_int", - "get_vector_float64", - "sparse_matrix_c", - "sparse_matrix_r", -] - -def accept_matrix_int( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.int32, "[3, 3]"], -) -> None: ... -def accept_vector_float64( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float64, "[3, 1]"], -) -> None: ... -def dense_matrix_c( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float32, "[m, n]"], -) -> typing.Annotated[numpy.typing.NDArray[numpy.float32], "[m, n]"]: ... -def dense_matrix_r( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float32, "[m, n]"], -) -> typing.Annotated[numpy.typing.NDArray[numpy.float32], "[m, n]"]: ... -def fixed_mutator_a( - arg0: typing.Annotated[ - numpy.typing.NDArray[numpy.float32], "[5, 6]", "flags.writeable" - ], -) -> None: ... -def fixed_mutator_c( - arg0: typing.Annotated[ - numpy.typing.NDArray[numpy.float32], - "[5, 6]", - "flags.writeable", - "flags.f_contiguous", - ], -) -> None: ... -def fixed_mutator_r( - arg0: typing.Annotated[ - numpy.typing.NDArray[numpy.float32], - "[5, 6]", - "flags.writeable", - "flags.c_contiguous", - ], -) -> None: ... -def four_col_matrix_r( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float32, "[m, 4]"], -) -> typing.Annotated[numpy.typing.NDArray[numpy.float32], "[m, 4]"]: ... -def four_row_matrix_r( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float32, "[4, n]"], -) -> typing.Annotated[numpy.typing.NDArray[numpy.float32], "[4, n]"]: ... -def get_matrix_int() -> typing.Annotated[ - numpy.typing.NDArray[numpy.int32], "[3, 3]" -]: ... -def get_vector_float64() -> typing.Annotated[ - numpy.typing.NDArray[numpy.float64], "[3, 1]" -]: ... -def sparse_matrix_c(arg0: scipy.sparse.csc_matrix) -> scipy.sparse.csc_matrix: ... -def sparse_matrix_r(arg0: scipy.sparse.csr_matrix) -> scipy.sparse.csr_matrix: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/enum.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/enum.pyi deleted file mode 100644 index dcef00e7..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/enum.pyi +++ /dev/null @@ -1,73 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Blue", - "ConsoleForegroundColor", - "Green", - "Magenta", - "None_", - "Yellow", - "accept_defaulted_enum", -] - -class ConsoleForegroundColor: - """ - Members: - - Green - - Yellow - - Blue - - Magenta - - None_ - """ - - Blue: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Green: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Magenta: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - None_: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Yellow: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - __members__: typing.ClassVar[ - dict[str, ConsoleForegroundColor] - ] # value = {'Green': , 'Yellow': , 'Blue': , 'Magenta': , 'None_': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: typing.SupportsInt | typing.SupportsIndex) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__( - self, state: typing.SupportsInt | typing.SupportsIndex - ) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - -def accept_defaulted_enum( - color: ConsoleForegroundColor = ConsoleForegroundColor.None_, -) -> None: ... - -Blue: ConsoleForegroundColor # value = -Green: ConsoleForegroundColor # value = -Magenta: ConsoleForegroundColor # value = -None_: ConsoleForegroundColor # value = -Yellow: ConsoleForegroundColor # value = diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/flawed_bindings.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/flawed_bindings.pyi deleted file mode 100644 index f8c22db8..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/flawed_bindings.pyi +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Enum", - "Unbound", - "accept_unbound_enum", - "accept_unbound_enum_defaulted", - "accept_unbound_type", - "accept_unbound_type_defaulted", - "get_unbound_type", -] - -class Unbound: - pass - -class Enum: - pass - -def accept_unbound_enum(arg0: ...) -> int: ... -def accept_unbound_enum_defaulted(x: Enum = ...) -> int: ... -def accept_unbound_type( - arg0: tuple[..., typing.SupportsInt | typing.SupportsIndex], -) -> int: ... -def accept_unbound_type_defaulted(x: Unbound = ...) -> int: ... -def get_unbound_type() -> ...: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/functions.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/functions.pyi deleted file mode 100644 index a6528c0f..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/functions.pyi +++ /dev/null @@ -1,127 +0,0 @@ -from __future__ import annotations - -import collections.abc -import typing - -__all__: list[str] = [ - "Foo", - "U", - "accept_annotated_callable", - "accept_callable", - "accept_frozenset", - "accept_py_handle", - "accept_py_object", - "accept_set", - "add", - "default_custom_arg", - "default_int_arg", - "default_list_arg", - "default_optional_arg", - "func_w_anon_args", - "func_w_named_pos_args", - "generic", - "mul", - "nested_types", - "pass_callback", - "passthrough1", - "passthrough2", - "passthrough3", - "passthrough_backwards", - "pos_kw_only_mix", - "pos_kw_only_variadic_mix", -] - -class Foo: - def __init__(self, arg0: typing.SupportsInt | typing.SupportsIndex) -> None: ... - -def accept_annotated_callable( - arg0: collections.abc.Callable[ - [ - typing.SupportsInt | typing.SupportsIndex, - typing.SupportsInt | typing.SupportsIndex, - ], - int, - ], -) -> typing.Any: ... -def accept_callable(arg0: collections.abc.Callable) -> typing.Any: ... -def accept_frozenset(arg0: frozenset) -> None: ... -def accept_py_handle(arg0: typing.Any) -> str: ... -def accept_py_object(arg0: typing.Any) -> str: ... -def accept_set(arg0: set) -> None: ... -def add( - arg0: typing.SupportsInt | typing.SupportsIndex, - arg1: typing.SupportsInt | typing.SupportsIndex, -) -> int: ... -def default_custom_arg(foo: Foo = Foo(5)) -> None: ... -def default_int_arg(n: typing.SupportsInt | typing.SupportsIndex = 5) -> None: ... -def default_list_arg(l: list = [1, 2, 6, 18]) -> None: ... -def default_optional_arg( - n: typing.SupportsInt | typing.SupportsIndex | None = None, -) -> None: ... -def func_w_anon_args( - arg0: typing.SupportsInt | typing.SupportsIndex, - arg1: typing.SupportsInt | typing.SupportsIndex, - arg2: typing.SupportsInt | typing.SupportsIndex, -) -> None: ... -def func_w_named_pos_args( - x: typing.SupportsInt | typing.SupportsIndex, - y: typing.SupportsInt | typing.SupportsIndex, - z: typing.SupportsInt | typing.SupportsIndex, -) -> None: ... -def generic(*args, **kwargs) -> None: ... -@typing.overload -def mul( - x: typing.SupportsInt | typing.SupportsIndex, - y: typing.SupportsInt | typing.SupportsIndex, -) -> int: - """ - Multiply x and y (int) - """ - -@typing.overload -def mul( - p: typing.SupportsFloat | typing.SupportsIndex, - q: typing.SupportsFloat | typing.SupportsIndex, -) -> float: - """ - Multiply p and q (double) - """ - -def nested_types(arg0: collections.abc.Sequence[Foo] | Foo) -> list[Foo] | Foo: ... -def pass_callback(arg0: collections.abc.Callable[[Foo], Foo]) -> Foo: ... -def passthrough1(*args, **kwargs): - """ - passthrough1[T](obj: T) -> T - """ - -@typing.overload -def passthrough2() -> None: - """ - 2. passthrough2[T](obj: T) -> T - """ - -@typing.overload -def passthrough3() -> tuple[None, None]: - """ - 2. passthrough3[T](obj: T) -> tuple[T, None] - 3. passthrough3[T1, T2](obj1: T1, obj2: T2) -> tuple[T1, T2] - """ - -def passthrough_backwards(obj: U) -> U: ... -def pos_kw_only_mix( - i: typing.SupportsInt | typing.SupportsIndex, - /, - j: typing.SupportsInt | typing.SupportsIndex, - *, - k: typing.SupportsInt | typing.SupportsIndex, -) -> tuple: ... -def pos_kw_only_variadic_mix( - i: typing.SupportsInt | typing.SupportsIndex, - /, - j: typing.SupportsInt | typing.SupportsIndex, - *args, - k: typing.SupportsInt | typing.SupportsIndex, - **kwargs, -) -> tuple: ... - -U: typing.TypeVar # value = ~U diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/issues.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/issues.pyi deleted file mode 100644 index 4dccd320..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/issues.pyi +++ /dev/null @@ -1,45 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "backslashes_should_be_escaped", - "issue_51_catastrophic_regex", - "issue_73_utf8_doc_chars", - "issue_274_docstring_leading_newline", -] - -def backslashes_should_be_escaped() -> None: - """ - \\brief A brief description of this function. - - A detailed description of this function. - - Here's some reStructuredText: :math:`x = [x, y, \\theta]^T` - """ - -def issue_274_docstring_leading_newline() -> None: - """ - This is a docstring - """ - -def issue_51_catastrophic_regex( - arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsInt -) -> None: - """ - Use-case: - issue_51(os.get_handle_inheritable, os.set_handle_inheritable) - """ - -def issue_73_utf8_doc_chars() -> None: - """ - Construct a Ramsete unicycle controller. - - Tuning parameter (b > 0 rad²/m²) for which larger values make - - convergence more aggressive like a proportional term. - Tuning parameter (0 rad⁻¹ < zeta < 1 rad⁻¹) for which larger - values provide more damping in response. - """ - -_cleanup: typing.Any # value = diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/methods.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/methods.pyi deleted file mode 100644 index 0ac0df06..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/methods.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = ["Dummy"] - -class Dummy: - @staticmethod - def static_method(arg0: typing.SupportsInt | typing.SupportsIndex) -> int: ... - def regular_method( - self, arg0: typing.SupportsInt | typing.SupportsIndex - ) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/numpy.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/numpy.pyi deleted file mode 100644 index ca0a64b9..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/numpy.pyi +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy -import numpy.typing - -__all__: list[str] = [ - "accept_ndarray_float64", - "accept_ndarray_int", - "get_ndarray_float64", - "get_ndarray_int", - "return_dtype", -] - -def accept_ndarray_float64( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float64], -) -> None: ... -def accept_ndarray_int( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.int32], -) -> None: ... -def get_ndarray_float64() -> numpy.typing.NDArray[numpy.float64]: ... -def get_ndarray_int() -> numpy.typing.NDArray[numpy.int32]: ... -def return_dtype() -> numpy.dtype[typing.Any]: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/properties.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/properties.pyi deleted file mode 100644 index 7ebd112b..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/properties.pyi +++ /dev/null @@ -1,116 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "WithGetterSetterDoc", - "WithPropAndGetterSetterDoc", - "WithPropDoc", - "WithoutDoc", -] - -class WithoutDoc: - """ - No user docstring provided - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: ... - @def_property.setter - def def_property(self, arg1: typing.SupportsInt | typing.SupportsIndex) -> None: ... - @property - def def_property_readonly(self) -> int: ... - @property - def def_readonly(self) -> int: ... - @property - def def_readwrite(self) -> int: ... - @def_readwrite.setter - def def_readwrite( - self, arg0: typing.SupportsInt | typing.SupportsIndex - ) -> None: ... - -class WithPropDoc: - """ - User docstring provided only to `def_` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - prop doc token - """ - - @def_property.setter - def def_property(self, arg1: typing.SupportsInt | typing.SupportsIndex) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ - - @property - def def_readonly(self) -> int: - """ - prop doc token - """ - - @property - def def_readwrite(self) -> int: - """ - prop doc token - """ - - @def_readwrite.setter - def def_readwrite( - self, arg0: typing.SupportsInt | typing.SupportsIndex - ) -> None: ... - -class WithGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters, but NOT to `def_*(..., doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - getter doc token - """ - - @def_property.setter - def def_property(self, arg1: typing.SupportsInt | typing.SupportsIndex) -> None: - """ - setter doc token - """ - - @property - def def_property_readonly(self) -> int: - """ - getter doc token - """ - -class WithPropAndGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters and to `def_*(, doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - prop doc token - """ - - @def_property.setter - def def_property(self, arg1: typing.SupportsInt | typing.SupportsIndex) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/stl.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/stl.pyi deleted file mode 100644 index 58f6a5d2..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/stl.pyi +++ /dev/null @@ -1,34 +0,0 @@ -from __future__ import annotations - -import collections.abc -import typing - -__all__: list[str] = [ - "std_array", - "std_map", - "std_optional", - "std_variant", - "std_vector", -] - -def std_array( - arg0: typing.Annotated[ - collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex], - "FixedSize(3)", - ], -) -> typing.Annotated[list[int], "FixedSize(3)"]: ... -def std_map() -> dict[int, complex]: ... -def std_optional(arg0: typing.SupportsInt | typing.SupportsIndex | None) -> None: ... -def std_variant( - arg0: ( - typing.SupportsInt - | typing.SupportsIndex - | typing.SupportsFloat - | typing.SupportsIndex - | tuple[ - typing.SupportsInt | typing.SupportsIndex, - typing.SupportsInt | typing.SupportsIndex, - ] - ), -) -> None: ... -def std_vector() -> list[tuple[int, float]]: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/stl_bind.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/stl_bind.pyi deleted file mode 100644 index efa43c51..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/stl_bind.pyi +++ /dev/null @@ -1,162 +0,0 @@ -from __future__ import annotations - -import collections.abc -import typing - -__all__: list[str] = [ - "MapStringComplex", - "VectorPairStringDouble", - "get_complex_map", - "get_vector_of_pairs", -] - -class VectorPairStringDouble: - __hash__: typing.ClassVar[None] = None - def __bool__(self) -> bool: - """ - Check whether the list is nonempty - """ - - def __contains__( - self, x: tuple[str, typing.SupportsFloat | typing.SupportsIndex] - ) -> bool: - """ - Return true the container contains ``x`` - """ - - @typing.overload - def __delitem__(self, arg0: typing.SupportsInt | typing.SupportsIndex) -> None: - """ - Delete the list elements at index ``i`` - """ - - @typing.overload - def __delitem__(self, arg0: slice) -> None: - """ - Delete list elements using a slice object - """ - - def __eq__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __getitem__(self, s: slice) -> VectorPairStringDouble: - """ - Retrieve list elements using a slice object - """ - - @typing.overload - def __getitem__( - self, arg0: typing.SupportsInt | typing.SupportsIndex - ) -> tuple[str, float]: ... - @typing.overload - def __init__(self) -> None: ... - @typing.overload - def __init__(self, arg0: VectorPairStringDouble) -> None: - """ - Copy constructor - """ - - @typing.overload - def __init__(self, arg0: collections.abc.Iterable) -> None: ... - def __iter__(self) -> collections.abc.Iterator[tuple[str, float]]: ... - def __len__(self) -> int: ... - def __ne__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __setitem__( - self, - arg0: typing.SupportsInt | typing.SupportsIndex, - arg1: tuple[str, typing.SupportsFloat | typing.SupportsIndex], - ) -> None: ... - @typing.overload - def __setitem__(self, arg0: slice, arg1: VectorPairStringDouble) -> None: - """ - Assign list elements using a slice object - """ - - def append( - self, x: tuple[str, typing.SupportsFloat | typing.SupportsIndex] - ) -> None: - """ - Add an item to the end of the list - """ - - def clear(self) -> None: - """ - Clear the contents - """ - - def count(self, x: tuple[str, typing.SupportsFloat | typing.SupportsIndex]) -> int: - """ - Return the number of times ``x`` appears in the list - """ - - @typing.overload - def extend(self, L: VectorPairStringDouble) -> None: - """ - Extend the list by appending all the items in the given list - """ - - @typing.overload - def extend(self, L: collections.abc.Iterable) -> None: - """ - Extend the list by appending all the items in the given list - """ - - def insert( - self, - i: typing.SupportsInt | typing.SupportsIndex, - x: tuple[str, typing.SupportsFloat | typing.SupportsIndex], - ) -> None: - """ - Insert an item at a given position. - """ - - @typing.overload - def pop(self) -> tuple[str, float]: - """ - Remove and return the last item - """ - - @typing.overload - def pop(self, i: typing.SupportsInt | typing.SupportsIndex) -> tuple[str, float]: - """ - Remove and return the item at index ``i`` - """ - - def remove( - self, x: tuple[str, typing.SupportsFloat | typing.SupportsIndex] - ) -> None: - """ - Remove the first item from the list whose value is x. It is an error if there is no such item. - """ - -class MapStringComplex: - def __bool__(self) -> bool: - """ - Check whether the map is nonempty - """ - - @typing.overload - def __contains__(self, arg0: str) -> bool: ... - @typing.overload - def __contains__(self, arg0: typing.Any) -> bool: ... - def __delitem__(self, arg0: str) -> None: ... - def __getitem__(self, arg0: str) -> complex: ... - def __init__(self) -> None: ... - def __iter__(self) -> collections.abc.Iterator[str]: ... - def __len__(self) -> int: ... - def __repr__(self) -> str: - """ - Return the canonical string representation of this map. - """ - - def __setitem__( - self, - arg0: str, - arg1: typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex, - ) -> None: ... - def items(self) -> typing.ItemsView: ... - def keys(self) -> typing.KeysView: ... - def values(self) -> typing.ValuesView: ... - -def get_complex_map() -> MapStringComplex: ... -def get_vector_of_pairs() -> VectorPairStringDouble: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/typing.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/typing.pyi deleted file mode 100644 index dc505538..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/typing.pyi +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import annotations - -import collections.abc - -import typing_extensions - -__all__: list[str] = ["get_buffer", "get_sequence"] - -def get_buffer(arg0: typing_extensions.Buffer) -> typing_extensions.Buffer: ... -def get_sequence(arg0: collections.abc.Sequence) -> collections.abc.Sequence: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/values.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/values.pyi deleted file mode 100644 index 7c36d3fd..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/values.pyi +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -import datetime - -import numpy -from numpy import random - -__all__: list[str] = [ - "Dummy", - "Foo", - "add_day", - "foolist", - "foovar", - "list_with_none", - "none", - "random", - "t_10ms", - "t_20ns", - "t_30s", -] - -class Dummy: - linalg = numpy.linalg - -class Foo: - pass - -def add_day(arg0: datetime.datetime) -> datetime.datetime: ... - -foolist: list # value = [, ] -foovar: Foo # value = -list_with_none: list = [None, 2, {}] -none = None -t_10ms: datetime.timedelta # value = datetime.timedelta(microseconds=10000) -t_20ns: datetime.timedelta # value = datetime.timedelta(0) -t_30s: datetime.timedelta # value = datetime.timedelta(seconds=30) diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/core.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/core.pyi deleted file mode 100644 index 95c16467..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/core.pyi +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/__init__.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/__init__.pyi deleted file mode 100644 index 10d343e0..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/__init__.pyi +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from . import classes, functions, functions_3_8_plus, functions_3_9_plus, values - -__all__: list[str] = [ - "classes", - "functions", - "functions_3_8_plus", - "functions_3_9_plus", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/classes.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/classes.pyi deleted file mode 100644 index 32aa068b..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/classes.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["A", "B", "C", "X"] - -class A: - """ - A - """ - -class B(A): - """ - B - """ - -class C(B): - """ - C - """ - -class X: - pass diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions.pyi deleted file mode 100644 index 5ed9ef3c..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions.pyi +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import annotations - -import sys as sys -import typing as typing - -from demo.pure_python.functions_3_8_plus import args_mix -from demo.pure_python.functions_3_9_plus import generic_alias_annotation - -__all__: list[str] = [ - "accept_frozenset", - "args_mix", - "builtin_function_as_default_arg", - "function_as_default_arg", - "generic_alias_annotation", - "lambda_as_default_arg", - "search", - "static_method_as_default_arg", - "sys", - "typing", -] - -class _Dummy: - @staticmethod - def foo(): ... - -def accept_frozenset(arg: frozenset[int | float]) -> int | None: ... -def builtin_function_as_default_arg(func: type(len) = len): ... -def function_as_default_arg(func: type(search) = search): ... -def lambda_as_default_arg(callback=...): ... -def search(a: int, b: list[int]) -> int: ... -def static_method_as_default_arg(callback=_Dummy.foo): ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions_3_8_plus.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions_3_8_plus.pyi deleted file mode 100644 index c55aed1d..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions_3_8_plus.pyi +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import typing as typing - -__all__: list[str] = [ - "Expression", - "Token", - "args_mix", - "nested_current_module_annotations", - "typing", -] - -class Token: - pass - -class Expression: - pass - -def args_mix( - a: int, - b: float = 0.5, - c: str = "", - *args: int, - x: int = 1, - y=int, - **kwargs: dict[int, str], -): ... -def nested_current_module_annotations( - tokens: list[Token], expr: Expression | None = None -) -> dict[str, Expression]: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions_3_9_plus.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions_3_9_plus.pyi deleted file mode 100644 index 59b9b80c..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions_3_9_plus.pyi +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["generic_alias_annotation"] - -def generic_alias_annotation(a: list[tuple[int]], b: dict[int, str]) -> list[float]: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/values.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/values.pyi deleted file mode 100644 index 07913391..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/values.pyi +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["callables_dict"] -callables_dict: dict = {"len": len, "int": int} diff --git a/tests/stubs/python-3.8/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi b/tests/stubs/python-3.8/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi deleted file mode 100644 index 9ea9beb4..00000000 --- a/tests/stubs/python-3.8/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from . import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.8/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi b/tests/stubs/python-3.8/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi deleted file mode 100644 index ffc6c84f..00000000 --- a/tests/stubs/python-3.8/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import annotations - -import types - -__all__: list[str] = ["mapping_proxy"] -mapping_proxy: types.MappingProxyType # value = mappingproxy({}) From 565522d032cd0c429a2259fd2ad0aaadd1a3e60f Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 24 Sep 2026 03:52:35 +0000 Subject: [PATCH 02/40] docs: specify phase-one pytest harness migration --- ...026-09-24-test-harness-phase-one-design.md | 320 ++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-24-test-harness-phase-one-design.md diff --git a/docs/superpowers/specs/2026-09-24-test-harness-phase-one-design.md b/docs/superpowers/specs/2026-09-24-test-harness-phase-one-design.md new file mode 100644 index 00000000..eb05bc99 --- /dev/null +++ b/docs/superpowers/specs/2026-09-24-test-harness-phase-one-design.md @@ -0,0 +1,320 @@ +# Phase 1: Non-mutating pytest test harness + +Date: 2026-09-24 + +Status: Design approved in chat; written specification awaiting review. + +Repository baseline: `5057f41` (`Prepare 3.0 release`). + +## Goal + +Make checking generated stubs a normal, non-mutating test operation, distinct +from deliberately updating reference outputs. Preserve the existing native demo, +compatibility coverage, and snapshot layout during this migration. + +This document specifies phase 1. Later phases are a roadmap, not implementation +requirements for this change. No production stub-generation behavior is changed. + +## Current behavior and motivation + +`tox.ini` and `.github/workflows/ci.yml` select Python, pybind11, and NumPy-format +configurations. `tests/install-demo-module.sh` builds and installs the native +fixture. Two shell scripts then check generated stubs and error messages. + +The stub checker writes into `tests/stubs`, formats the output with Ruff, stages +it with Git, and compares it against `HEAD`. The error checker attempts an +analogous stderr comparison, but its path resolver selects the wrong destination. +This mixes verification with reference updates and makes test results depend on +repository state. + +The investigation also identified incorrect path handling and exit-status +checking in the error script, and a quoted wildcard that prevents the stub +script from clearing old output. The replacement must test these failure modes, +not reproduce the scripts literally. + +## Scope and approach + +Use ordinary pytest tests and a small shared snapshot helper. Do not add a +snapshot plugin or retain the shell checkers behind pytest wrappers. + +- A plugin could reduce comparison code, but introduces another abstraction for + the existing directory snapshots and aliases. +- Shell wrappers would minimize migration work but retain the unsafe behavior. +- A small Python helper makes path resolution, comparison, and update behavior + explicit and independently testable. + +### Included + +- Non-mutating comparisons of generated stubs and error messages. +- Explicit, narrowly scoped snapshot updates. +- Subprocess status assertions and failure diagnostics. +- Compiler-free tests of the harness itself. +- Minimal pytest wiring in tox and CI, including failure artifacts. +- Contributor documentation for checking, updating, and adding regressions. + +### Excluded + +- Native build restructuring or dependency-version harmonization. +- Removing matrix configurations or combining NumPy modes into one build. +- Snapshot deduplication, renaming, or replacement of existing directory aliases. +- Broad unit coverage of production parser/printer code. +- Changes to the `gemmi` smoke-test job or production CLI options. +- New platform support or parallel local native builds. + +## Components and responsibilities + +| File | Responsibility | +| --- | --- | +| `tests/conftest.py` | Register test options; supply configuration, subprocess, temporary-directory, and artifact fixtures. | +| `tests/snapshot_helpers.py` | Resolve permitted snapshot destinations, compare output, normalize results, and perform explicit updates. Keep functions small and testable without the native demo. | +| `tests/test_snapshot_helpers.py` | Exercise comparison, update, path-safety, normalization, and subprocess-contract behavior using synthetic fixtures. | +| `tests/test_demo_stubs.py` | Generate successful demo stubs and compare the complete output tree. | +| `tests/test_demo_errors.py` | Assert the error exit code, normalized stderr, and absence of generated stubs. | +| `pyproject.toml`, `uv.lock`, `tox.ini` | Declare pytest and configure its invocation. | +| `.github/workflows/ci.yml` | Replace shell checks with pytest and upload diagnostics on failure. | +| `README.md`, `tests/README.md` | Explain the new workflows and fixture organization. | + +Remove `tests/check-demo-stubs-generation.sh` and +`tests/check-demo-errors-generation.sh` once their callers have migrated. Do not +maintain two implementations of comparison or updating. + +Keep `tests/install-demo-module.sh`, the demo sources, and their build +configuration unchanged. Test helpers must not import or build the native demo +when only harness self-tests are selected. + +## Configuration and preserved coverage + +One pytest integration invocation targets one configuration. The actual Python +interpreter supplies the Python version; callers supply these options: + +- `--pybind11-branch`, using the existing values such as `v3.0`. +- `--numpy-format`, using `numpy-array-wrap-with-annotated` or + `numpy-array-use-type-var`. +- `--update-snapshots`, absent by default. +- `--artifacts-dir`, an optional destination outside the reference trees. + +Native tests require explicit configuration and an installed matching demo. +Missing configuration or an unavailable demo is an actionable failure, not a +silent skip or an automatic build. Harness self-tests need neither native +configuration nor the demo. + +A supported profile uses a recognized branch and NumPy format and has reference +directories for the running, supported Python version. The following table +records existing CI/tox coverage, not an additional matrix to maintain in test +code. Preserve all 13 existing integration configurations: + +| pybind11 branch | Python versions | NumPy format | Cases | +| --- | --- | --- | --- | +| `v3.0` | 3.10, 3.11, 3.12, 3.13 | annotated | 4 | +| `v3.0` | 3.13 | type variable | 1 | +| `v2.13` | 3.10, 3.11, 3.12, 3.13 | annotated | 4 | +| `v2.13` | 3.13 | type variable | 1 | +| `v2.12` | 3.13 | annotated | 1 | +| `v2.11` | 3.13 | annotated | 1 | +| `v2.9` | 3.13 | annotated | 1 | + +Use the existing expectation paths: + +- Stubs: `tests/stubs/python-X.Y/pybind11-BRANCH/NUMPY_FORMAT/`. +- Errors: `tests/errors/pybind11-BRANCH/demo.errors.stderr.txt`. + +Resolve configured directory aliases deliberately, preserving them on disk. +Unsupported configurations and absent profile directories fail clearly; there +is no fallback to a nearby version. This phase does not add new profiles. + +## Check-mode data flow + +1. Resolve the selected reference path and validate its containment in the + appropriate reference root. +2. Create a fresh temporary workspace for each integration test, separate from + reference files and native build directories. +3. Run the installed generator in the pytest interpreter's environment. Use + `sys.executable -I -m pybind11_stubgen` with the temporary working directory so + CI exercises the installed wheel rather than importing the checkout through + the working directory or `PYTHONPATH`. Do not use `uv run` inside the helper, + where it could reinstall the project over the wheel being tested. +4. Capture the command, exit status, stdout, stderr, and generated files. +5. Validate the process contract, normalize applicable results, and compare + against the working-tree reference files. Comparison never consults Git or + `HEAD`. +6. On failure, report the configuration and relevant differences, and retain + diagnostics in the temporary workspace or requested artifact directory. + +The successful-generation test retains the existing CLI arguments, including +error-ignore expressions, enum locations, safe-value representations, NumPy +format, value comments, and `--exit-code`. It requires status `0`, then compares +both the complete relative file set and file contents. + +The error test retains the existing error-producing invocation, requires status +`1`, compares normalized stderr, and verifies that its output location contains +no generated stubs. Before comparison or updating, require the generator's +`Terminating due to previous errors` diagnostic and reject Python tracebacks. +An absent executable, import traceback, status `0`, or any other unexpected +status must not be accepted as the intended error behavior, including in update +mode. + +Comparisons must detect added, missing, changed, and unexpected files. Compare +normalized bytes; decode text for readable diffs. Tool caches and diagnostic +files belong outside the generated-output tree. Neither +passing nor failing checks may write to reference trees or the Git index. + +## Normalization and diagnostics + +Preserve the current normalization policy: + +- Resolve the Ruff version from the existing pre-commit pin; do not introduce a + second independent pin in phase 1. +- Apply Ruff formatting, then the `I,RUF022` fixes, with the running interpreter's + target version. +- Make repository Ruff configuration explicit when running in temporary + directories so changing the working directory does not silently change + formatting. Keep Ruff's cache outside the compared output tree. +- Replace hexadecimal addresses in captured error messages with + `0x1234abcd5678`, matching the existing normalization. +- Do not add broad sorting, annotation rewriting, or other normalization that + could hide generator regressions. + +A process-contract violation or formatting failure fails the test before +accepting output or updating its expectations. Include captured diagnostics even +if no output tree was produced. Apply a 300-second timeout to the generator and +each formatting subprocess, reporting the command and captured output on timeout. +This timeout does not change the native build's execution policy. + +Failure reports include the logical configuration, resolved reference location, +missing/extra filenames, and unified content diffs. With `--artifacts-dir`, retain +available generated output, process diagnostics, and diffs under separate +configuration/test paths. Validate that this destination is outside both +reference roots before writing to it. + +CI will use `tmp/pytest-artifacts/`, already covered by the repository's `tmp` +ignore rule, and upload diagnostics after either integration test fails. Nothing +is written alongside the checked-in snapshots as an artifact. + +## Explicit update semantics + +`--update-snapshots` changes only the behavior after an individual test has +successfully generated and validated its output. It does not relax status, +normalization, path-safety, or no-output assertions. + +For each selected snapshot: + +1. Resolve its existing profile directory under `tests/stubs` or `tests/errors`. + Reject paths that escape the permitted root, including symlink escapes. +2. Report the logical profile and canonical destination. Explain in the docs + that directory aliases share expectations with other logical profiles. +3. Validate the generated file tree and destination before modifying references. + Reject unexpected symlinks within generated output or the selected snapshot + contents; existing directory aliases in the profile path remain supported. +4. Copy validated content into that destination. For stub trees, remove stale + reference files belonging to that selected tree so its file set exactly + matches the generated tree. For errors, update only the selected stderr file. +5. Preserve directory aliases, unrelated profiles, and unrelated repository + files. Never stage or commit anything. +6. Report changed paths so the developer can review the resulting Git diff. + +Updates are per selected test, not a transaction across an entire pytest session +or tox matrix. A later failure does not undo updates made by an earlier successful +test. A test with failed generation or normalization must not update its own +references. Document this boundary rather than promising global rollback. + +Run updates serially. Existing native build directories and aliased expectations +are shared; this phase does not make `tox -p` or pytest worker parallelism safe. +CI runs in check mode only. + +## Local and CI integration + +Add a Python-3.10-compatible pytest dependency to the existing development and +tox dependency declarations, and update `uv.lock`. Keeping these declarations in +two places is intentional for phase 1; consolidation belongs to phase 3. + +Tox continues to build the demo with the existing script and install the project +as before. Replace shell checks with pytest, pass the branch and NumPy format +explicitly, and forward user test arguments. Remove the obsolete Python-tag +exchange file because pytest can read its own interpreter version. Change tox's +description from regeneration to checking. + +CI keeps its existing matrix, CMake choices, native build steps, wheel +installation, and `gemmi` job. Run both new integration tests in one pytest +invocation without fail-fast behavior, so a stub mismatch does not suppress the +error check. Run harness self-tests as well, and upload failure artifacts after +pytest. Do not invoke a project-resolving wrapper after installing the wheel. + +The contributor documentation will present these target workflows after +implementation (these are not commands supported by the current harness): + +```sh +# Harness self-tests: requires pytest, but no C++ build or installed demo. +python -m pytest tests/test_snapshot_helpers.py + +# Build and check one native configuration. +tox -e py313-pb30-naa + +# Explicitly update that configuration, then review the changes. +tox -e py313-pb30-naa -- --update-snapshots +git diff -- tests/stubs tests/errors + +# Check all configured environments, serially. +tox +``` + +Also document environment/tool installation, selecting an individual integration +test, rerunning pytest against an already installed demo, where failure artifacts +are stored, and how to add a regression fixture and update only its expectations. +The README should link to `tests/README.md` rather than duplicate the full guide. + +## Verification and acceptance criteria + +Harness tests use synthetic files and subprocess results, not the native demo. +They must establish: + +- Identical output passes; changed, missing, and extra files fail with useful + diagnostics, including filenames containing spaces and nested paths. +- Passing and failing checks preserve reference content and the Git index, + including pre-existing developer changes. +- Explicit updates add, replace, and remove only the selected reference files; + unrelated profiles and files remain unchanged. +- Alias chains resolve correctly, aliases survive updates, and escaping paths, + unsupported profiles, and unexpected nested symlinks are rejected safely. +- Unexpected statuses, absent commands, tracebacks, timeouts, and formatting + failures cannot be accepted by update mode; the error test also rejects + generated stubs and missing fatal-diagnostic termination markers. +- Address normalization is narrow and deterministic, and normalization command + failures retain useful diagnostics. +- Artifact collection cannot write into either reference root. +- Harness self-tests run with pytest alone, without native fixture setup. + +Validate representative native configurations while developing the migration, +then exercise all 13 preserved configurations in CI before merging. Verify that +CI still tests the installed wheel and that the existing `gemmi` job remains +unchanged. Check snapshot content and index state before and after check-mode +runs, including a deliberately induced mismatch in an isolated test fixture. + +Do not assume existing snapshots are correct merely because the old scripts +appeared to pass. The error checker could miss regressions and stale stub files +could survive cleanup. Investigate newly exposed differences individually. Any +necessary reference correction must be explained and reviewed separately from +the mechanical harness migration, not accepted by a blanket regeneration. + +## Later-phase roadmap + +### Phase 2: Compiler-free production tests + +Add focused parser, annotation, class-ordering, printer, and writer tests. +Separate pure-Python fixtures from the package that imports native bindings. +Retain real pybind11 integration coverage. + +### Phase 3: Shared orchestration and simpler native builds + +Unify local/CI configuration and dependency choices. Run NumPy modes against a +shared compiled fixture. Remove the redundant standalone demo-library build, +isolate build directories by environment, and evaluate a standard build backend +and pinned pybind11 releases. Preserve installed-wheel coverage. + +### Phase 4: Snapshot organization + +Share identical expectations and use explicit variants only where outputs +genuinely differ. Preserve compatibility coverage and avoid a complex fallback +or overlay framework. Do this only after the new harness gives trustworthy, +non-mutating comparisons. + +Each later phase requires its own scoped design and implementation plan. From 082ba528f26fcaa190205dfe87e18bda55f59000 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 24 Sep 2026 04:26:41 +0000 Subject: [PATCH 03/40] docs: plan phase-one pytest harness implementation --- .../2026-09-24-test-harness-phase-one.md | 1371 +++++++++++++++++ ...026-09-24-test-harness-phase-one-design.md | 4 +- 2 files changed, 1374 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/plans/2026-09-24-test-harness-phase-one.md diff --git a/docs/superpowers/plans/2026-09-24-test-harness-phase-one.md b/docs/superpowers/plans/2026-09-24-test-harness-phase-one.md new file mode 100644 index 00000000..c61bedf4 --- /dev/null +++ b/docs/superpowers/plans/2026-09-24-test-harness-phase-one.md @@ -0,0 +1,1371 @@ +# Phase-One Pytest Harness Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace mutating shell snapshot checks with independently tested pytest checks and explicit reference updates, without changing native builds or compatibility coverage. + +**Architecture:** A standard-library-only helper module handles snapshot paths, byte comparisons, guarded updates, subprocess diagnostics, normalization, and artifact retention. Thin pytest fixtures select an existing profile, and two integration tests invoke the installed generator outside the checkout. Tox and CI retain their environments and build steps, changing only how checks run. + +**Tech Stack:** Python >=3.10, pytest >=8,<9, pathlib, subprocess, difflib, existing uv/tox tooling, existing Ruff pre-commit pin, GitHub Actions. + +**Spec:** `docs/superpowers/specs/2026-09-24-test-harness-phase-one-design.md` (approved in chat). + +## Global Constraints + +- "No production stub-generation behavior is changed." +- "Keep `tests/install-demo-module.sh`, the demo sources, and their build configuration unchanged." +- "Preserve all 13 existing integration configurations." +- "Comparison never consults Git or `HEAD`." +- "Compare normalized bytes; decode text for readable diffs." +- "Never stage or commit anything." This applies to the test harness; implementation commits below are developer actions. +- "Apply a 300-second timeout to the generator and each formatting subprocess." +- "Run updates serially." +- "CI runs in check mode only." +- "A test with failed generation or normalization must not update its own references." +- Preserve directory aliases and the current Ruff/address normalization policy; do not deduplicate snapshots. +- Require explicit native-test configuration and an installed demo; never silently skip native checks or build at collection time. +- Use `sys.executable -I -m pybind11_stubgen` from temporary directories. Never use `uv run` inside subprocess helpers or CI after wheel installation. +- Keep the `gemmi` job, existing matrix/CMake choices, and native dependency versions unchanged. +- Harness self-tests require pytest and the standard library only, not the demo, Git, Ruff, or a compiler. +- Until Task 6 rewires tox, **do not run the old tox commands or shell checkers against real reference trees**: they mutate and stage snapshots. +- Code blocks below are implementation instructions, not changes already made or tests already run. + +--- + +## Execution preparation and file map + +At execution time, use `superpowers:using-git-worktrees` to establish an isolated workspace. Read the spec, this plan, and any repository instructions there. Record initial Git status and do not overwrite existing developer changes. Do not run the legacy test suite as a baseline; use the compiler-free commands below until its callers are replaced. + +| File | Boundary | +| --- | --- | +| `tests/snapshot_helpers.py` | Small functions for filesystem snapshots, subprocesses, normalization, artifacts, and profile metadata. No production-module imports or pytest dependency. | +| `tests/test_snapshot_helpers.py` | Synthetic regression tests for each helper and the integration-test orchestration. | +| `tests/conftest.py` | Four CLI options and lazy fixtures; no native imports, subprocesses, or builds during collection. | +| `tests/test_demo_stubs.py` | Successful generator contract, Ruff normalization, then compare/update. | +| `tests/test_demo_errors.py` | Fatal-error contract, address normalization, then compare/update one stderr file. | +| `pyproject.toml`, `uv.lock` | pytest dependency and test discovery settings. | +| `tox.ini` | Existing environment matrix/build plus pytest invocation and argument forwarding. | +| `.github/workflows/ci.yml` | Existing wheel/native setup plus pytest and artifact upload. | +| `README.md`, `tests/README.md` | Short contributor entry point and complete testing guide. | +| Two legacy `tests/check-demo-*.sh` scripts | Remove only after callers migrate. | + +Do not create `tests/__init__.py`. Use pytest's default import mode so sibling test modules can import `snapshot_helpers`. The generator subprocess is isolated from these test-module imports. + +Tasks 1–4 grow the shared helper in small, reviewed increments; merge each new import into its import block rather than duplicating imports. Keep subprocesses confined to `run_command`; filesystem comparisons must never invoke Git. + +## Task 1: Establish compiler-free, read-only snapshot comparisons + +**Files:** +- Create: `tests/snapshot_helpers.py`, `tests/test_snapshot_helpers.py`. +- Modify: `pyproject.toml` (`dependency-groups.dev`, new pytest configuration), `uv.lock`. + +**Interfaces:** +- Consumes: Python standard library, pytest. +- Produces: `HarnessError(AssertionError)`, `Snapshot = dict[str, bytes]`. +- Produces: `relative_file(name: str) -> Path`, `resolve_profile(root: Path, profile: Path) -> Path`. +- Produces: `read_tree(root: Path) -> Snapshot`, `diff_tree(expected: Snapshot, actual: Snapshot) -> str`. +- Test utility: `write_files(root: Path, files: dict[str, bytes]) -> None`. + +- [ ] **Step 1: Add pytest and explicit test discovery.** Add `"pytest>=8,<9"` to the existing dev group; preserve all existing dependencies. Append: + +```toml +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = "-ra" +``` + +Run `uv lock` without `--upgrade`; review the lock diff for unrelated dependency changes. Use this project-independent command for all compiler-free red/green cycles below: + +```sh +uv run --no-project --with 'pytest>=8,<9' python -m pytest tests/test_snapshot_helpers.py -q +``` + +- [ ] **Step 2: Write the comparison/path tests before the helper exists.** Create `tests/test_snapshot_helpers.py`: + +```python +from pathlib import Path + +import pytest + +import snapshot_helpers as h + + +def write_files(root: Path, files: dict[str, bytes]) -> None: + root.mkdir(parents=True, exist_ok=True) + for name, content in files.items(): + path = root / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(content) + + +def test_diff_detects_changed_missing_and_unexpected_files(): + expected = {"nested/a file.pyi": b"old\n", "gone.pyi": b"x\n"} + actual = {"nested/a file.pyi": b"new\n", "extra.pyi": b"y\n"} + assert h.diff_tree(expected, expected) == "" + difference = h.diff_tree(expected, actual) + assert "Changed: nested/a file.pyi" in difference + assert "Missing: gone.pyi" in difference + assert "Unexpected: extra.pyi" in difference + assert "-old" in difference and "+new" in difference + assert h.diff_tree({"x": b"x\r\n"}, {"x": b"x\n"}) + + +def test_read_tree_preserves_bytes_and_rejects_links(tmp_path): + write_files(tmp_path, {"sub/a.pyi": b"\xff\r\n"}) + assert h.read_tree(tmp_path) == {"sub/a.pyi": b"\xff\r\n"} + assert h.read_tree(tmp_path / "absent") == {} + (tmp_path / "linked.pyi").symlink_to("sub/a.pyi") + with pytest.raises(h.HarnessError, match="symlink"): + h.read_tree(tmp_path) + + +def test_profile_aliases_resolve_but_escapes_and_missing_profiles_fail(tmp_path): + refs = tmp_path / "refs" + write_files(refs / "real", {"x.pyi": b"x"}) + (refs / "alias").symlink_to("real", target_is_directory=True) + (refs / "chain").symlink_to("alias", target_is_directory=True) + assert h.resolve_profile(refs, Path("chain")) == refs / "real" + outside = tmp_path / "outside" + outside.mkdir() + (refs / "escape").symlink_to(outside, target_is_directory=True) + for name in ("escape", "missing", "../outside"): + with pytest.raises(h.HarnessError): + h.resolve_profile(refs, Path(name)) + + +@pytest.mark.parametrize("name", ["", ".", "..", "../x", "/x", "a/../x", "a\\x"]) +def test_relative_file_rejects_unsafe_names(name): + with pytest.raises(h.HarnessError): + h.relative_file(name) +``` + +- [ ] **Step 3: Run the compiler-free command.** Expected red result: collection fails because `snapshot_helpers` does not exist. Confirm the failure reason before proceeding. + +- [ ] **Step 4: Implement the read-only helper.** Create `tests/snapshot_helpers.py`: + +```python +from __future__ import annotations + +import difflib +from pathlib import Path, PurePosixPath + +Snapshot = dict[str, bytes] + + +class HarnessError(AssertionError): + """An actionable test-harness or comparison failure.""" + + +def relative_file(name: str) -> Path: + path = PurePosixPath(name) + if ( + not name + or not path.parts + or path.is_absolute() + or ".." in path.parts + or "\\" in name + or path.as_posix() != name + ): + raise HarnessError(f"Unsafe snapshot name: {name!r}") + return Path(*path.parts) + + +def resolve_profile(root: Path, profile: Path) -> Path: + relative_file(profile.as_posix()) + try: + base = root.resolve(strict=True) + destination = (base / profile).resolve(strict=True) + except (OSError, RuntimeError) as error: + raise HarnessError(f"Unavailable snapshot profile: {root / profile}") from error + if not destination.is_relative_to(base) or destination == base: + raise HarnessError(f"Snapshot profile escapes root: {root / profile}") + if not destination.is_dir(): + raise HarnessError(f"Snapshot profile is not a directory: {destination}") + return destination + + +def read_tree(root: Path) -> Snapshot: + if root.is_symlink(): + raise HarnessError(f"Unexpected symlink: {root}") + if not root.exists(): + return {} + if not root.is_dir(): + raise HarnessError(f"Expected directory: {root}") + result = {} + for path in sorted(root.rglob("*")): + if path.is_symlink(): + raise HarnessError(f"Unexpected symlink: {path}") + if path.is_file(): + result[path.relative_to(root).as_posix()] = path.read_bytes() + elif not path.is_dir(): + raise HarnessError(f"Unexpected filesystem entry: {path}") + return result + + +def diff_tree(expected: Snapshot, actual: Snapshot) -> str: + differences = [] + for name in sorted(expected.keys() | actual.keys()): + if name in expected and name in actual and expected[name] == actual[name]: + continue + kind = "Missing" if name not in actual else "Unexpected" if name not in expected else "Changed" + differences.append(f"{kind}: {name}\n") + before = expected.get(name, b"").decode("utf-8", errors="backslashreplace") + after = actual.get(name, b"").decode("utf-8", errors="backslashreplace") + differences.extend(difflib.unified_diff( + before.splitlines(keepends=True), after.splitlines(keepends=True), + fromfile=f"expected/{name}", tofile=f"actual/{name}", + )) + return "".join(differences) +``` + +- [ ] **Step 5: Rerun the compiler-free command.** Expected: all Task 1 tests pass, without native imports or filesystem writes beyond synthetic temporary fixtures. +- [ ] **Step 6: Commit the independently tested comparison layer.** + +```sh +git add pyproject.toml uv.lock tests/snapshot_helpers.py tests/test_snapshot_helpers.py +git commit -m "test: add read-only snapshot comparison helpers" +``` + +## Task 2: Add narrowly scoped updates and mutation regressions + +**Files:** Modify `tests/snapshot_helpers.py`, `tests/test_snapshot_helpers.py`. + +**Interfaces:** +- Consumes: `Snapshot`, `HarnessError`, `relative_file`, `resolve_profile`, `read_tree`, `diff_tree` from Task 1. +- Produces: `SnapshotMismatch(HarnessError)`. +- Produces: `check_snapshot(root: Path, profile: Path, actual: Snapshot, *, update: bool = False, only: frozenset[str] | None = None) -> tuple[str, ...]`. +- Return value: changed relative filenames when updating, or an empty tuple when already equal. A check-mode mismatch raises `SnapshotMismatch` with the diff. +- `only` restricts error snapshots to their stderr filename; it must not delete other files in that profile. + +**Self-test command for this task (from the repository root):** + +```sh +uv run --no-project --with 'pytest>=8,<9' python -m pytest tests/test_snapshot_helpers.py -q +``` + +- [ ] **Step 1: Add update and mutation tests.** Append: + +```python +@pytest.mark.parametrize("update", [False, True]) +def test_check_update_preserve_aliases_unrelated_files_and_index(tmp_path, update): + refs = tmp_path / "refs" + write_files(refs / "real", {"a.pyi": b"developer edit", "old.pyi": b"old"}) + write_files(refs / "unrelated", {"keep.pyi": b"keep"}) + (refs / "alias").symlink_to("real", target_is_directory=True) + write_files(tmp_path / ".git", {"index": b"pre-existing staged state"}) + before = h.read_tree(refs / "real") + actual = {"a.pyi": b"new", "nested/new.pyi": b"added"} + if update: + changed = h.check_snapshot(refs, Path("alias"), actual, update=True) + assert set(changed) == {"a.pyi", "old.pyi", "nested/new.pyi"} + assert h.read_tree(refs / "real") == actual + else: + assert h.check_snapshot(refs, Path("alias"), before) == () + with pytest.raises(h.SnapshotMismatch): + h.check_snapshot(refs, Path("alias"), actual) + assert h.read_tree(refs / "real") == before + assert (refs / "alias").is_symlink() + assert h.read_tree(refs / "unrelated") == {"keep.pyi": b"keep"} + assert (tmp_path / ".git/index").read_bytes() == b"pre-existing staged state" + + +def test_stderr_update_is_scoped_to_one_file(tmp_path): + write_files(tmp_path / "case", {"stderr.txt": b"old", "unrelated.txt": b"keep"}) + h.check_snapshot(tmp_path, Path("case"), {"stderr.txt": b"new"}, + update=True, only=frozenset({"stderr.txt"})) + assert h.read_tree(tmp_path / "case") == { + "stderr.txt": b"new", "unrelated.txt": b"keep", + } + + +@pytest.mark.parametrize("actual", [ + {"../escape": b"bad"}, {"x": b"file", "x/child": b"collision"}, + {"x": "not bytes"}, +]) +def test_update_validates_all_content_before_writing(tmp_path, actual): + write_files(tmp_path / "case", {"keep.pyi": b"original"}) + with pytest.raises(h.HarnessError): + h.check_snapshot(tmp_path, Path("case"), actual, update=True) + assert h.read_tree(tmp_path / "case") == {"keep.pyi": b"original"} + + +def test_update_rejects_nested_reference_symlinks(tmp_path): + write_files(tmp_path / "case", {"keep.pyi": b"original"}) + (tmp_path / "case/link.pyi").symlink_to("keep.pyi") + with pytest.raises(h.HarnessError, match="symlink"): + h.check_snapshot(tmp_path, Path("case"), {"keep.pyi": b"new"}, update=True) + assert (tmp_path / "case/keep.pyi").read_bytes() == b"original" + + +def test_update_handles_file_directory_shape_changes(tmp_path): + write_files(tmp_path / "case", {"old/child": b"old", "flat": b"old"}) + actual = {"old": b"now a file", "flat/child": b"now nested"} + h.check_snapshot(tmp_path, Path("case"), actual, update=True) + assert h.read_tree(tmp_path / "case") == actual + + +def test_scoped_update_rejects_extra_actual_files_before_writing(tmp_path): + write_files(tmp_path / "case", {"stderr.txt": b"original", "unrelated": b"keep"}) + with pytest.raises(h.HarnessError, match="scope"): + h.check_snapshot(tmp_path, Path("case"), {"stderr.txt": b"new", "extra": b"bad"}, + update=True, only=frozenset({"stderr.txt"})) + assert h.read_tree(tmp_path / "case") == {"stderr.txt": b"original", "unrelated": b"keep"} +``` + +The synthetic `.git/index` sentinel keeps self-tests independent of Git. Task 6 separately checks the real repository index and references around native runs. + +- [ ] **Step 2: Run the compiler-free command.** Expected red result: the new tests fail on missing `check_snapshot`/`SnapshotMismatch`; Task 1 tests stay green. +- [ ] **Step 3: Implement validation, comparison, and update in that order.** Append: + +```python +class SnapshotMismatch(HarnessError): + """Generated output differs from its reference.""" + + +def check_snapshot( + root: Path, profile: Path, actual: Snapshot, *, + update: bool = False, only: frozenset[str] | None = None, +) -> tuple[str, ...]: + for name, content in actual.items(): + path = relative_file(name) + if not isinstance(content, bytes): + raise HarnessError(f"Snapshot content must be bytes: {name}") + if any(parent.as_posix() in actual for parent in path.parents): + raise HarnessError(f"Conflicting snapshot paths: {name}") + if only is not None: + for name in only: + relative_file(name) + if actual.keys() - only: + raise HarnessError("Actual output exceeds the selected snapshot scope") + destination = resolve_profile(root, profile) + expected = read_tree(destination) + if only is not None: + for name in only: + target = destination / relative_file(name) + if target.exists() and not target.is_file(): + raise HarnessError(f"Scoped reference is not a file: {target}") + expected = {name: content for name, content in expected.items() if name in only} + difference = diff_tree(expected, actual) + if not difference: + return () + if not update: + raise SnapshotMismatch(difference) + changed = tuple(sorted( + name for name in expected.keys() | actual.keys() + if name not in expected or name not in actual or expected[name] != actual[name] + )) + for name in expected.keys() - actual.keys(): + (destination / relative_file(name)).unlink() + if only is None: + directories = [path for path in destination.rglob("*") if path.is_dir()] + for directory in sorted(directories, key=lambda path: len(path.parts), reverse=True): + try: + directory.rmdir() + except OSError: + # Nonempty directories contain retained reference files. + continue + for name, content in actual.items(): + target = destination / relative_file(name) + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(content) + return changed +``` + +Do not add Git calls, whole-repository cleanup, alias replacement, or an automatic fallback profile. I/O failures propagate; this is not a transaction across tests or the matrix. + +- [ ] **Step 4: Rerun all harness self-tests.** Expected: green. Inspect the helper to confirm all content/path validation occurs before unlink/write operations. +- [ ] **Step 5: Commit.** + +```sh +git add tests/snapshot_helpers.py tests/test_snapshot_helpers.py +git commit -m "test: add explicit scoped snapshot updates" +``` + +## Task 3: Add strict subprocess contracts and existing normalization + +**Files:** Modify `tests/snapshot_helpers.py`, `tests/test_snapshot_helpers.py`. + +**Interfaces:** +- Consumes: `HarnessError`, `read_tree`. +- Produces: `run_command(argv: list[str], *, cwd: Path, log: Path, expected_status: int = 0) -> subprocess.CompletedProcess[bytes]`. +- Produces: `normalize_stderr(stderr: bytes) -> bytes`, `validate_error_run(result: subprocess.CompletedProcess[bytes], output: Path) -> bytes`. +- Produces: `ruff_version(repo: Path) -> str`, `format_stubs(output: Path, repo: Path, workspace: Path, python_version: tuple[int, int]) -> None`. +- Every process writes `.json`, `.stdout`, `.stderr` before reporting a failure. No process runs through a shell. + +**Self-test command for this task (from the repository root):** + +```sh +uv run --no-project --with 'pytest>=8,<9' python -m pytest tests/test_snapshot_helpers.py -q +``` + +- [ ] **Step 1: Add process and fatal-error tests.** Merge these imports into the self-test file: `import json`, `import subprocess`, `import sys`. Append: + +```python +@pytest.mark.parametrize("status", [0, 1, 2, 127]) +def test_run_command_requires_exact_status_and_retains_logs(tmp_path, status): + command = [sys.executable, "-c", f"import sys; print('diagnostic', file=sys.stderr); sys.exit({status})"] + if status == 1: + result = h.run_command(command, cwd=tmp_path, log=tmp_path / "process", expected_status=1) + assert result.returncode == 1 + else: + with pytest.raises(h.HarnessError, match="expected 1"): + h.run_command(command, cwd=tmp_path, log=tmp_path / "process", expected_status=1) + assert b"diagnostic" in (tmp_path / "process.stderr").read_bytes() + assert json.loads((tmp_path / "process.json").read_text())["returncode"] == status + + +def test_missing_command_and_timeout_are_diagnostic(tmp_path, monkeypatch): + with pytest.raises(h.HarnessError): + h.run_command([str(tmp_path / "missing-command")], cwd=tmp_path, log=tmp_path / "missing") + assert (tmp_path / "missing.json").is_file() + + def timeout(argv, **kwargs): + assert kwargs["timeout"] == 300 + raise subprocess.TimeoutExpired(argv, 300, output=b"partial", stderr=b"stuck") + + monkeypatch.setattr(h.subprocess, "run", timeout) + with pytest.raises(h.HarnessError, match="300"): + h.run_command(["fake"], cwd=tmp_path, log=tmp_path / "timeout") + assert (tmp_path / "timeout.stdout").read_bytes() == b"partial" + assert (tmp_path / "timeout.stderr").read_bytes() == b"stuck" + + +@pytest.mark.parametrize("status,stderr,files", [ + (0, b"Terminating due to previous errors", {}), + (2, b"Terminating due to previous errors", {}), + (1, b"Traceback (most recent call last):\nTerminating due to previous errors", {}), + (1, b"unrelated failure", {}), + (1, b"Terminating due to previous errors", {"unexpected.pyi": b"x"}), +]) +def test_invalid_fatal_error_runs_are_rejected(tmp_path, status, stderr, files): + write_files(tmp_path / "output", files) + result = subprocess.CompletedProcess(["stubgen"], status, b"", stderr) + with pytest.raises(h.HarnessError): + h.validate_error_run(result, tmp_path / "output") + + +def test_error_normalization_is_narrow(tmp_path): + stderr = b"object at 0xAB12\nTerminating due to previous errors\n" + result = subprocess.CompletedProcess(["stubgen"], 1, b"", stderr) + assert h.validate_error_run(result, tmp_path / "absent") == ( + b"object at 0x1234abcd5678\nTerminating due to previous errors\n" + ) + assert h.normalize_stderr(b"ordinary text 123 abc 0xZZ") == b"ordinary text 123 abc 0xZZ" +``` + +- [ ] **Step 2: Run the compiler-free command and confirm missing APIs cause the new failures.** +- [ ] **Step 3: Implement process logging and contracts.** Add imports `json`, `re`, and `subprocess` to the helper and append: + +```python +def run_command( + argv: list[str], *, cwd: Path, log: Path, expected_status: int = 0, +) -> subprocess.CompletedProcess[bytes]: + result = None + stdout = stderr = b"" + problem = None + try: + result = subprocess.run(argv, cwd=cwd, capture_output=True, timeout=300, check=False) + stdout, stderr = result.stdout, result.stderr + if result.returncode != expected_status: + problem = f"exit {result.returncode}, expected {expected_status}" + except subprocess.TimeoutExpired as error: + stdout, stderr = error.stdout or b"", error.stderr or b"" + problem = "timed out after 300 seconds" + except OSError as error: + problem = str(error) + log.parent.mkdir(parents=True, exist_ok=True) + log.with_suffix(".stdout").write_bytes(stdout) + log.with_suffix(".stderr").write_bytes(stderr) + log.with_suffix(".json").write_text(json.dumps({ + "command": argv, "cwd": str(cwd), + "returncode": result.returncode if result is not None else None, + "error": problem, + }, indent=2) + "\n", encoding="utf-8") + if problem is not None: + raise HarnessError( + f"{argv!r}: {problem}\nLogs: {log}\n" + f"stdout:\n{stdout.decode('utf-8', errors='replace')}\n" + f"stderr:\n{stderr.decode('utf-8', errors='replace')}" + ) + assert result is not None + return result + + +def normalize_stderr(stderr: bytes) -> bytes: + return re.sub(rb"0x[0-9A-Fa-f]+", b"0x1234abcd5678", stderr) + + +def validate_error_run(result: subprocess.CompletedProcess[bytes], output: Path) -> bytes: + if result.returncode != 1: + raise HarnessError(f"Expected fatal-error exit 1, got {result.returncode}") + if b"Traceback (most recent call last)" in result.stderr: + raise HarnessError("Unexpected Python traceback in fatal-error run") + if b"Terminating due to previous errors" not in result.stderr: + raise HarnessError("Missing fatal-diagnostic termination marker") + if read_tree(output): + raise HarnessError("Fatal-error run unexpectedly wrote output") + return normalize_stderr(result.stderr) +``` + +- [ ] **Step 4: Run the self-test command.** Expected: all process and fatal-error tests pass. +- [ ] **Step 5: Add the Ruff boundary tests before implementing normalization.** Append: + +```python +def test_ruff_pin_configuration_target_order_and_cache_policy(tmp_path, monkeypatch): + repo = tmp_path / "repo" + write_files(repo, { + ".pre-commit-config.yaml": ( + b"repos:\n - repo: unrelated\n rev: v99.0.0\n" + b" - repo: https://github.com/astral-sh/ruff-pre-commit\n rev: v0.15.20\n" + ), + "pyproject.toml": b"[tool.ruff]\nline-length = 88\n", + }) + commands = [] + + def record(argv, **kwargs): + commands.append((argv, kwargs)) + return subprocess.CompletedProcess(argv, 0, b"", b"") + + monkeypatch.setattr(h, "run_command", record) + h.format_stubs(tmp_path / "output", repo, tmp_path, (3, 12)) + assert len(commands) == 2 + assert commands[0][0][:5] == ["uvx", "--from", "ruff==0.15.20", "ruff", "format"] + assert commands[1][0][4:8] == ["check", "--select", "I,RUF022", "--fix"] + for argv, kwargs in commands: + assert argv[argv.index("--config") + 1] == str(repo / "pyproject.toml") + assert argv[argv.index("--target-version") + 1] == "py312" + assert "--no-cache" in argv + assert kwargs["cwd"] == tmp_path + + +def test_invalid_ruff_pin_and_formatter_failure_are_not_ignored(tmp_path, monkeypatch): + write_files(tmp_path, {".pre-commit-config.yaml": b"repos: []\n"}) + with pytest.raises(h.HarnessError, match="Ruff"): + h.ruff_version(tmp_path) + write_files(tmp_path, {".pre-commit-config.yaml": ( + b"repos:\n - repo: https://github.com/astral-sh/ruff-pre-commit\n rev: v0.15.20\n" + )}) + + def fail(argv, **kwargs): + raise h.HarnessError("formatter failed") + + monkeypatch.setattr(h, "run_command", fail) + with pytest.raises(h.HarnessError, match="formatter failed"): + h.format_stubs(tmp_path / "output", tmp_path, tmp_path, (3, 10)) +``` + +- [ ] **Step 6: Run the self-test command.** Expected: the new tests fail because `ruff_version` and `format_stubs` do not exist yet. +- [ ] **Step 7: Implement the Ruff adapter.** This narrowly parses the existing pin; do not introduce PyYAML or a generic configuration framework: + +```python +def ruff_version(repo: Path) -> str: + text = (repo / ".pre-commit-config.yaml").read_text(encoding="utf-8") + blocks = re.split(r"(?m)^\s*-\s+repo:\s*", text)[1:] + blocks = [block for block in blocks if block.splitlines()[0].strip().strip("\"'") + == "https://github.com/astral-sh/ruff-pre-commit"] + if len(blocks) != 1: + raise HarnessError("Cannot identify the Ruff pre-commit pin") + match = re.search(r'''(?m)^\s*rev:\s*["']?v?(\d+\.\d+\.\d+)["']?\s*$''', blocks[0]) + if match is None: + raise HarnessError("Cannot resolve the Ruff pre-commit version") + return match.group(1) + + +def format_stubs( + output: Path, repo: Path, workspace: Path, python_version: tuple[int, int], +) -> None: + prefix = ["uvx", "--from", f"ruff=={ruff_version(repo)}", "ruff"] + options = ["--config", str(repo / "pyproject.toml"), "--target-version", + f"py{python_version[0]}{python_version[1]}", "--no-cache", str(output)] + run_command(prefix + ["format"] + options, cwd=workspace, log=workspace / "ruff-format") + run_command(prefix + ["check", "--select", "I,RUF022", "--fix"] + options, + cwd=workspace, log=workspace / "ruff-check") +``` + +`--no-cache` keeps Ruff cache files out of the compared output; uv's tool cache remains outside that tree. Tests mock Ruff execution, so self-tests still need pytest alone. + +- [ ] **Step 8: Run the self-test command.** Expected: all process, error-contract, and normalization tests pass without executing Ruff. +- [ ] **Step 9: Commit.** + +```sh +git add tests/snapshot_helpers.py tests/test_snapshot_helpers.py +git commit -m "test: validate generator processes and preserve normalization" +``` + +## Task 4: Retain useful failure artifacts without touching references + +**Files:** Modify `tests/snapshot_helpers.py`, `tests/test_snapshot_helpers.py`. + +**Interfaces:** +- Consumes: `HarnessError`, `SnapshotMismatch`, `relative_file`. +- Produces context manager: `diagnostics(workspace: Path, *, artifacts: Path | None, reference_roots: tuple[Path, ...], case_id: str, check_name: str, expected: Path) -> Iterator[None]`. +- Validates destinations before yielding; records context locally. On a failure, retains regular files in a unique run directory under `///` and raises `HarnessError` with locations and original diagnostics. + +**Self-test command for this task (from the repository root):** + +```sh +uv run --no-project --with 'pytest>=8,<9' python -m pytest tests/test_snapshot_helpers.py -q +``` + +- [ ] **Step 1: Add artifact safety and retention tests.** Append: + +```python +@pytest.mark.parametrize("through_alias", [False, True]) +def test_artifacts_cannot_target_reference_trees(tmp_path, through_alias): + refs = tmp_path / "refs" + write_files(refs / "case", {"x.pyi": b"keep"}) + destination = refs / "artifacts" + if through_alias: + alias = tmp_path / "alias" + alias.symlink_to(refs, target_is_directory=True) + destination = alias / "artifacts" + with pytest.raises(h.HarnessError, match="reference"): + with h.diagnostics(tmp_path / "work", artifacts=destination, + reference_roots=(refs,), case_id="case", check_name="stubs", + expected=refs / "case"): + raise AssertionError("body must not run") + assert h.read_tree(refs) == {"case/x.pyi": b"keep"} + + +def test_failure_artifacts_include_output_diff_and_context(tmp_path): + workspace = tmp_path / "work" + refs = tmp_path / "refs" + write_files(refs / "case", {"x.pyi": b"expected"}) + with pytest.raises(h.HarnessError, match="Artifacts:"): + with h.diagnostics(workspace, artifacts=tmp_path / "artifacts", + reference_roots=(refs,), case_id="case", check_name="stubs", + expected=refs / "case"): + write_files(workspace / "output", {"x.pyi": b"actual"}) + raise h.SnapshotMismatch("Changed: x.pyi\n") + runs = list((tmp_path / "artifacts/case/stubs").iterdir()) + assert len(runs) == 1 + assert (runs[0] / "output/x.pyi").read_bytes() == b"actual" + assert "Changed: x.pyi" in (runs[0] / "diff.patch").read_text() + assert str(refs / "case") in (runs[0] / "context.txt").read_text() + assert h.read_tree(refs) == {"case/x.pyi": b"expected"} + + +def test_artifacts_cannot_recurse_into_workspace(tmp_path): + with pytest.raises(h.HarnessError, match="workspace"): + with h.diagnostics(tmp_path, artifacts=tmp_path / "artifacts", + reference_roots=(tmp_path / "refs",), case_id="case", + check_name="stubs", expected=tmp_path / "refs/case"): + raise AssertionError("body must not run") +``` + +- [ ] **Step 2: Run the self-tests and confirm the missing `diagnostics` API causes failures.** +- [ ] **Step 3: Implement diagnostics.** Add `from contextlib import contextmanager`, `from collections.abc import Iterator`, `import shutil`, and `import tempfile` to the helper. Append: + +```python +@contextmanager +def diagnostics( + workspace: Path, *, artifacts: Path | None, reference_roots: tuple[Path, ...], + case_id: str, check_name: str, expected: Path, +) -> Iterator[None]: + workspace = workspace.resolve() + artifact_parent = None + for component in (case_id, check_name): + if len(relative_file(component).parts) != 1: + raise HarnessError(f"Invalid artifact identifier: {component}") + if artifacts is not None: + base = artifacts.resolve() + artifact_parent = (base / case_id / check_name).resolve() + if not artifact_parent.is_relative_to(base): + raise HarnessError("Artifact identifiers escape their destination") + for candidate in (base, artifact_parent): + if any(candidate.is_relative_to(root.resolve()) for root in reference_roots): + raise HarnessError("Artifact destination is inside a reference tree") + if artifact_parent.is_relative_to(workspace): + raise HarnessError("Artifact destination is inside the working workspace") + workspace.mkdir(parents=True, exist_ok=True) + context = f"Case: {case_id}/{check_name}\nReference: {expected.resolve()}\nWorkspace: {workspace}\n" + (workspace / "context.txt").write_text(context, encoding="utf-8") + try: + yield + except Exception as error: + summary = context + str(error) + (workspace / "failure.txt").write_text(summary, encoding="utf-8") + if isinstance(error, SnapshotMismatch): + (workspace / "diff.patch").write_text(str(error), encoding="utf-8") + if artifact_parent is not None: + artifact_parent.mkdir(parents=True, exist_ok=True) + destination = Path(tempfile.mkdtemp(prefix="run-", dir=artifact_parent)) + for source in workspace.rglob("*"): + # Never dereference rejected output symlinks while retaining diagnostics. + if source.is_symlink() or not source.is_file(): + continue + target = destination / source.relative_to(workspace) + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(source, target) + summary += f"\nArtifacts: {destination}" + raise HarnessError(summary) from error +``` + +- [ ] **Step 4: Run all self-tests.** Expected: green; artifact failures cannot create directories inside reference roots, including through aliases. +- [ ] **Step 5: Commit.** + +```sh +git add tests/snapshot_helpers.py tests/test_snapshot_helpers.py +git commit -m "test: retain isolated snapshot failure diagnostics" +``` + +## Task 5: Connect lazy pytest fixtures and both native test contracts + +**Files:** +- Create: `tests/conftest.py`, `tests/test_demo_stubs.py`, `tests/test_demo_errors.py`. +- Modify: `tests/snapshot_helpers.py`, `tests/test_snapshot_helpers.py`. + +**Interfaces:** +- Consumes all helper interfaces from Tasks 1–4. +- Produces frozen `DemoCase(repo: Path, python_version: tuple[int, int], branch: str, numpy_format: str)` with properties `id`, `stubs_root`, `errors_root`, `stub_profile`, `error_profile`. +- Produces `make_case(repo: Path, python_version: tuple[int, int], branch: str | None, numpy_format: str | None) -> DemoCase`. +- pytest fixtures: `demo_case -> DemoCase`, `update_snapshots -> bool`, `artifacts_dir -> Path | None`, `report_update -> Callable[[str], None]`. +- Native test functions take those four fixtures plus `tmp_path`; they are also callable from synthetic orchestration tests without a compiler. + +**Self-test command for this task (from the repository root):** + +```sh +uv run --no-project --with 'pytest>=8,<9' python -m pytest tests/test_snapshot_helpers.py -q +``` + +- [ ] **Step 1: Add profile-selection tests first.** Append: + +```python +@pytest.mark.parametrize("branch,mode", [(None, None), ("v9.9", "numpy-array-use-type-var"), + ("v3.0", "invalid-format")]) +def test_case_requires_explicit_recognized_configuration(tmp_path, branch, mode): + with pytest.raises(h.HarnessError): + h.make_case(tmp_path, (3, 13), branch, mode) + + +def test_case_requires_existing_profiles_and_uses_runtime_python(tmp_path): + mode = "numpy-array-wrap-with-annotated" + with pytest.raises(h.HarnessError): + h.make_case(tmp_path, (3, 13), "v3.0", mode) + write_files(tmp_path / "tests/stubs/python-3.13/pybind11-v3.0" / mode, {}) + write_files(tmp_path / "tests/errors/pybind11-v3.0", {}) + case = h.make_case(tmp_path, (3, 13), "v3.0", mode) + assert case.stub_profile == Path("python-3.13/pybind11-v3.0") / mode + assert case.error_profile == Path("pybind11-v3.0") + assert "python-3.13" in case.id +``` + +- [ ] **Step 2: Run the self-test command.** Expected: the new profile tests fail because `make_case` does not exist yet. +- [ ] **Step 3: Implement the metadata.** Add `from dataclasses import dataclass` to the helper and append: + +```python +BRANCHES = ("v2.9", "v2.11", "v2.12", "v2.13", "v3.0") +NUMPY_FORMATS = ("numpy-array-wrap-with-annotated", "numpy-array-use-type-var") + + +@dataclass(frozen=True) +class DemoCase: + repo: Path + python_version: tuple[int, int] + branch: str + numpy_format: str + + @property + def id(self) -> str: + major, minor = self.python_version + return f"python-{major}.{minor}-pybind11-{self.branch}-{self.numpy_format}" + + @property + def stubs_root(self) -> Path: + return self.repo / "tests/stubs" + + @property + def errors_root(self) -> Path: + return self.repo / "tests/errors" + + @property + def stub_profile(self) -> Path: + major, minor = self.python_version + return Path(f"python-{major}.{minor}") / f"pybind11-{self.branch}" / self.numpy_format + + @property + def error_profile(self) -> Path: + return Path(f"pybind11-{self.branch}") + + +def make_case( + repo: Path, python_version: tuple[int, int], branch: str | None, numpy_format: str | None, +) -> DemoCase: + if python_version < (3, 10): + raise HarnessError("The test harness requires Python >=3.10") + if branch not in BRANCHES or numpy_format not in NUMPY_FORMATS: + raise HarnessError("Native tests require --pybind11-branch and --numpy-format; use tox to build the matching demo") + case = DemoCase(repo.resolve(), python_version, branch, numpy_format) + resolve_profile(case.stubs_root, case.stub_profile) + resolve_profile(case.errors_root, case.error_profile) + return case +``` + +- [ ] **Step 4: Run the self-test command.** Expected: all profile-selection tests pass. +- [ ] **Step 5: Add orchestration safety tests before creating the native test modules.** Append: + +```python +@pytest.mark.parametrize("failure_stage", ["generator", "formatter"]) +def test_success_update_never_accepts_failed_generation_or_formatting(tmp_path, monkeypatch, failure_stage): + import test_demo_stubs as integration + + repo = tmp_path / "repo" + case = h.DemoCase(repo, (3, 13), "v3.0", "numpy-array-wrap-with-annotated") + reference = case.stubs_root / case.stub_profile + write_files(reference, {"demo/__init__.pyi": b"original"}) + messages = [] + + def generate(argv, **kwargs): + assert argv[:4] == [sys.executable, "-I", "-m", "pybind11_stubgen"] + assert kwargs["expected_status"] == 0 + if failure_stage == "generator": + raise h.HarnessError("generator failed") + write_files(kwargs["cwd"] / "output", {"demo/__init__.pyi": b"new"}) + return subprocess.CompletedProcess(argv, 0, b"", b"") + + def format_failure(*args): + raise h.HarnessError("formatter failed") + + monkeypatch.setattr(integration, "run_command", generate) + monkeypatch.setattr(integration, "format_stubs", format_failure) + with pytest.raises(h.HarnessError, match=f"{failure_stage} failed"): + integration.test_demo_stubs(case, tmp_path / "work", True, None, messages.append) + assert h.read_tree(reference) == {"demo/__init__.pyi": b"original"} + assert messages == [] + + +def test_error_update_rejects_traceback_even_with_exit_one(tmp_path, monkeypatch): + import test_demo_errors as integration + + case = h.DemoCase(tmp_path / "repo", (3, 13), "v3.0", "numpy-array-use-type-var") + reference = case.errors_root / case.error_profile + write_files(reference, {"demo.errors.stderr.txt": b"original"}) + + def traceback_result(argv, **kwargs): + assert argv[:4] == [sys.executable, "-I", "-m", "pybind11_stubgen"] + assert kwargs["expected_status"] == 1 + return subprocess.CompletedProcess(argv, 1, b"", b"Traceback (most recent call last):\n") + + monkeypatch.setattr(integration, "run_command", traceback_result) + with pytest.raises(h.HarnessError, match="traceback"): + integration.test_demo_errors(case, tmp_path / "work", True, None, print) + assert h.read_tree(reference) == {"demo.errors.stderr.txt": b"original"} + + +@pytest.mark.parametrize("kind", ["stubs", "errors"]) +@pytest.mark.parametrize("update", [False, True]) +def test_integration_check_and_update_paths_with_synthetic_output(tmp_path, monkeypatch, kind, update): + import test_demo_errors + import test_demo_stubs + + case = h.DemoCase(tmp_path / "repo", (3, 13), "v3.0", "numpy-array-wrap-with-annotated") + integration = test_demo_stubs if kind == "stubs" else test_demo_errors + run_test = integration.test_demo_stubs if kind == "stubs" else integration.test_demo_errors + reference = case.stubs_root / case.stub_profile if kind == "stubs" else case.errors_root / case.error_profile + filename = "demo/__init__.pyi" if kind == "stubs" else "demo.errors.stderr.txt" + new_content = b"new stubs\n" if kind == "stubs" else b"object 0x1234abcd5678\nTerminating due to previous errors\n" + write_files(reference, {filename: b"original"}) + messages = [] + + def generate(argv, **kwargs): + if kind == "stubs": + write_files(kwargs["cwd"] / "output", {filename: new_content}) + return subprocess.CompletedProcess(argv, 0, b"", b"") + return subprocess.CompletedProcess(argv, 1, b"", b"object 0xABCD\nTerminating due to previous errors\n") + + monkeypatch.setattr(integration, "run_command", generate) + if kind == "stubs": + monkeypatch.setattr(integration, "format_stubs", lambda *args: None) + if update: + run_test(case, tmp_path / "work", True, None, messages.append) + assert h.read_tree(reference) == {filename: new_content} + assert messages and str(reference.resolve()) in messages[0] + else: + with pytest.raises(h.HarnessError, match="Changed:"): + run_test(case, tmp_path / "work", False, None, messages.append) + assert h.read_tree(reference) == {filename: b"original"} + assert messages == [] + + +@pytest.mark.parametrize("empty_at", ["generator", "formatter"]) +def test_empty_output_cannot_erase_references(tmp_path, monkeypatch, empty_at): + import test_demo_stubs as integration + + case = h.DemoCase(tmp_path / "repo", (3, 13), "v3.0", "numpy-array-wrap-with-annotated") + reference = case.stubs_root / case.stub_profile + write_files(reference, {"demo/__init__.pyi": b"original"}) + + def generate(argv, **kwargs): + if empty_at == "formatter": + write_files(kwargs["cwd"] / "output", {"demo/__init__.pyi": b"new"}) + return subprocess.CompletedProcess(argv, 0, b"", b"") + + def erase_output(output, repo, workspace, python_version): + (output / "demo/__init__.pyi").unlink() + + monkeypatch.setattr(integration, "run_command", generate) + monkeypatch.setattr(integration, "format_stubs", erase_output) + with pytest.raises(h.HarnessError, match="demo/__init__.pyi"): + integration.test_demo_stubs(case, tmp_path / "work", True, None, print) + assert h.read_tree(reference) == {"demo/__init__.pyi": b"original"} +``` + +- [ ] **Step 6: Run the self-test command.** Expected: new orchestration tests fail because the native-test modules do not exist yet. +- [ ] **Step 7: Create the lazy fixtures in `tests/conftest.py`.** + +```python +import sys +from pathlib import Path + +import pytest + +from snapshot_helpers import BRANCHES, NUMPY_FORMATS, make_case + + +def pytest_addoption(parser): + group = parser.getgroup("demo snapshots") + group.addoption("--pybind11-branch", choices=BRANCHES) + group.addoption("--numpy-format", choices=NUMPY_FORMATS) + group.addoption("--update-snapshots", action="store_true", default=False) + group.addoption("--artifacts-dir", type=Path) + + +@pytest.fixture +def demo_case(pytestconfig): + return make_case(Path(__file__).resolve().parents[1], sys.version_info[:2], + pytestconfig.getoption("pybind11_branch"), + pytestconfig.getoption("numpy_format")) + + +@pytest.fixture +def update_snapshots(pytestconfig): + return pytestconfig.getoption("update_snapshots") + + +@pytest.fixture +def artifacts_dir(pytestconfig): + return pytestconfig.getoption("artifacts_dir") + + +@pytest.fixture +def report_update(pytestconfig): + def report(message): + terminal = pytestconfig.pluginmanager.get_plugin("terminalreporter") + if terminal is None: + print(message) + else: + terminal.write_line(message) + return report +``` + +The terminal reporter is intentional: updates must display destinations and changed paths even when a passing test's stdout would normally be captured. + +- [ ] **Step 8: Create `tests/test_demo_stubs.py` with the existing generator flags and validated ordering.** + +```python +import sys + +from snapshot_helpers import ( + HarnessError, check_snapshot, diagnostics, format_stubs, read_tree, run_command, +) + + +def test_demo_stubs(demo_case, tmp_path, update_snapshots, artifacts_dir, report_update): + case = demo_case + expected = case.stubs_root / case.stub_profile + output = tmp_path / "output" + with diagnostics(tmp_path, artifacts=artifacts_dir, + reference_roots=(case.stubs_root, case.errors_root), + case_id=case.id, check_name="stubs", expected=expected): + run_command([ + sys.executable, "-I", "-m", "pybind11_stubgen", "demo", + "--output-dir", str(output), f"--{case.numpy_format}", + r"--ignore-invalid-expressions=\(anonymous namespace\)::(Enum|Unbound)| {expected.resolve()}; changed: {changed}") +``` + +Validate the tree before and after normalization: reject unexpected links before handing files to Ruff, and prevent update mode from deleting all references after a spurious successful command or formatter that left no demo output. + +- [ ] **Step 9: Create `tests/test_demo_errors.py`.** + +```python +import sys + +from snapshot_helpers import check_snapshot, diagnostics, run_command, validate_error_run + + +def test_demo_errors(demo_case, tmp_path, update_snapshots, artifacts_dir, report_update): + case = demo_case + expected = case.errors_root / case.error_profile + output = tmp_path / "output" + filename = "demo.errors.stderr.txt" + with diagnostics(tmp_path, artifacts=artifacts_dir, + reference_roots=(case.stubs_root, case.errors_root), + case_id=case.id, check_name="errors", expected=expected / filename): + result = run_command([ + sys.executable, "-I", "-m", "pybind11_stubgen", "demo", + "--output-dir", str(output), "--exit-code", + ], cwd=tmp_path, log=tmp_path / "stubgen", expected_status=1) + stderr = validate_error_run(result, output) + (tmp_path / "normalized.stderr").write_bytes(stderr) + changed = check_snapshot(case.errors_root, case.error_profile, {filename: stderr}, + update=update_snapshots, only=frozenset({filename})) + if update_snapshots: + report_update(f"{case.id}: {expected} -> {expected.resolve()}; changed: {changed}") +``` + +- [ ] **Step 10: Run the self-test command.** Expected: all harness tests pass without an installed demo. +- [ ] **Step 11: Verify collection without native configuration.** + +```sh +uv run --no-project --with 'pytest>=8,<9' python -m pytest tests --collect-only -q +``` + +Expected: collection succeeds without native imports or build commands. + +- [ ] **Step 12: Verify that missing configuration fails explicitly.** + +```sh +uv run --no-project --with 'pytest>=8,<9' python -m pytest tests/test_demo_stubs.py -q +``` + +Expected: failure with the missing-configuration message, not a skip or attempted build. This failure is deliberate; do not treat it as a native baseline run. Actual installed-demo validation happens after Task 6 replaces the unsafe tox entry point. + +- [ ] **Step 13: Commit.** + +```sh +git add tests/snapshot_helpers.py tests/test_snapshot_helpers.py tests/conftest.py tests/test_demo_stubs.py tests/test_demo_errors.py +git commit -m "test: add pytest demo generation and error checks" +``` + +## Task 6: Switch callers, document workflows, and verify compatibility + +**Files:** +- Modify: `tox.ini`, `.github/workflows/ci.yml`, `README.md`, `tests/README.md`, `tests/test_snapshot_helpers.py`. +- Delete: `tests/check-demo-stubs-generation.sh`, `tests/check-demo-errors-generation.sh`. +- Verify but do not rewrite: `tests/install-demo-module.sh`, native fixture sources, reference trees, existing CI matrix and `gemmi` job. + +**Interfaces:** +- Consumes the four pytest options and three test files from Task 5. +- Produces safe default tox/CI checks, `tox -e py313-pb30-naa -- --update-snapshots` forwarding, and failed-run artifacts under `tmp/pytest-artifacts/` in CI. +- No new production interface, native build step, or matrix configuration. + +**Self-test command for this task (from the repository root):** + +```sh +uv run --no-project --with 'pytest>=8,<9' python -m pytest tests/test_snapshot_helpers.py -q +``` + +- [ ] **Step 1: Add a regression for the entry-point switch.** Append to the self-tests: + +```python +def test_entry_points_use_pytest_without_legacy_mutating_checks(): + repo = Path(__file__).resolve().parents[1] + tox = (repo / "tox.ini").read_text() + ci = (repo / ".github/workflows/ci.yml").read_text() + assert "-m pytest" in tox and "{posargs}" in tox + assert "pytest>=8,<9" in tox + assert "PYTHON_TAG_FILE" not in tox + native_job = ci.split(" tests:\n", 1)[1].split(" test-cli-options:\n", 1)[0] + assert "-m pytest" in native_job + assert "tmp/pytest-artifacts/" in native_job + assert "--update-snapshots" not in native_job + for script in ("check-demo-stubs-generation.sh", "check-demo-errors-generation.sh"): + assert script not in tox and script not in ci +``` + +- [ ] **Step 2: Run self-tests.** Expected red result: only the entry-point test fails because tox/CI still invoke the old scripts. +- [ ] **Step 3: Rewire tox without changing its environment list, branch mappings, CMake pins, or build command.** Add `pytest>=8,<9` to `deps`. Change the description, remove `STUBS_OUT`, `PYTHON_TAG_FILE`, and the Python-tag `commands_pre` line. Keep the existing demo installation `commands_pre` line. Set: + +```ini +description = Check generated demo stubs and errors + +commands = + {envpython} -m pip install . + {envpython} -m pytest {toxinidir}/tests/test_snapshot_helpers.py {toxinidir}/tests/test_demo_stubs.py {toxinidir}/tests/test_demo_errors.py --pybind11-branch {env:PYBIND11_BRANCH} --numpy-format {env:NUMPY_FORMAT} --artifacts-dir {envtmpdir}/pytest-artifacts {posargs} +``` + +The `description` belongs in `[testenv]`, not inside `commands`. Retain the existing uv/tox setup policy; changing native dependency or CMake selection belongs to phase 3. + +- [ ] **Step 4: Replace CI's two checking steps and patch-upload step with these steps.** Preserve all earlier wheel/demo installation steps and the surrounding matrix: + +```yaml + - name: Check installed wheel and demo snapshots + shell: bash + run: | + source .venv/bin/activate + python -I -c 'import sys; from pathlib import Path; import pybind11_stubgen; p = Path(pybind11_stubgen.__file__).resolve(); assert p.is_relative_to(Path(sys.prefix).resolve()), p; print(p)' + python -m pytest tests/test_snapshot_helpers.py tests/test_demo_stubs.py tests/test_demo_errors.py \ + --pybind11-branch "${{ matrix.config.pybind11-branch }}" \ + --numpy-format "${{ matrix.config.numpy-format }}" \ + --artifacts-dir tmp/pytest-artifacts/ --maxfail=0 + + - name: Archive test diagnostics + uses: actions/upload-artifact@v6 + if: failure() + with: + name: "python-${{ matrix.config.python }}-pybind-${{ matrix.config.pybind11-branch }}-${{ matrix.config.numpy-format }}-diagnostics" + path: tmp/pytest-artifacts/ + retention-days: 30 + if-no-files-found: ignore +``` + +The wheel-origin assertion protects against accidentally importing an editable checkout. Do not run `uv run` or another project sync in this job after the wheel is installed. The single pytest invocation attempts both native checks even when one fails. + +- [ ] **Step 5: Remove the legacy checker files now that their callers are gone.** Use scoped removal; do not touch the installer: + +```sh +git rm tests/check-demo-stubs-generation.sh tests/check-demo-errors-generation.sh +``` + +- [ ] **Step 6: Replace `tests/README.md` with this workflow guide.** + +````markdown +# Testing + +The native demo models a C++ library (`demo-lib`), its bindings +(`py-demo/bindings`), and a mixed Python/native package (`py-demo`). + +Checks generate output in temporary directories and compare it with `stubs/` +and `errors/`. They never modify references or stage files. Updating references +is a separate, explicit operation. + +## Prerequisites + +Use Python 3.10 or newer. Native tests additionally need Git, a C++17 compiler, +uv, and tox with tox-uv. Tox installs the existing CMake/Eigen/Python dependencies. +Initial builds clone pybind11; normalization may download the pinned Ruff tool. + +```sh +uv python install 3.10 3.11 3.12 3.13 +uv tool install tox --with tox-uv +``` + +## Harness self-tests, without a compiler + +With pytest installed: + +```sh +python -m pytest tests/test_snapshot_helpers.py +``` + +Or let uv supply only pytest, without syncing the project's native dependencies: + +```sh +uv run --no-project --with 'pytest>=8,<9' python -m pytest tests/test_snapshot_helpers.py +``` + +## Native checks + +```sh +tox -e py313-pb30-naa # One environment: Python 3.13, pybind11 3.0, annotated arrays. +tox -e py313-pb30-naa -- -k demo_errors # One check in that environment. +tox # Full configured matrix, serially. +``` + +Environment suffix `naa` means `numpy-array-wrap-with-annotated`; `nutv` means +`numpy-array-use-type-var`. Keep runs serial: the existing native build and some +reference directories are shared. Do not use `tox -p` or pytest workers yet. + +After tox has installed the project and matching demo, a check can be rerun +without rebuilding: + +```sh +.tox/py313-pb30-naa/bin/python -m pytest tests/test_demo_stubs.py \ + --pybind11-branch v3.0 --numpy-format numpy-array-wrap-with-annotated +``` + +That command tests the installed generator, not uninstalled source edits. Rerun +tox after changing generator or native fixture code. Missing configuration or a +missing demo is an error, not a skipped test. + +## Update references deliberately + +```sh +tox -e py313-pb30-naa -- --update-snapshots +git diff -- tests/stubs tests/errors +``` + +Use `-k demo_stubs` or `-k demo_errors` with the update option to select one kind +of expectation. Generation, exit-status, no-output, and formatting assertions +still apply in update mode. A broken command cannot become a new expectation. + +Updates report their canonical destination and changed paths. Some directories +are aliases (for example, Python 3.13 uses the Python 3.12 reference directory). +An update to shared expectations affects every profile using that directory; +review and recheck those profiles. Aliases are preserved, and unrelated profiles +are not rewritten. Nothing is automatically staged or committed. + +Updates are per test, not transactional across a run. If a later check fails, +earlier successful checks may already have updated their references. Always +review the diff; do not accept unexplained changes from a bulk regeneration. + +## Failures and artifacts + +Failures show the configuration, resolved reference location, file-set changes, +content diffs, and process diagnostics. Each check uses a fresh temporary output +directory. Optional `--artifacts-dir PATH` retains failure output outside the +reference trees. Tox uses its environment temporary directory; CI uploads +`tmp/pytest-artifacts/` with separate configuration/check/run subdirectories. + +## Add a regression + +For harness behavior, add a small synthetic test to `test_snapshot_helpers.py`; +no compiler is required. For generated-output behavior, add or adjust the demo +fixture, run a relevant native environment, inspect its failure, then explicitly +update that environment's expectations. Review only the intended changes and +run affected shared profiles and the compatibility matrix before merging. +```` + +- [ ] **Step 7: Replace only the root README's Contributing section with a short link and hook instructions.** + +````markdown +Contributing +------------ + +See [the testing guide](tests/README.md) for prerequisites, running checks, +updating reference snapshots, and adding regression fixtures. Normal test runs +do not update or stage reference files. + +To enable repository hooks locally: + +```sh +uv sync +uv run pre-commit install +``` +```` + +Keep the rest of the README unchanged. + +- [ ] **Step 8: Verify the compiler-free layer on the supported floor and a current matrix interpreter.** These are verification runs, not snapshot updates: + +```sh +uv run --no-project --python 3.10 --with 'pytest>=8,<9' python -m pytest tests/test_snapshot_helpers.py -q +uv run --no-project --python 3.13 --with 'pytest>=8,<9' python -m pytest tests/test_snapshot_helpers.py -q +uv lock --check +``` + +Expected: both self-test runs and the lock check pass. Run the repository hooks on the changed files in the development environment: + +```sh +uv run pre-commit run --files pyproject.toml uv.lock tox.ini .github/workflows/ci.yml README.md tests/README.md tests/snapshot_helpers.py tests/test_snapshot_helpers.py tests/conftest.py tests/test_demo_stubs.py tests/test_demo_errors.py +``` + +If hooks reformat code, rerun both compiler-free commands before committing. This project-syncing hook command is local development only, not part of the installed-wheel CI test step. + +- [ ] **Step 9: Verify preservation of the native matrix/build and the `gemmi` job.** Run this read-only comparison against the investigation baseline: + +```sh +python - <<'PY' +from pathlib import Path +import subprocess + +base = '99eb2dc' +old = subprocess.check_output(['git', 'show', f'{base}:.github/workflows/ci.yml'], text=True) +new = Path('.github/workflows/ci.yml').read_text() +def matrix(text): + return text.split(' tests:\n', 1)[1].split(' steps:\n', 1)[0] +assert matrix(old) == matrix(new), 'Native matrix or scheduling changed' +assert old.split(' test-cli-options:\n', 1)[1] == new.split(' test-cli-options:\n', 1)[1], 'Smoke/publish jobs changed' +for path in ['tests/install-demo-module.sh', 'tests/demo-lib', 'tests/py-demo']: + subprocess.run(['git', 'diff', '--exit-code', base, '--', path], check=True) +print('Matrix, native fixtures/build, and gemmi/publish jobs preserved') +PY +``` + +Also review `git diff 99eb2dc -- tox.ini` to confirm the environment list and native dependency pins are unchanged. Do not create a third copy of the matrix in test code. + +- [ ] **Step 10: Run one representative native environment and prove check-mode immutability, even on failure.** Only now is the tox entry point safe to use: + +```sh +python - <<'PY' +from pathlib import Path +import hashlib +import os +import subprocess + +def state(): + references = {} + for root in (Path('tests/stubs'), Path('tests/errors')): + for path in sorted(root.rglob('*')): + name = str(path) + if path.is_symlink(): + references[name] = ('symlink', os.readlink(path)) + elif path.is_file(): + references[name] = ('file', hashlib.sha256(path.read_bytes()).hexdigest()) + elif path.is_dir(): + references[name] = ('directory',) + index = subprocess.check_output(['git', 'ls-files', '--stage', '-z']) + return references, index + +before = state() +result = subprocess.run(['tox', '-e', 'py313-pb30-naa'], check=False) +assert state() == before, 'Check-mode run changed references or staging state' +raise SystemExit(result.returncode) +PY +``` + +Expected: two native checks and the self-tests pass; reference bytes, alias targets, and index entries remain unchanged. If a baseline snapshot/build issue is exposed, preserve diagnostics and investigate it separately; never run a blanket update to obtain green results. + +- [ ] **Step 11: Exercise the remaining representative differences, then the full matrix.** + +```sh +tox -e py310-pb30-naa,py313-pb213-naa,py313-pb30-nutv +tox +``` + +The full CI matrix must exercise all 13 profiles before merge. Local missing interpreters, unavailable network/toolchains, and pre-existing build failures must be reported as limits, not represented as passing coverage. Confirm a deliberate synthetic mismatch from the self-tests produces useful artifacts and does not change references; do not corrupt checked-in snapshots to trigger CI failures. + +- [ ] **Step 12: Review the final diff and commit the caller/documentation switch.** + +```sh +git diff --check +git diff -- tests/stubs tests/errors +git add tox.ini .github/workflows/ci.yml README.md tests/README.md tests/test_snapshot_helpers.py +git commit -m "test: switch tox and CI to non-mutating pytest checks" +``` + +The script deletions were staged by the scoped `git rm`. No reference changes belong in this mechanical commit. If reference corrections are proven necessary, explain and review them separately as required by the spec. + +## Spec-to-task review map + +| Specification requirement | Implementation/verification task | +| --- | --- | +| Python >=3.10; pytest-only self-tests; explicit discovery | Tasks 1 and 6 | +| Complete byte/file-set comparisons; working-tree expectations, not Git | Tasks 1–2 | +| Scoped updates; stale files; aliases; escaping paths; developer changes | Task 2, integration guards in Task 5, real index check in Task 6 | +| Exact exit codes; tracebacks; absent executable; no output on errors; 300-second timeouts | Tasks 3 and 5 | +| Existing Ruff pin/config/target/order and narrow address normalization | Task 3 | +| Safe, useful failure artifacts and CI retention | Tasks 4 and 6 | +| Explicit profile options; lazy fixtures; isolated installed-module subprocesses | Task 5 | +| Update reporting, shared aliases, and per-test transaction boundary | Tasks 2 and 5; Task 6 documentation | +| Existing native build, 13 configurations, wheel coverage, and unchanged gemmi job | Task 6 | +| Contributor workflows and removal of duplicate shell checkers | Task 6 | +| Phases 2–4 remain a roadmap, not incidental implementation | Completion boundary below | + +## Completion evidence and later phases + +Before reporting implementation complete, record: + +- Python 3.10 and 3.13 compiler-free results. +- Native configurations actually run and any limitations; CI results for all 13 configurations. +- Installed-wheel origin verification and unchanged `gemmi` job. +- Passing and intentionally failing synthetic check/update safety cases. +- Check-mode reference/index immutability evidence and usable failure artifacts. +- Clean formatting/lock checks, scoped commits, and absence of unexplained reference churn. + +Do not start phases 2–4 as incidental refactoring. Their roadmap remains in the +approved spec: compiler-free production tests; shared orchestration/simpler +native builds; then snapshot deduplication. Each requires a separate scoped plan. diff --git a/docs/superpowers/specs/2026-09-24-test-harness-phase-one-design.md b/docs/superpowers/specs/2026-09-24-test-harness-phase-one-design.md index eb05bc99..30577d25 100644 --- a/docs/superpowers/specs/2026-09-24-test-harness-phase-one-design.md +++ b/docs/superpowers/specs/2026-09-24-test-harness-phase-one-design.md @@ -2,7 +2,9 @@ Date: 2026-09-24 -Status: Design approved in chat; written specification awaiting review. +Status: Written specification approved in chat on 2026-09-24. + +Implementation plan: [phase-one pytest harness](../plans/2026-09-24-test-harness-phase-one.md). Repository baseline: `5057f41` (`Prepare 3.0 release`). From a07bd90b477594392bf7adae539777f9b3ee2fd4 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 24 Sep 2026 04:31:59 +0000 Subject: [PATCH 04/40] test: add read-only snapshot comparison helpers --- pyproject.toml | 5 +++ tests/snapshot_helpers.py | 72 +++++++++++++++++++++++++++++++ tests/test_snapshot_helpers.py | 54 ++++++++++++++++++++++++ uv.lock | 77 ++++++++++++++++++++++++++++++++++ 4 files changed, 208 insertions(+) create mode 100644 tests/snapshot_helpers.py create mode 100644 tests/test_snapshot_helpers.py diff --git a/pyproject.toml b/pyproject.toml index 418a8352..5aa1e5b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ dev = [ "numpy~=1.20", "pip", "pre-commit>=4,<5", + "pytest>=8,<9", "scipy~=1.0", "typing_extensions>=4,<5", "mypy>=1.19.1", @@ -58,3 +59,7 @@ indent-style = "space" [tool.ruff.lint.pycodestyle] max-line-length = 88 + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = "-ra" diff --git a/tests/snapshot_helpers.py b/tests/snapshot_helpers.py new file mode 100644 index 00000000..dc509564 --- /dev/null +++ b/tests/snapshot_helpers.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +import difflib +from pathlib import Path, PurePosixPath + +Snapshot = dict[str, bytes] + + +class HarnessError(AssertionError): + """An actionable test-harness or comparison failure.""" + + +def relative_file(name: str) -> Path: + path = PurePosixPath(name) + if ( + not name + or not path.parts + or path.is_absolute() + or ".." in path.parts + or "\\" in name + or path.as_posix() != name + ): + raise HarnessError(f"Unsafe snapshot name: {name!r}") + return Path(*path.parts) + + +def resolve_profile(root: Path, profile: Path) -> Path: + relative_file(profile.as_posix()) + try: + base = root.resolve(strict=True) + destination = (base / profile).resolve(strict=True) + except (OSError, RuntimeError) as error: + raise HarnessError(f"Unavailable snapshot profile: {root / profile}") from error + if not destination.is_relative_to(base) or destination == base: + raise HarnessError(f"Snapshot profile escapes root: {root / profile}") + if not destination.is_dir(): + raise HarnessError(f"Snapshot profile is not a directory: {destination}") + return destination + + +def read_tree(root: Path) -> Snapshot: + if root.is_symlink(): + raise HarnessError(f"Unexpected symlink: {root}") + if not root.exists(): + return {} + if not root.is_dir(): + raise HarnessError(f"Expected directory: {root}") + result = {} + for path in sorted(root.rglob("*")): + if path.is_symlink(): + raise HarnessError(f"Unexpected symlink: {path}") + if path.is_file(): + result[path.relative_to(root).as_posix()] = path.read_bytes() + elif not path.is_dir(): + raise HarnessError(f"Unexpected filesystem entry: {path}") + return result + + +def diff_tree(expected: Snapshot, actual: Snapshot) -> str: + differences = [] + for name in sorted(expected.keys() | actual.keys()): + if name in expected and name in actual and expected[name] == actual[name]: + continue + kind = "Missing" if name not in actual else "Unexpected" if name not in expected else "Changed" + differences.append(f"{kind}: {name}\n") + before = expected.get(name, b"").decode("utf-8", errors="backslashreplace") + after = actual.get(name, b"").decode("utf-8", errors="backslashreplace") + differences.extend(difflib.unified_diff( + before.splitlines(keepends=True), after.splitlines(keepends=True), + fromfile=f"expected/{name}", tofile=f"actual/{name}", + )) + return "".join(differences) diff --git a/tests/test_snapshot_helpers.py b/tests/test_snapshot_helpers.py new file mode 100644 index 00000000..7950bd80 --- /dev/null +++ b/tests/test_snapshot_helpers.py @@ -0,0 +1,54 @@ +from pathlib import Path + +import pytest + +import snapshot_helpers as h + + +def write_files(root: Path, files: dict[str, bytes]) -> None: + root.mkdir(parents=True, exist_ok=True) + for name, content in files.items(): + path = root / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(content) + + +def test_diff_detects_changed_missing_and_unexpected_files(): + expected = {"nested/a file.pyi": b"old\n", "gone.pyi": b"x\n"} + actual = {"nested/a file.pyi": b"new\n", "extra.pyi": b"y\n"} + assert h.diff_tree(expected, expected) == "" + difference = h.diff_tree(expected, actual) + assert "Changed: nested/a file.pyi" in difference + assert "Missing: gone.pyi" in difference + assert "Unexpected: extra.pyi" in difference + assert "-old" in difference and "+new" in difference + assert h.diff_tree({"x": b"x\r\n"}, {"x": b"x\n"}) + + +def test_read_tree_preserves_bytes_and_rejects_links(tmp_path): + write_files(tmp_path, {"sub/a.pyi": b"\xff\r\n"}) + assert h.read_tree(tmp_path) == {"sub/a.pyi": b"\xff\r\n"} + assert h.read_tree(tmp_path / "absent") == {} + (tmp_path / "linked.pyi").symlink_to("sub/a.pyi") + with pytest.raises(h.HarnessError, match="symlink"): + h.read_tree(tmp_path) + + +def test_profile_aliases_resolve_but_escapes_and_missing_profiles_fail(tmp_path): + refs = tmp_path / "refs" + write_files(refs / "real", {"x.pyi": b"x"}) + (refs / "alias").symlink_to("real", target_is_directory=True) + (refs / "chain").symlink_to("alias", target_is_directory=True) + assert h.resolve_profile(refs, Path("chain")) == refs / "real" + outside = tmp_path / "outside" + outside.mkdir() + (refs / "escape").symlink_to(outside, target_is_directory=True) + for name in ("escape", "missing", "../outside"): + with pytest.raises(h.HarnessError): + h.resolve_profile(refs, Path(name)) + + +@pytest.mark.parametrize("name", ["", ".", "..", "../x", "/x", "a/../x", "a\\x"]) +def test_relative_file_rejects_unsafe_names(name): + with pytest.raises(h.HarnessError): + h.relative_file(name) diff --git a/uv.lock b/uv.lock index f38a5030..2ad86972 100644 --- a/uv.lock +++ b/uv.lock @@ -39,6 +39,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/c8/bf03f1684bdcc61f2c2cce2f8ab7d13289c67f0f6bb58288d30c318df183/cmeel_eigen-3.4.0.2-0-py3-none-any.whl", hash = "sha256:2c3efe70560aca10b9d5b9bac6c883057d30aa08fc7b8cb43f4837e935b214e4", size = 1883353, upload-time = "2023-11-16T00:21:54.205Z" }, ] +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + [[package]] name = "distlib" version = "0.4.0" @@ -48,6 +57,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + [[package]] name = "filelock" version = "3.29.0" @@ -66,6 +87,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, ] +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + [[package]] name = "librt" version = "0.7.8" @@ -235,6 +265,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" }, ] +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + [[package]] name = "pathspec" version = "1.0.4" @@ -262,6 +301,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, ] +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + [[package]] name = "pre-commit" version = "4.5.1" @@ -291,6 +339,7 @@ dev = [ { name = "numpy" }, { name = "pip" }, { name = "pre-commit" }, + { name = "pytest" }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "typing-extensions" }, @@ -306,10 +355,38 @@ dev = [ { name = "numpy", specifier = "~=1.20" }, { name = "pip" }, { name = "pre-commit", specifier = ">=4,<5" }, + { name = "pytest", specifier = ">=8,<9" }, { name = "scipy", specifier = "~=1.0" }, { name = "typing-extensions", specifier = ">=4,<5" }, ] +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + [[package]] name = "python-discovery" version = "1.2.2" From c734ada03f1bf38f57e9a4084293753acbaf935b Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 24 Sep 2026 04:35:48 +0000 Subject: [PATCH 05/40] test: report snapshot directory scan failures --- tests/snapshot_helpers.py | 25 ++++++++++++++++++------- tests/test_snapshot_helpers.py | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/tests/snapshot_helpers.py b/tests/snapshot_helpers.py index dc509564..e30a39a3 100644 --- a/tests/snapshot_helpers.py +++ b/tests/snapshot_helpers.py @@ -1,6 +1,7 @@ from __future__ import annotations import difflib +import os from pathlib import Path, PurePosixPath Snapshot = dict[str, bytes] @@ -46,13 +47,23 @@ def read_tree(root: Path) -> Snapshot: if not root.is_dir(): raise HarnessError(f"Expected directory: {root}") result = {} - for path in sorted(root.rglob("*")): - if path.is_symlink(): - raise HarnessError(f"Unexpected symlink: {path}") - if path.is_file(): - result[path.relative_to(root).as_posix()] = path.read_bytes() - elif not path.is_dir(): - raise HarnessError(f"Unexpected filesystem entry: {path}") + pending = [root] + while pending: + directory = pending.pop() + try: + with os.scandir(directory) as entries: + paths = sorted(directory / entry.name for entry in entries) + except OSError as error: + raise HarnessError(f"Unable to scan directory: {directory}") from error + for path in paths: + if path.is_symlink(): + raise HarnessError(f"Unexpected symlink: {path}") + if path.is_file(): + result[path.relative_to(root).as_posix()] = path.read_bytes() + elif path.is_dir(): + pending.append(path) + else: + raise HarnessError(f"Unexpected filesystem entry: {path}") return result diff --git a/tests/test_snapshot_helpers.py b/tests/test_snapshot_helpers.py index 7950bd80..6f50b355 100644 --- a/tests/test_snapshot_helpers.py +++ b/tests/test_snapshot_helpers.py @@ -1,3 +1,4 @@ +import os from pathlib import Path import pytest @@ -34,6 +35,24 @@ def test_read_tree_preserves_bytes_and_rejects_links(tmp_path): h.read_tree(tmp_path) +@pytest.mark.parametrize("unreadable", [".", "sub"]) +def test_read_tree_reports_scan_errors(tmp_path, monkeypatch, unreadable): + write_files(tmp_path, {"sub/a.pyi": b"must not disappear\n"}) + blocked = tmp_path / unreadable + scandir = os.scandir + + def failing_scandir(path): + if Path(path) == blocked: + raise PermissionError("synthetic scan failure") + return scandir(path) + + monkeypatch.setattr(os, "scandir", failing_scandir) + with pytest.raises(h.HarnessError) as failure: + h.read_tree(tmp_path) + assert str(blocked) in str(failure.value) + assert isinstance(failure.value.__cause__, PermissionError) + + def test_profile_aliases_resolve_but_escapes_and_missing_profiles_fail(tmp_path): refs = tmp_path / "refs" write_files(refs / "real", {"x.pyi": b"x"}) From f127b07d16183e686193e79da05785f771740a9b Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 24 Sep 2026 04:39:45 +0000 Subject: [PATCH 06/40] test: add explicit scoped snapshot updates --- tests/snapshot_helpers.py | 53 +++++++++++++++++++++++++++ tests/test_snapshot_helpers.py | 66 ++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/tests/snapshot_helpers.py b/tests/snapshot_helpers.py index e30a39a3..0f1767b4 100644 --- a/tests/snapshot_helpers.py +++ b/tests/snapshot_helpers.py @@ -81,3 +81,56 @@ def diff_tree(expected: Snapshot, actual: Snapshot) -> str: fromfile=f"expected/{name}", tofile=f"actual/{name}", )) return "".join(differences) + + +class SnapshotMismatch(HarnessError): + """Generated output differs from its reference.""" + + +def check_snapshot( + root: Path, profile: Path, actual: Snapshot, *, + update: bool = False, only: frozenset[str] | None = None, +) -> tuple[str, ...]: + for name, content in actual.items(): + path = relative_file(name) + if not isinstance(content, bytes): + raise HarnessError(f"Snapshot content must be bytes: {name}") + if any(parent.as_posix() in actual for parent in path.parents): + raise HarnessError(f"Conflicting snapshot paths: {name}") + if only is not None: + for name in only: + relative_file(name) + if actual.keys() - only: + raise HarnessError("Actual output exceeds the selected snapshot scope") + destination = resolve_profile(root, profile) + expected = read_tree(destination) + if only is not None: + for name in only: + target = destination / relative_file(name) + if target.exists() and not target.is_file(): + raise HarnessError(f"Scoped reference is not a file: {target}") + expected = {name: content for name, content in expected.items() if name in only} + difference = diff_tree(expected, actual) + if not difference: + return () + if not update: + raise SnapshotMismatch(difference) + changed = tuple(sorted( + name for name in expected.keys() | actual.keys() + if name not in expected or name not in actual or expected[name] != actual[name] + )) + for name in expected.keys() - actual.keys(): + (destination / relative_file(name)).unlink() + if only is None: + directories = [path for path in destination.rglob("*") if path.is_dir()] + for directory in sorted(directories, key=lambda path: len(path.parts), reverse=True): + try: + directory.rmdir() + except OSError: + # Nonempty directories contain retained reference files. + continue + for name, content in actual.items(): + target = destination / relative_file(name) + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(content) + return changed diff --git a/tests/test_snapshot_helpers.py b/tests/test_snapshot_helpers.py index 6f50b355..018348a1 100644 --- a/tests/test_snapshot_helpers.py +++ b/tests/test_snapshot_helpers.py @@ -71,3 +71,69 @@ def test_profile_aliases_resolve_but_escapes_and_missing_profiles_fail(tmp_path) def test_relative_file_rejects_unsafe_names(name): with pytest.raises(h.HarnessError): h.relative_file(name) + + +@pytest.mark.parametrize("update", [False, True]) +def test_check_update_preserve_aliases_unrelated_files_and_index(tmp_path, update): + refs = tmp_path / "refs" + write_files(refs / "real", {"a.pyi": b"developer edit", "old.pyi": b"old"}) + write_files(refs / "unrelated", {"keep.pyi": b"keep"}) + (refs / "alias").symlink_to("real", target_is_directory=True) + write_files(tmp_path / ".git", {"index": b"pre-existing staged state"}) + before = h.read_tree(refs / "real") + actual = {"a.pyi": b"new", "nested/new.pyi": b"added"} + if update: + changed = h.check_snapshot(refs, Path("alias"), actual, update=True) + assert set(changed) == {"a.pyi", "old.pyi", "nested/new.pyi"} + assert h.read_tree(refs / "real") == actual + else: + assert h.check_snapshot(refs, Path("alias"), before) == () + with pytest.raises(h.SnapshotMismatch): + h.check_snapshot(refs, Path("alias"), actual) + assert h.read_tree(refs / "real") == before + assert (refs / "alias").is_symlink() + assert h.read_tree(refs / "unrelated") == {"keep.pyi": b"keep"} + assert (tmp_path / ".git/index").read_bytes() == b"pre-existing staged state" + + +def test_stderr_update_is_scoped_to_one_file(tmp_path): + write_files(tmp_path / "case", {"stderr.txt": b"old", "unrelated.txt": b"keep"}) + h.check_snapshot(tmp_path, Path("case"), {"stderr.txt": b"new"}, + update=True, only=frozenset({"stderr.txt"})) + assert h.read_tree(tmp_path / "case") == { + "stderr.txt": b"new", "unrelated.txt": b"keep", + } + + +@pytest.mark.parametrize("actual", [ + {"../escape": b"bad"}, {"x": b"file", "x/child": b"collision"}, + {"x": "not bytes"}, +]) +def test_update_validates_all_content_before_writing(tmp_path, actual): + write_files(tmp_path / "case", {"keep.pyi": b"original"}) + with pytest.raises(h.HarnessError): + h.check_snapshot(tmp_path, Path("case"), actual, update=True) + assert h.read_tree(tmp_path / "case") == {"keep.pyi": b"original"} + + +def test_update_rejects_nested_reference_symlinks(tmp_path): + write_files(tmp_path / "case", {"keep.pyi": b"original"}) + (tmp_path / "case/link.pyi").symlink_to("keep.pyi") + with pytest.raises(h.HarnessError, match="symlink"): + h.check_snapshot(tmp_path, Path("case"), {"keep.pyi": b"new"}, update=True) + assert (tmp_path / "case/keep.pyi").read_bytes() == b"original" + + +def test_update_handles_file_directory_shape_changes(tmp_path): + write_files(tmp_path / "case", {"old/child": b"old", "flat": b"old"}) + actual = {"old": b"now a file", "flat/child": b"now nested"} + h.check_snapshot(tmp_path, Path("case"), actual, update=True) + assert h.read_tree(tmp_path / "case") == actual + + +def test_scoped_update_rejects_extra_actual_files_before_writing(tmp_path): + write_files(tmp_path / "case", {"stderr.txt": b"original", "unrelated": b"keep"}) + with pytest.raises(h.HarnessError, match="scope"): + h.check_snapshot(tmp_path, Path("case"), {"stderr.txt": b"new", "extra": b"bad"}, + update=True, only=frozenset({"stderr.txt"})) + assert h.read_tree(tmp_path / "case") == {"stderr.txt": b"original", "unrelated": b"keep"} From c13b326b058d8f138be05dd81bc44f787465fa58 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 24 Sep 2026 04:43:42 +0000 Subject: [PATCH 07/40] test: propagate snapshot directory cleanup failures --- tests/snapshot_helpers.py | 6 ++++-- tests/test_snapshot_helpers.py | 39 ++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/tests/snapshot_helpers.py b/tests/snapshot_helpers.py index 0f1767b4..c6993632 100644 --- a/tests/snapshot_helpers.py +++ b/tests/snapshot_helpers.py @@ -1,6 +1,7 @@ from __future__ import annotations import difflib +import errno import os from pathlib import Path, PurePosixPath @@ -126,9 +127,10 @@ def check_snapshot( for directory in sorted(directories, key=lambda path: len(path.parts), reverse=True): try: directory.rmdir() - except OSError: + except OSError as error: # Nonempty directories contain retained reference files. - continue + if error.errno not in (errno.ENOTEMPTY, errno.EEXIST): + raise for name, content in actual.items(): target = destination / relative_file(name) target.parent.mkdir(parents=True, exist_ok=True) diff --git a/tests/test_snapshot_helpers.py b/tests/test_snapshot_helpers.py index 018348a1..4f9af5cf 100644 --- a/tests/test_snapshot_helpers.py +++ b/tests/test_snapshot_helpers.py @@ -1,3 +1,4 @@ +import errno import os from pathlib import Path @@ -131,6 +132,44 @@ def test_update_handles_file_directory_shape_changes(tmp_path): assert h.read_tree(tmp_path / "case") == actual +@pytest.mark.parametrize("error_number", [errno.EACCES, errno.EPERM, errno.EIO]) +def test_update_propagates_directory_cleanup_failures(tmp_path, monkeypatch, error_number): + write_files(tmp_path / "case", {"keep.pyi": b"old"}) + blocked = tmp_path / "case/empty" + blocked.mkdir() + error = OSError(error_number, "synthetic cleanup failure", str(blocked)) + rmdir = Path.rmdir + + def failing_rmdir(path): + if path == blocked: + raise error + return rmdir(path) + + monkeypatch.setattr(Path, "rmdir", failing_rmdir) + with pytest.raises(OSError) as failure: + h.check_snapshot(tmp_path, Path("case"), {"keep.pyi": b"new"}, update=True) + assert failure.value is error + assert (tmp_path / "case/keep.pyi").read_bytes() == b"old" + + +@pytest.mark.parametrize("error_number", [None, errno.ENOTEMPTY, errno.EEXIST]) +def test_update_preserves_nonempty_directories(tmp_path, monkeypatch, error_number): + write_files(tmp_path / "case", {"nested/keep.pyi": b"old"}) + retained = tmp_path / "case/nested" + rmdir = Path.rmdir + + def nonempty_rmdir(path): + if path == retained and error_number is not None: + raise OSError(error_number, "Directory not empty", str(retained)) + return rmdir(path) + + monkeypatch.setattr(Path, "rmdir", nonempty_rmdir) + assert h.check_snapshot( + tmp_path, Path("case"), {"nested/keep.pyi": b"new"}, update=True, + ) == ("nested/keep.pyi",) + assert (retained / "keep.pyi").read_bytes() == b"new" + + def test_scoped_update_rejects_extra_actual_files_before_writing(tmp_path): write_files(tmp_path / "case", {"stderr.txt": b"original", "unrelated": b"keep"}) with pytest.raises(h.HarnessError, match="scope"): From ca3e55a5d958c255822aa29e983a01ea0eaed78b Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 24 Sep 2026 04:47:40 +0000 Subject: [PATCH 08/40] test: validate generator processes and preserve normalization --- tests/snapshot_helpers.py | 77 ++++++++++++++++++++++++++ tests/test_snapshot_helpers.py | 98 ++++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+) diff --git a/tests/snapshot_helpers.py b/tests/snapshot_helpers.py index c6993632..bcb7a6ce 100644 --- a/tests/snapshot_helpers.py +++ b/tests/snapshot_helpers.py @@ -2,7 +2,10 @@ import difflib import errno +import json import os +import re +import subprocess from pathlib import Path, PurePosixPath Snapshot = dict[str, bytes] @@ -136,3 +139,77 @@ def check_snapshot( target.parent.mkdir(parents=True, exist_ok=True) target.write_bytes(content) return changed + + +def run_command( + argv: list[str], *, cwd: Path, log: Path, expected_status: int = 0, +) -> subprocess.CompletedProcess[bytes]: + result = None + stdout = stderr = b"" + problem = None + try: + result = subprocess.run(argv, cwd=cwd, capture_output=True, timeout=300, check=False) + stdout, stderr = result.stdout, result.stderr + if result.returncode != expected_status: + problem = f"exit {result.returncode}, expected {expected_status}" + except subprocess.TimeoutExpired as error: + stdout, stderr = error.stdout or b"", error.stderr or b"" + problem = "timed out after 300 seconds" + except OSError as error: + problem = str(error) + log.parent.mkdir(parents=True, exist_ok=True) + log.with_suffix(".stdout").write_bytes(stdout) + log.with_suffix(".stderr").write_bytes(stderr) + log.with_suffix(".json").write_text(json.dumps({ + "command": argv, "cwd": str(cwd), + "returncode": result.returncode if result is not None else None, + "error": problem, + }, indent=2) + "\n", encoding="utf-8") + if problem is not None: + raise HarnessError( + f"{argv!r}: {problem}\nLogs: {log}\n" + f"stdout:\n{stdout.decode('utf-8', errors='replace')}\n" + f"stderr:\n{stderr.decode('utf-8', errors='replace')}" + ) + assert result is not None + return result + + +def normalize_stderr(stderr: bytes) -> bytes: + return re.sub(rb"0x[0-9A-Fa-f]+", b"0x1234abcd5678", stderr) + + +def validate_error_run(result: subprocess.CompletedProcess[bytes], output: Path) -> bytes: + if result.returncode != 1: + raise HarnessError(f"Expected fatal-error exit 1, got {result.returncode}") + if b"Traceback (most recent call last)" in result.stderr: + raise HarnessError("Unexpected Python traceback in fatal-error run") + if b"Terminating due to previous errors" not in result.stderr: + raise HarnessError("Missing fatal-diagnostic termination marker") + if read_tree(output): + raise HarnessError("Fatal-error run unexpectedly wrote output") + return normalize_stderr(result.stderr) + + +def ruff_version(repo: Path) -> str: + text = (repo / ".pre-commit-config.yaml").read_text(encoding="utf-8") + blocks = re.split(r"(?m)^\s*-\s+repo:\s*", text)[1:] + blocks = [block for block in blocks if block.splitlines()[0].strip().strip("\"'") + == "https://github.com/astral-sh/ruff-pre-commit"] + if len(blocks) != 1: + raise HarnessError("Cannot identify the Ruff pre-commit pin") + match = re.search(r'''(?m)^\s*rev:\s*["']?v?(\d+\.\d+\.\d+)["']?\s*$''', blocks[0]) + if match is None: + raise HarnessError("Cannot resolve the Ruff pre-commit version") + return match.group(1) + + +def format_stubs( + output: Path, repo: Path, workspace: Path, python_version: tuple[int, int], +) -> None: + prefix = ["uvx", "--from", f"ruff=={ruff_version(repo)}", "ruff"] + options = ["--config", str(repo / "pyproject.toml"), "--target-version", + f"py{python_version[0]}{python_version[1]}", "--no-cache", str(output)] + run_command(prefix + ["format"] + options, cwd=workspace, log=workspace / "ruff-format") + run_command(prefix + ["check", "--select", "I,RUF022", "--fix"] + options, + cwd=workspace, log=workspace / "ruff-check") diff --git a/tests/test_snapshot_helpers.py b/tests/test_snapshot_helpers.py index 4f9af5cf..e900cdf7 100644 --- a/tests/test_snapshot_helpers.py +++ b/tests/test_snapshot_helpers.py @@ -1,5 +1,8 @@ import errno +import json import os +import subprocess +import sys from pathlib import Path import pytest @@ -176,3 +179,98 @@ def test_scoped_update_rejects_extra_actual_files_before_writing(tmp_path): h.check_snapshot(tmp_path, Path("case"), {"stderr.txt": b"new", "extra": b"bad"}, update=True, only=frozenset({"stderr.txt"})) assert h.read_tree(tmp_path / "case") == {"stderr.txt": b"original", "unrelated": b"keep"} + + +@pytest.mark.parametrize("status", [0, 1, 2, 127]) +def test_run_command_requires_exact_status_and_retains_logs(tmp_path, status): + command = [sys.executable, "-c", f"import sys; print('diagnostic', file=sys.stderr); sys.exit({status})"] + if status == 1: + result = h.run_command(command, cwd=tmp_path, log=tmp_path / "process", expected_status=1) + assert result.returncode == 1 + else: + with pytest.raises(h.HarnessError, match="expected 1"): + h.run_command(command, cwd=tmp_path, log=tmp_path / "process", expected_status=1) + assert b"diagnostic" in (tmp_path / "process.stderr").read_bytes() + assert json.loads((tmp_path / "process.json").read_text())["returncode"] == status + + +def test_missing_command_and_timeout_are_diagnostic(tmp_path, monkeypatch): + with pytest.raises(h.HarnessError): + h.run_command([str(tmp_path / "missing-command")], cwd=tmp_path, log=tmp_path / "missing") + assert (tmp_path / "missing.json").is_file() + + def timeout(argv, **kwargs): + assert kwargs["timeout"] == 300 + raise subprocess.TimeoutExpired(argv, 300, output=b"partial", stderr=b"stuck") + + monkeypatch.setattr(h.subprocess, "run", timeout) + with pytest.raises(h.HarnessError, match="300"): + h.run_command(["fake"], cwd=tmp_path, log=tmp_path / "timeout") + assert (tmp_path / "timeout.stdout").read_bytes() == b"partial" + assert (tmp_path / "timeout.stderr").read_bytes() == b"stuck" + + +@pytest.mark.parametrize("status,stderr,files", [ + (0, b"Terminating due to previous errors", {}), + (2, b"Terminating due to previous errors", {}), + (1, b"Traceback (most recent call last):\nTerminating due to previous errors", {}), + (1, b"unrelated failure", {}), + (1, b"Terminating due to previous errors", {"unexpected.pyi": b"x"}), +]) +def test_invalid_fatal_error_runs_are_rejected(tmp_path, status, stderr, files): + write_files(tmp_path / "output", files) + result = subprocess.CompletedProcess(["stubgen"], status, b"", stderr) + with pytest.raises(h.HarnessError): + h.validate_error_run(result, tmp_path / "output") + + +def test_error_normalization_is_narrow(tmp_path): + stderr = b"object at 0xAB12\nTerminating due to previous errors\n" + result = subprocess.CompletedProcess(["stubgen"], 1, b"", stderr) + assert h.validate_error_run(result, tmp_path / "absent") == ( + b"object at 0x1234abcd5678\nTerminating due to previous errors\n" + ) + assert h.normalize_stderr(b"ordinary text 123 abc 0xZZ") == b"ordinary text 123 abc 0xZZ" + + +def test_ruff_pin_configuration_target_order_and_cache_policy(tmp_path, monkeypatch): + repo = tmp_path / "repo" + write_files(repo, { + ".pre-commit-config.yaml": ( + b"repos:\n - repo: unrelated\n rev: v99.0.0\n" + b" - repo: https://github.com/astral-sh/ruff-pre-commit\n rev: v0.15.20\n" + ), + "pyproject.toml": b"[tool.ruff]\nline-length = 88\n", + }) + commands = [] + + def record(argv, **kwargs): + commands.append((argv, kwargs)) + return subprocess.CompletedProcess(argv, 0, b"", b"") + + monkeypatch.setattr(h, "run_command", record) + h.format_stubs(tmp_path / "output", repo, tmp_path, (3, 12)) + assert len(commands) == 2 + assert commands[0][0][:5] == ["uvx", "--from", "ruff==0.15.20", "ruff", "format"] + assert commands[1][0][4:8] == ["check", "--select", "I,RUF022", "--fix"] + for argv, kwargs in commands: + assert argv[argv.index("--config") + 1] == str(repo / "pyproject.toml") + assert argv[argv.index("--target-version") + 1] == "py312" + assert "--no-cache" in argv + assert kwargs["cwd"] == tmp_path + + +def test_invalid_ruff_pin_and_formatter_failure_are_not_ignored(tmp_path, monkeypatch): + write_files(tmp_path, {".pre-commit-config.yaml": b"repos: []\n"}) + with pytest.raises(h.HarnessError, match="Ruff"): + h.ruff_version(tmp_path) + write_files(tmp_path, {".pre-commit-config.yaml": ( + b"repos:\n - repo: https://github.com/astral-sh/ruff-pre-commit\n rev: v0.15.20\n" + )}) + + def fail(argv, **kwargs): + raise h.HarnessError("formatter failed") + + monkeypatch.setattr(h, "run_command", fail) + with pytest.raises(h.HarnessError, match="formatter failed"): + h.format_stubs(tmp_path / "output", tmp_path, tmp_path, (3, 10)) From 9ca89dbb0ced90b81126077673165e7e6551db04 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 24 Sep 2026 04:54:25 +0000 Subject: [PATCH 09/40] test: retain isolated snapshot failure diagnostics --- tests/snapshot_helpers.py | 48 ++++++++++++++ tests/test_snapshot_helpers.py | 111 +++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) diff --git a/tests/snapshot_helpers.py b/tests/snapshot_helpers.py index bcb7a6ce..3ba076bd 100644 --- a/tests/snapshot_helpers.py +++ b/tests/snapshot_helpers.py @@ -5,7 +5,11 @@ import json import os import re +import shutil import subprocess +import tempfile +from collections.abc import Iterator +from contextlib import contextmanager from pathlib import Path, PurePosixPath Snapshot = dict[str, bytes] @@ -213,3 +217,47 @@ def format_stubs( run_command(prefix + ["format"] + options, cwd=workspace, log=workspace / "ruff-format") run_command(prefix + ["check", "--select", "I,RUF022", "--fix"] + options, cwd=workspace, log=workspace / "ruff-check") + + +@contextmanager +def diagnostics( + workspace: Path, *, artifacts: Path | None, reference_roots: tuple[Path, ...], + case_id: str, check_name: str, expected: Path, +) -> Iterator[None]: + workspace = workspace.resolve() + artifact_parent = None + for component in (case_id, check_name): + if len(relative_file(component).parts) != 1: + raise HarnessError(f"Invalid artifact identifier: {component}") + if artifacts is not None: + base = artifacts.resolve() + artifact_parent = (base / case_id / check_name).resolve() + if not artifact_parent.is_relative_to(base): + raise HarnessError("Artifact identifiers escape their destination") + for candidate in (base, artifact_parent): + if any(candidate.is_relative_to(root.resolve()) for root in reference_roots): + raise HarnessError("Artifact destination is inside a reference tree") + if artifact_parent.is_relative_to(workspace): + raise HarnessError("Artifact destination is inside the working workspace") + workspace.mkdir(parents=True, exist_ok=True) + context = f"Case: {case_id}/{check_name}\nReference: {expected.resolve()}\nWorkspace: {workspace}\n" + (workspace / "context.txt").write_text(context, encoding="utf-8") + try: + yield + except Exception as error: + summary = context + str(error) + (workspace / "failure.txt").write_text(summary, encoding="utf-8") + if isinstance(error, SnapshotMismatch): + (workspace / "diff.patch").write_text(str(error), encoding="utf-8") + if artifact_parent is not None: + artifact_parent.mkdir(parents=True, exist_ok=True) + destination = Path(tempfile.mkdtemp(prefix="run-", dir=artifact_parent)) + for source in workspace.rglob("*"): + # Never dereference rejected output symlinks while retaining diagnostics. + if source.is_symlink() or not source.is_file(): + continue + target = destination / source.relative_to(workspace) + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(source, target) + summary += f"\nArtifacts: {destination}" + raise HarnessError(summary) from error diff --git a/tests/test_snapshot_helpers.py b/tests/test_snapshot_helpers.py index e900cdf7..46a7dff3 100644 --- a/tests/test_snapshot_helpers.py +++ b/tests/test_snapshot_helpers.py @@ -274,3 +274,114 @@ def fail(argv, **kwargs): monkeypatch.setattr(h, "run_command", fail) with pytest.raises(h.HarnessError, match="formatter failed"): h.format_stubs(tmp_path / "output", tmp_path, tmp_path, (3, 10)) + + +@pytest.mark.parametrize("through_alias", [False, True]) +def test_artifacts_cannot_target_reference_trees(tmp_path, through_alias): + refs = tmp_path / "refs" + write_files(refs / "case", {"x.pyi": b"keep"}) + destination = refs / "artifacts" + if through_alias: + alias = tmp_path / "alias" + alias.symlink_to(refs, target_is_directory=True) + destination = alias / "artifacts" + with pytest.raises(h.HarnessError, match="reference"): + with h.diagnostics(tmp_path / "work", artifacts=destination, + reference_roots=(refs,), case_id="case", check_name="stubs", + expected=refs / "case"): + raise AssertionError("body must not run") + assert h.read_tree(refs) == {"case/x.pyi": b"keep"} + + +def test_failure_artifacts_include_output_diff_and_context(tmp_path): + workspace = tmp_path / "work" + refs = tmp_path / "refs" + write_files(refs / "case", {"x.pyi": b"expected"}) + with pytest.raises(h.HarnessError, match="Artifacts:"): + with h.diagnostics(workspace, artifacts=tmp_path / "artifacts", + reference_roots=(refs,), case_id="case", check_name="stubs", + expected=refs / "case"): + write_files(workspace / "output", {"x.pyi": b"actual"}) + raise h.SnapshotMismatch("Changed: x.pyi\n") + runs = list((tmp_path / "artifacts/case/stubs").iterdir()) + assert len(runs) == 1 + assert (runs[0] / "output/x.pyi").read_bytes() == b"actual" + assert "Changed: x.pyi" in (runs[0] / "diff.patch").read_text() + assert str(refs / "case") in (runs[0] / "context.txt").read_text() + assert h.read_tree(refs) == {"case/x.pyi": b"expected"} + + +def test_artifacts_cannot_recurse_into_workspace(tmp_path): + with pytest.raises(h.HarnessError, match="workspace"): + with h.diagnostics(tmp_path, artifacts=tmp_path / "artifacts", + reference_roots=(tmp_path / "refs",), case_id="case", + check_name="stubs", expected=tmp_path / "refs/case"): + raise AssertionError("body must not run") + + +@pytest.mark.parametrize("component", ["case_id", "check_name"]) +@pytest.mark.parametrize("value", ["", "..", "../escape", "/absolute", "nested/name"]) +def test_diagnostics_rejects_invalid_identifiers_before_writing(tmp_path, component, value): + identifiers = {"case_id": "case", "check_name": "stubs", component: value} + with pytest.raises(h.HarnessError): + with h.diagnostics(tmp_path / "work", artifacts=tmp_path / "artifacts", + reference_roots=(tmp_path / "refs",), + expected=tmp_path / "refs/case", **identifiers): + pytest.fail("body must not run") + assert list(tmp_path.iterdir()) == [] + + +@pytest.mark.parametrize("artifacts_enabled", [False, True]) +def test_success_records_context_without_retaining_artifacts(tmp_path, artifacts_enabled): + workspace = tmp_path / "work" + artifacts = tmp_path / "artifacts" + expected = tmp_path / "refs/case" + with h.diagnostics(workspace, artifacts=artifacts if artifacts_enabled else None, + reference_roots=(tmp_path / "refs",), case_id="case", + check_name="stubs", expected=expected): + assert (workspace / "context.txt").read_text() == ( + f"Case: case/stubs\nReference: {expected}\nWorkspace: {workspace}\n" + ) + assert not artifacts.exists() + assert not (workspace / "failure.txt").exists() + + +def test_failure_without_artifacts_preserves_original_diagnostics(tmp_path): + error = RuntimeError("generation failed") + workspace = tmp_path / "work" + expected = tmp_path / "refs/case" + with pytest.raises(h.HarnessError) as failure: + with h.diagnostics(workspace, artifacts=None, + reference_roots=(tmp_path / "refs",), case_id="case", + check_name="stubs", expected=expected): + raise error + summary = str(failure.value) + assert failure.value.__cause__ is error + assert "generation failed" in summary + assert str(workspace) in summary and str(expected) in summary + assert "Artifacts:" not in summary + assert (workspace / "failure.txt").read_text() == summary + assert not (workspace / "diff.patch").exists() + + +def test_failure_artifacts_are_unique_and_skip_symlinks_and_special_files(tmp_path): + refs = tmp_path / "refs" + write_files(refs / "case", {"x.pyi": b"keep"}) + for index in range(2): + workspace = tmp_path / f"work-{index}" + with pytest.raises(h.HarnessError, match="rejected output"): + with h.diagnostics(workspace, artifacts=tmp_path / "artifacts", + reference_roots=(refs,), case_id="case", check_name="stubs", + expected=refs / "case"): + write_files(workspace / "output", {"x.pyi": str(index).encode()}) + (workspace / "linked.pyi").symlink_to(refs / "case/x.pyi") + (workspace / "linked-dir").symlink_to(refs, target_is_directory=True) + (workspace / "broken").symlink_to(tmp_path / "absent") + os.mkfifo(workspace / "fifo") + raise h.HarnessError("rejected output") + runs = list((tmp_path / "artifacts/case/stubs").iterdir()) + assert len(runs) == 2 + assert {(run / "output/x.pyi").read_bytes() for run in runs} == {b"0", b"1"} + for run in runs: + assert set(h.read_tree(run)) == {"output/x.pyi", "context.txt", "failure.txt"} + assert h.read_tree(refs) == {"case/x.pyi": b"keep"} From 2b9640926036a06f1c41fe19f7aaf035f5216fd3 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 24 Sep 2026 04:59:17 +0000 Subject: [PATCH 10/40] test: protect snapshot diagnostics and preserve failure context --- tests/snapshot_helpers.py | 53 ++++++++++++++------ tests/test_snapshot_helpers.py | 92 ++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 14 deletions(-) diff --git a/tests/snapshot_helpers.py b/tests/snapshot_helpers.py index 3ba076bd..3ff3ae5c 100644 --- a/tests/snapshot_helpers.py +++ b/tests/snapshot_helpers.py @@ -219,12 +219,24 @@ def format_stubs( cwd=workspace, log=workspace / "ruff-check") +def _write_diagnostic(path: Path, content: str) -> None: + # Replace existing links rather than following them; exclusive creation also + # refuses a link introduced between unlinking and opening the file. + path.unlink(missing_ok=True) + with path.open("x", encoding="utf-8") as stream: + stream.write(content) + + @contextmanager def diagnostics( workspace: Path, *, artifacts: Path | None, reference_roots: tuple[Path, ...], case_id: str, check_name: str, expected: Path, ) -> Iterator[None]: workspace = workspace.resolve() + for root in reference_roots: + root = root.resolve() + if workspace.is_relative_to(root) or root.is_relative_to(workspace): + raise HarnessError("Working workspace overlaps a reference tree") artifact_parent = None for component in (case_id, check_name): if len(relative_file(component).parts) != 1: @@ -239,25 +251,38 @@ def diagnostics( raise HarnessError("Artifact destination is inside a reference tree") if artifact_parent.is_relative_to(workspace): raise HarnessError("Artifact destination is inside the working workspace") - workspace.mkdir(parents=True, exist_ok=True) context = f"Case: {case_id}/{check_name}\nReference: {expected.resolve()}\nWorkspace: {workspace}\n" - (workspace / "context.txt").write_text(context, encoding="utf-8") + try: + workspace.mkdir(parents=True, exist_ok=True) + _write_diagnostic(workspace / "context.txt", context) + except OSError as error: + raise HarnessError(f"{context}Diagnostic write failed: {error}") from error try: yield except Exception as error: summary = context + str(error) - (workspace / "failure.txt").write_text(summary, encoding="utf-8") + files = {"failure.txt": summary} if isinstance(error, SnapshotMismatch): - (workspace / "diff.patch").write_text(str(error), encoding="utf-8") + files["diff.patch"] = str(error) + for name, content in files.items(): + try: + _write_diagnostic(workspace / name, content) + except OSError as diagnostic_error: + summary += f"\nDiagnostic write failed ({workspace / name}): {diagnostic_error}" if artifact_parent is not None: - artifact_parent.mkdir(parents=True, exist_ok=True) - destination = Path(tempfile.mkdtemp(prefix="run-", dir=artifact_parent)) - for source in workspace.rglob("*"): - # Never dereference rejected output symlinks while retaining diagnostics. - if source.is_symlink() or not source.is_file(): - continue - target = destination / source.relative_to(workspace) - target.parent.mkdir(parents=True, exist_ok=True) - shutil.copyfile(source, target) - summary += f"\nArtifacts: {destination}" + destination = artifact_parent + try: + artifact_parent.mkdir(parents=True, exist_ok=True) + destination = Path(tempfile.mkdtemp(prefix="run-", dir=artifact_parent)) + for source in workspace.rglob("*"): + # Never dereference rejected output symlinks while retaining diagnostics. + if source.is_symlink() or not source.is_file(): + continue + target = destination / source.relative_to(workspace) + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(source, target) + except OSError as retention_error: + summary += f"\nArtifact retention failed ({destination}): {retention_error}" + else: + summary += f"\nArtifacts: {destination}" raise HarnessError(summary) from error diff --git a/tests/test_snapshot_helpers.py b/tests/test_snapshot_helpers.py index 46a7dff3..b274feb3 100644 --- a/tests/test_snapshot_helpers.py +++ b/tests/test_snapshot_helpers.py @@ -385,3 +385,95 @@ def test_failure_artifacts_are_unique_and_skip_symlinks_and_special_files(tmp_pa for run in runs: assert set(h.read_tree(run)) == {"output/x.pyi", "context.txt", "failure.txt"} assert h.read_tree(refs) == {"case/x.pyi": b"keep"} + + +@pytest.mark.parametrize("relation", ["inside", "same", "ancestor"]) +@pytest.mark.parametrize("through_alias", [False, True]) +def test_diagnostics_rejects_reference_overlapping_workspaces(tmp_path, relation, through_alias): + refs = tmp_path / "refs" + write_files(refs / "case", {"x.pyi": b"keep"}) + workspace = {"inside": refs / "work", "same": refs, "ancestor": tmp_path}[relation] + if through_alias: + alias = tmp_path / "alias" + alias.symlink_to(workspace, target_is_directory=True) + workspace = alias + body_ran = False + with pytest.raises(h.HarnessError) as failure: + with h.diagnostics(workspace, artifacts=None, reference_roots=(refs,), + case_id="case", check_name="stubs", expected=refs / "case"): + body_ran = True + raise h.SnapshotMismatch("Changed: x.pyi\n") + assert h.read_tree(refs) == {"case/x.pyi": b"keep"} + assert not body_ran + assert "reference" in str(failure.value) + + +@pytest.mark.parametrize("name", ["context.txt", "failure.txt", "diff.patch"]) +def test_diagnostic_writes_do_not_follow_reference_symlinks(tmp_path, name): + refs = tmp_path / "refs" + write_files(refs / "case", {"x.pyi": b"keep"}) + workspace = tmp_path / "work" + workspace.mkdir() + if name == "context.txt": + (workspace / name).symlink_to(refs / "case/x.pyi") + error = h.SnapshotMismatch("Changed: x.pyi\n") + with pytest.raises(h.HarnessError) as failure: + with h.diagnostics(workspace, artifacts=tmp_path / "artifacts", + reference_roots=(refs,), case_id="case", check_name="stubs", + expected=refs / "case"): + if name != "context.txt": + (workspace / name).symlink_to(refs / "case/x.pyi") + raise error + assert h.read_tree(refs) == {"case/x.pyi": b"keep"} + assert failure.value.__cause__ is error + assert not (workspace / name).is_symlink() + assert "Artifacts:" in str(failure.value) + + +@pytest.mark.parametrize("name", ["context.txt", "failure.txt", "diff.patch"]) +def test_diagnostic_write_errors_preserve_context(tmp_path, name): + workspace = tmp_path / "work" + blocked = workspace / name + blocked.mkdir(parents=True) + expected = tmp_path / "refs/case" + error = h.SnapshotMismatch("Changed: x.pyi\n") + body_ran = False + with pytest.raises(h.HarnessError) as failure: + with h.diagnostics(workspace, artifacts=tmp_path / "artifacts", + reference_roots=(tmp_path / "refs",), case_id="case", + check_name="stubs", expected=expected): + body_ran = True + raise error + summary = str(failure.value) + assert "case/stubs" in summary + assert str(expected) in summary and str(workspace) in summary + assert str(blocked) in summary + assert "Diagnostic write failed" in summary + if name == "context.txt": + assert not body_ran + assert isinstance(failure.value.__cause__, OSError) + else: + assert failure.value.__cause__ is error + assert "Changed: x.pyi" in summary + assert "Artifacts:" in summary + + +def test_artifact_retention_errors_preserve_original_failure_and_locations(tmp_path): + workspace = tmp_path / "work" + artifacts = tmp_path / "artifacts" + artifacts.write_bytes(b"not a directory") + expected = tmp_path / "refs/case" + error = h.SnapshotMismatch("Changed: x.pyi\n") + with pytest.raises(h.HarnessError) as failure: + with h.diagnostics(workspace, artifacts=artifacts, + reference_roots=(tmp_path / "refs",), case_id="case", + check_name="stubs", expected=expected): + raise error + summary = str(failure.value) + assert failure.value.__cause__ is error + assert "Changed: x.pyi" in summary and "case/stubs" in summary + assert str(workspace) in summary and str(expected) in summary + assert "Artifact retention failed" in summary + assert str(artifacts / "case/stubs") in summary + assert "Changed: x.pyi" in (workspace / "failure.txt").read_text() + assert artifacts.read_bytes() == b"not a directory" From 66e86f4cb258a005576fabb8eea7513ec58487e7 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 24 Sep 2026 05:04:16 +0000 Subject: [PATCH 11/40] test: add pytest demo generation and error checks --- tests/conftest.py | 42 +++++++++++ tests/snapshot_helpers.py | 48 +++++++++++++ tests/test_demo_errors.py | 23 ++++++ tests/test_demo_stubs.py | 31 +++++++++ tests/test_snapshot_helpers.py | 124 +++++++++++++++++++++++++++++++++ 5 files changed, 268 insertions(+) create mode 100644 tests/conftest.py create mode 100644 tests/test_demo_errors.py create mode 100644 tests/test_demo_stubs.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..fd640911 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,42 @@ +import sys +from pathlib import Path + +import pytest + +from snapshot_helpers import BRANCHES, NUMPY_FORMATS, make_case + + +def pytest_addoption(parser): + group = parser.getgroup("demo snapshots") + group.addoption("--pybind11-branch", choices=BRANCHES) + group.addoption("--numpy-format", choices=NUMPY_FORMATS) + group.addoption("--update-snapshots", action="store_true", default=False) + group.addoption("--artifacts-dir", type=Path) + + +@pytest.fixture +def demo_case(pytestconfig): + return make_case(Path(__file__).resolve().parents[1], sys.version_info[:2], + pytestconfig.getoption("pybind11_branch"), + pytestconfig.getoption("numpy_format")) + + +@pytest.fixture +def update_snapshots(pytestconfig): + return pytestconfig.getoption("update_snapshots") + + +@pytest.fixture +def artifacts_dir(pytestconfig): + return pytestconfig.getoption("artifacts_dir") + + +@pytest.fixture +def report_update(pytestconfig): + def report(message): + terminal = pytestconfig.pluginmanager.get_plugin("terminalreporter") + if terminal is None: + print(message) + else: + terminal.write_line(message) + return report diff --git a/tests/snapshot_helpers.py b/tests/snapshot_helpers.py index 3ff3ae5c..e8fc80f2 100644 --- a/tests/snapshot_helpers.py +++ b/tests/snapshot_helpers.py @@ -10,6 +10,7 @@ import tempfile from collections.abc import Iterator from contextlib import contextmanager +from dataclasses import dataclass from pathlib import Path, PurePosixPath Snapshot = dict[str, bytes] @@ -286,3 +287,50 @@ def diagnostics( else: summary += f"\nArtifacts: {destination}" raise HarnessError(summary) from error + + +BRANCHES = ("v2.9", "v2.11", "v2.12", "v2.13", "v3.0") +NUMPY_FORMATS = ("numpy-array-wrap-with-annotated", "numpy-array-use-type-var") + + +@dataclass(frozen=True) +class DemoCase: + repo: Path + python_version: tuple[int, int] + branch: str + numpy_format: str + + @property + def id(self) -> str: + major, minor = self.python_version + return f"python-{major}.{minor}-pybind11-{self.branch}-{self.numpy_format}" + + @property + def stubs_root(self) -> Path: + return self.repo / "tests/stubs" + + @property + def errors_root(self) -> Path: + return self.repo / "tests/errors" + + @property + def stub_profile(self) -> Path: + major, minor = self.python_version + return Path(f"python-{major}.{minor}") / f"pybind11-{self.branch}" / self.numpy_format + + @property + def error_profile(self) -> Path: + return Path(f"pybind11-{self.branch}") + + +def make_case( + repo: Path, python_version: tuple[int, int], branch: str | None, numpy_format: str | None, +) -> DemoCase: + if python_version < (3, 10): + raise HarnessError("The test harness requires Python >=3.10") + if branch not in BRANCHES or numpy_format not in NUMPY_FORMATS: + raise HarnessError("Native tests require --pybind11-branch and --numpy-format; use tox to build the matching demo") + case = DemoCase(repo.resolve(), python_version, branch, numpy_format) + resolve_profile(case.stubs_root, case.stub_profile) + resolve_profile(case.errors_root, case.error_profile) + return case diff --git a/tests/test_demo_errors.py b/tests/test_demo_errors.py new file mode 100644 index 00000000..6ba51753 --- /dev/null +++ b/tests/test_demo_errors.py @@ -0,0 +1,23 @@ +import sys + +from snapshot_helpers import check_snapshot, diagnostics, run_command, validate_error_run + + +def test_demo_errors(demo_case, tmp_path, update_snapshots, artifacts_dir, report_update): + case = demo_case + expected = case.errors_root / case.error_profile + output = tmp_path / "output" + filename = "demo.errors.stderr.txt" + with diagnostics(tmp_path, artifacts=artifacts_dir, + reference_roots=(case.stubs_root, case.errors_root), + case_id=case.id, check_name="errors", expected=expected / filename): + result = run_command([ + sys.executable, "-I", "-m", "pybind11_stubgen", "demo", + "--output-dir", str(output), "--exit-code", + ], cwd=tmp_path, log=tmp_path / "stubgen", expected_status=1) + stderr = validate_error_run(result, output) + (tmp_path / "normalized.stderr").write_bytes(stderr) + changed = check_snapshot(case.errors_root, case.error_profile, {filename: stderr}, + update=update_snapshots, only=frozenset({filename})) + if update_snapshots: + report_update(f"{case.id}: {expected} -> {expected.resolve()}; changed: {changed}") diff --git a/tests/test_demo_stubs.py b/tests/test_demo_stubs.py new file mode 100644 index 00000000..38e6dcff --- /dev/null +++ b/tests/test_demo_stubs.py @@ -0,0 +1,31 @@ +import sys + +from snapshot_helpers import ( + HarnessError, check_snapshot, diagnostics, format_stubs, read_tree, run_command, +) + + +def test_demo_stubs(demo_case, tmp_path, update_snapshots, artifacts_dir, report_update): + case = demo_case + expected = case.stubs_root / case.stub_profile + output = tmp_path / "output" + with diagnostics(tmp_path, artifacts=artifacts_dir, + reference_roots=(case.stubs_root, case.errors_root), + case_id=case.id, check_name="stubs", expected=expected): + run_command([ + sys.executable, "-I", "-m", "pybind11_stubgen", "demo", + "--output-dir", str(output), f"--{case.numpy_format}", + r"--ignore-invalid-expressions=\(anonymous namespace\)::(Enum|Unbound)| {expected.resolve()}; changed: {changed}") diff --git a/tests/test_snapshot_helpers.py b/tests/test_snapshot_helpers.py index b274feb3..64ad334c 100644 --- a/tests/test_snapshot_helpers.py +++ b/tests/test_snapshot_helpers.py @@ -477,3 +477,127 @@ def test_artifact_retention_errors_preserve_original_failure_and_locations(tmp_p assert str(artifacts / "case/stubs") in summary assert "Changed: x.pyi" in (workspace / "failure.txt").read_text() assert artifacts.read_bytes() == b"not a directory" + + +@pytest.mark.parametrize("branch,mode", [(None, None), ("v9.9", "numpy-array-use-type-var"), + ("v3.0", "invalid-format")]) +def test_case_requires_explicit_recognized_configuration(tmp_path, branch, mode): + with pytest.raises(h.HarnessError): + h.make_case(tmp_path, (3, 13), branch, mode) + + +def test_case_requires_existing_profiles_and_uses_runtime_python(tmp_path): + mode = "numpy-array-wrap-with-annotated" + with pytest.raises(h.HarnessError): + h.make_case(tmp_path, (3, 13), "v3.0", mode) + write_files(tmp_path / "tests/stubs/python-3.13/pybind11-v3.0" / mode, {}) + write_files(tmp_path / "tests/errors/pybind11-v3.0", {}) + case = h.make_case(tmp_path, (3, 13), "v3.0", mode) + assert case.stub_profile == Path("python-3.13/pybind11-v3.0") / mode + assert case.error_profile == Path("pybind11-v3.0") + assert "python-3.13" in case.id + + +@pytest.mark.parametrize("failure_stage", ["generator", "formatter"]) +def test_success_update_never_accepts_failed_generation_or_formatting(tmp_path, monkeypatch, failure_stage): + import test_demo_stubs as integration + + repo = tmp_path / "repo" + case = h.DemoCase(repo, (3, 13), "v3.0", "numpy-array-wrap-with-annotated") + reference = case.stubs_root / case.stub_profile + write_files(reference, {"demo/__init__.pyi": b"original"}) + messages = [] + + def generate(argv, **kwargs): + assert argv[:4] == [sys.executable, "-I", "-m", "pybind11_stubgen"] + assert kwargs["expected_status"] == 0 + if failure_stage == "generator": + raise h.HarnessError("generator failed") + write_files(kwargs["cwd"] / "output", {"demo/__init__.pyi": b"new"}) + return subprocess.CompletedProcess(argv, 0, b"", b"") + + def format_failure(*args): + raise h.HarnessError("formatter failed") + + monkeypatch.setattr(integration, "run_command", generate) + monkeypatch.setattr(integration, "format_stubs", format_failure) + with pytest.raises(h.HarnessError, match=f"{failure_stage} failed"): + integration.test_demo_stubs(case, tmp_path / "work", True, None, messages.append) + assert h.read_tree(reference) == {"demo/__init__.pyi": b"original"} + assert messages == [] + + +def test_error_update_rejects_traceback_even_with_exit_one(tmp_path, monkeypatch): + import test_demo_errors as integration + + case = h.DemoCase(tmp_path / "repo", (3, 13), "v3.0", "numpy-array-use-type-var") + reference = case.errors_root / case.error_profile + write_files(reference, {"demo.errors.stderr.txt": b"original"}) + + def traceback_result(argv, **kwargs): + assert argv[:4] == [sys.executable, "-I", "-m", "pybind11_stubgen"] + assert kwargs["expected_status"] == 1 + return subprocess.CompletedProcess(argv, 1, b"", b"Traceback (most recent call last):\n") + + monkeypatch.setattr(integration, "run_command", traceback_result) + with pytest.raises(h.HarnessError, match="traceback"): + integration.test_demo_errors(case, tmp_path / "work", True, None, print) + assert h.read_tree(reference) == {"demo.errors.stderr.txt": b"original"} + + +@pytest.mark.parametrize("kind", ["stubs", "errors"]) +@pytest.mark.parametrize("update", [False, True]) +def test_integration_check_and_update_paths_with_synthetic_output(tmp_path, monkeypatch, kind, update): + import test_demo_errors + import test_demo_stubs + + case = h.DemoCase(tmp_path / "repo", (3, 13), "v3.0", "numpy-array-wrap-with-annotated") + integration = test_demo_stubs if kind == "stubs" else test_demo_errors + run_test = integration.test_demo_stubs if kind == "stubs" else integration.test_demo_errors + reference = case.stubs_root / case.stub_profile if kind == "stubs" else case.errors_root / case.error_profile + filename = "demo/__init__.pyi" if kind == "stubs" else "demo.errors.stderr.txt" + new_content = b"new stubs\n" if kind == "stubs" else b"object 0x1234abcd5678\nTerminating due to previous errors\n" + write_files(reference, {filename: b"original"}) + messages = [] + + def generate(argv, **kwargs): + if kind == "stubs": + write_files(kwargs["cwd"] / "output", {filename: new_content}) + return subprocess.CompletedProcess(argv, 0, b"", b"") + return subprocess.CompletedProcess(argv, 1, b"", b"object 0xABCD\nTerminating due to previous errors\n") + + monkeypatch.setattr(integration, "run_command", generate) + if kind == "stubs": + monkeypatch.setattr(integration, "format_stubs", lambda *args: None) + if update: + run_test(case, tmp_path / "work", True, None, messages.append) + assert h.read_tree(reference) == {filename: new_content} + assert messages and str(reference.resolve()) in messages[0] + else: + with pytest.raises(h.HarnessError, match="Changed:"): + run_test(case, tmp_path / "work", False, None, messages.append) + assert h.read_tree(reference) == {filename: b"original"} + assert messages == [] + + +@pytest.mark.parametrize("empty_at", ["generator", "formatter"]) +def test_empty_output_cannot_erase_references(tmp_path, monkeypatch, empty_at): + import test_demo_stubs as integration + + case = h.DemoCase(tmp_path / "repo", (3, 13), "v3.0", "numpy-array-wrap-with-annotated") + reference = case.stubs_root / case.stub_profile + write_files(reference, {"demo/__init__.pyi": b"original"}) + + def generate(argv, **kwargs): + if empty_at == "formatter": + write_files(kwargs["cwd"] / "output", {"demo/__init__.pyi": b"new"}) + return subprocess.CompletedProcess(argv, 0, b"", b"") + + def erase_output(output, repo, workspace, python_version): + (output / "demo/__init__.pyi").unlink() + + monkeypatch.setattr(integration, "run_command", generate) + monkeypatch.setattr(integration, "format_stubs", erase_output) + with pytest.raises(h.HarnessError, match="demo/__init__.pyi"): + integration.test_demo_stubs(case, tmp_path / "work", True, None, print) + assert h.read_tree(reference) == {"demo/__init__.pyi": b"original"} From 3a3035fef6526d1865e6757a85765c788a4e39a9 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 24 Sep 2026 05:23:42 +0000 Subject: [PATCH 12/40] test: switch tox and CI to non-mutating pytest checks --- .github/workflows/ci.yml | 20 +- README.md | 20 +- tests/README.md | 97 +++++++- tests/check-demo-errors-generation.sh | 66 ------ tests/check-demo-stubs-generation.sh | 111 --------- tests/conftest.py | 10 +- tests/snapshot_helpers.py | 145 +++++++++--- tests/test_demo_errors.py | 54 ++++- tests/test_demo_stubs.py | 62 +++-- tests/test_snapshot_helpers.py | 315 +++++++++++++++++++------- tox.ini | 11 +- 11 files changed, 549 insertions(+), 362 deletions(-) delete mode 100755 tests/check-demo-errors-generation.sh delete mode 100755 tests/check-demo-stubs-generation.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73bfe9bb..553585f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -182,27 +182,25 @@ jobs: source .venv/bin/activate ./tests/install-demo-module.sh --pybind11-branch "${{ matrix.config.pybind11-branch }}" - - name: Check stubs generation + - name: Check installed wheel and demo snapshots shell: bash run: | source .venv/bin/activate - ./tests/check-demo-stubs-generation.sh --stubs-sub-dir "stubs/python-${{ matrix.config.python }}/pybind11-${{ matrix.config.pybind11-branch }}/${{ matrix.config.numpy-format }}" --${{ matrix.config.numpy-format }} + python -I -c 'import sys; from pathlib import Path; import pybind11_stubgen; p = Path(pybind11_stubgen.__file__).resolve(); assert p.is_relative_to(Path(sys.prefix).resolve()), p; print(p)' + python -m pytest tests/test_snapshot_helpers.py tests/test_demo_stubs.py tests/test_demo_errors.py \ + --pybind11-branch "${{ matrix.config.pybind11-branch }}" \ + --numpy-format "${{ matrix.config.numpy-format }}" \ + --artifacts-dir tmp/pytest-artifacts/ --maxfail=0 - - name: Archive patch + - name: Archive test diagnostics uses: actions/upload-artifact@v6 if: failure() with: - name: "python-${{ matrix.config.python }}-pybind-${{ matrix.config.pybind11-branch }}-${{ matrix.config.numpy-format }}.patch" - path: "./tests/stubs/python-${{ matrix.config.python }}/pybind11-${{ matrix.config.pybind11-branch }}/${{ matrix.config.numpy-format }}.patch" + name: "python-${{ matrix.config.python }}-pybind-${{ matrix.config.pybind11-branch }}-${{ matrix.config.numpy-format }}-diagnostics" + path: tmp/pytest-artifacts/ retention-days: 30 if-no-files-found: ignore - - name: Check error generation - shell: bash - run: | - source .venv/bin/activate - ./tests/check-demo-errors-generation.sh "pybind11-${{ matrix.config.pybind11-branch }}" - test-cli-options: name: "Runs on 🐍 ${{ matrix.python }} • ${{ matrix.test-package }}" runs-on: ubuntu-latest diff --git a/README.md b/README.md index 31ec45dd..72592846 100644 --- a/README.md +++ b/README.md @@ -43,19 +43,13 @@ pybind11-stubgen [-h] Contributing ------------ -During development, you may need to update the reference stubs in tests/stubs. This can be done locally using a convenience tox configuration. Ensure that all Python interpreters required by the test suite are available in your environment. For example, you can install them via uv: -```shell -uv python install 3.10 3.11 3.12 3.13 -``` -To enable the repository hooks locally: -```shell +See [the testing guide](tests/README.md) for prerequisites, running checks, +updating reference snapshots, and adding regression fixtures. Normal test runs +do not update or stage reference files. + +To enable repository hooks locally: + +```sh uv sync uv run pre-commit install ``` -To regenerate the reference stubs run: -```shell -uv venv -source .venv/bin/activate -uv tool install tox --with tox-uv -uv run tox -``` diff --git a/tests/README.md b/tests/README.md index ba5a4257..e31f98cd 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,7 +1,94 @@ -# Test project structure +# Testing -The setup mocks the following project structure: +The native demo models a C++ library (`demo-lib`), its bindings +(`py-demo/bindings`), and a mixed Python/native package (`py-demo`). -1. A C++ library (C++/CMake): [`demo-lib`](demo-lib) -2. Bindings of C++ library as shared object (C++/CMake): [`py-demo/bindings`](py-demo/bindings) -3. Bindings of C++ library as python module (Python): [`py-demo`](py-demo) +Checks generate output in temporary directories and compare it with `stubs/` +and `errors/`. They never modify references or stage files. Updating references +is a separate, explicit operation. + +## Prerequisites + +Use Python 3.10 or newer. Native tests additionally need Git, a C++17 compiler, +uv, and tox with tox-uv. Tox installs the existing CMake/Eigen/Python dependencies. +Initial builds clone pybind11; normalization may download the pinned Ruff tool. + +```sh +uv python install 3.10 3.11 3.12 3.13 +uv tool install tox --with tox-uv +``` + +## Harness self-tests, without a compiler + +With pytest installed: + +```sh +python -m pytest tests/test_snapshot_helpers.py +``` + +Or let uv supply only pytest, without syncing the project's native dependencies: + +```sh +uv run --no-project --with 'pytest>=8,<9' python -m pytest tests/test_snapshot_helpers.py +``` + +## Native checks + +```sh +tox -e py313-pb30-naa # One environment: Python 3.13, pybind11 3.0, annotated arrays. +tox -e py313-pb30-naa -- -k demo_errors # One check in that environment. +tox # Full configured matrix, serially. +``` + +Environment suffix `naa` means `numpy-array-wrap-with-annotated`; `nutv` means +`numpy-array-use-type-var`. Keep runs serial: the existing native build and some +reference directories are shared. Do not use `tox -p` or pytest workers yet. + +After tox has installed the project and matching demo, a check can be rerun +without rebuilding: + +```sh +.tox/py313-pb30-naa/bin/python -m pytest tests/test_demo_stubs.py \ + --pybind11-branch v3.0 --numpy-format numpy-array-wrap-with-annotated +``` + +That command tests the installed generator, not uninstalled source edits. Rerun +tox after changing generator or native fixture code. Missing configuration or a +missing demo is an error, not a skipped test. + +## Update references deliberately + +```sh +tox -e py313-pb30-naa -- --update-snapshots +git diff -- tests/stubs tests/errors +``` + +Use `-k demo_stubs` or `-k demo_errors` with the update option to select one kind +of expectation. Generation, exit-status, no-output, and formatting assertions +still apply in update mode. A broken command cannot become a new expectation. + +Updates report their canonical destination and changed paths. Some directories +are aliases (for example, Python 3.13 uses the Python 3.12 reference directory). +An update to shared expectations affects every profile using that directory; +review and recheck those profiles. Aliases are preserved, and unrelated profiles +are not rewritten. Nothing is automatically staged or committed. + +Updates are per test, not transactional across a run. If a later check fails, +earlier successful checks may already have updated their references. Always +review the diff; do not accept unexplained changes from a bulk regeneration. + +## Failures and artifacts + +Failures show the configuration, resolved reference location, file-set changes, +content diffs, and process diagnostics. Each check uses a fresh temporary output +directory. Optional `--artifacts-dir PATH` retains failure output outside the +reference trees. Tox uses its environment temporary directory; CI uploads +`tmp/pytest-artifacts/` with separate configuration/check/run subdirectories. + +## Add a regression + +For harness behavior, add a small synthetic test to `test_snapshot_helpers.py`; +no compiler is required. For generated-output behavior, add or adjust the demo +fixture, run a relevant native environment, inspect its failure, then explicitly +update that environment's expectations. Review only the intended changes and +run affected shared profiles and the compatibility matrix before merging. diff --git a/tests/check-demo-errors-generation.sh b/tests/check-demo-errors-generation.sh deleted file mode 100755 index 461a9809..00000000 --- a/tests/check-demo-errors-generation.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash - -set -e - -if [ "$#" -lt 1 ]; then - echo "Usage: $0 pybind11-vX.Y [path]" - exit 1 -fi - -resolve_path() { - local path="$2" - - while [ -L "$path" ]; do - path="$(readlink "$path")" - done - - if [ -d "$path" ]; then - (cd "$path" && pwd -P) - else - (cd "$(dirname "$path")" && echo "$(pwd -P)/$(basename "$path")") - fi -} - -TESTS_ROOT="$(resolve_path "$(dirname "$0")")" -ERRORS_ROOT="$(resolve_path "${TESTS_ROOT}/errors/$1")" -DEMO_ERRORS_FILE="${ERRORS_ROOT}/demo.errors.stderr.txt" -STUBS_DIR="/tmp/out" # Stubs should never be actually written - -remove_demo_errors() { - rm -rf "${DEMO_ERRORS_FILE}"; -} - -check_error_messages() { - ( - set -o pipefail ; - git diff --exit-code HEAD -- "${DEMO_ERRORS_FILE}"; - ) -} -run_stubgen() { - ( - set +e ; - pybind11-stubgen \ - demo \ - --output-dir=${STUBS_DIR} \ - --exit-code \ - 2> "${DEMO_ERRORS_FILE}" \ - || exit 0 - ) || ( - echo "'pybind11-stubgen demo --exit-code' did not exit with code 1" - exit 1 - ) -} - -remove_randomness_in_errors (){ - sed -E -i.bak 's/0x[0-9A-Fa-f]+/0x1234abcd5678/g' "$DEMO_ERRORS_FILE" - rm -f "${DEMO_ERRORS_FILE}.bak" -} - -main () { - remove_demo_errors - run_stubgen - remove_randomness_in_errors - check_error_messages -} - -main "$@" diff --git a/tests/check-demo-stubs-generation.sh b/tests/check-demo-stubs-generation.sh deleted file mode 100755 index e452cc48..00000000 --- a/tests/check-demo-stubs-generation.sh +++ /dev/null @@ -1,111 +0,0 @@ -#!/bin/bash - -set -e - -resolve_path() { - local path="$1" - - while [ -L "$path" ]; do - path="$(readlink "$path")" - done - - if [ -d "$path" ]; then - (cd "$path" && pwd -P) - else - (cd "$(dirname "$path")" && echo "$(pwd -P)/$(basename "$path")") - fi -} - -function parse_args() { - - CLEAR='\033[0m' - RED='\033[0;31m' - - function usage() { - if [ -n "$1" ]; then - echo -e "${RED}👉 $1${CLEAR}\n"; - fi - echo "Usage: $0 --stubs-sub-dir STUBS_SUB_DIR [--numpy-array-wrap-with-annotated] [--numpy-array-use-type-var]" - echo " --stubs-sub-dir stubs output dir relative to this script directory" - echo " --numpy-array-wrap-with-annotated use --numpy-array-wrap-with-annotated to generate numpy array stubs" - echo " --numpy-array-use-type-var use --numpy-array-use-type-var to generate numpy array stubs" - exit 1 - } - - # parse params - while [[ "$#" > 0 ]]; do case $1 in - --stubs-sub-dir) STUBS_SUB_DIR="$2";shift;shift;; - --numpy-array-use-type-var) NUMPY_FORMAT="$1";shift;; - --numpy-array-wrap-with-annotated) NUMPY_FORMAT="$1";shift;; - *) usage "Unknown parameter passed: $1"; shift; shift;; - esac; done - - # verify params - if [ -z "$STUBS_SUB_DIR" ]; then usage "STUBS_SUB_DIR is not set"; fi; - if [ -z "$NUMPY_FORMAT" ]; then usage "NUMPY_FORMAT is not set"; fi; - - TESTS_ROOT="$(resolve_path "$(dirname "$0")")" - STUBS_DIR=$(resolve_path "${TESTS_ROOT}/${STUBS_SUB_DIR}") -} - -remove_stubs() { - rm -rf "${STUBS_DIR}/*" ; -} - -get_ruff_version() { - awk ' - $1 == "-" && $2 == "repo:" && $3 == "https://github.com/astral-sh/ruff-pre-commit" { - found = 1 - next - } - found && $1 == "rev:" { - sub(/^v/, "", $2) - print $2 - exit - } - ' "${TESTS_ROOT}/../.pre-commit-config.yaml" -} - -run_stubgen() { - pybind11-stubgen \ - demo \ - --output-dir=${STUBS_DIR} \ - ${NUMPY_FORMAT} \ - --ignore-invalid-expressions="\(anonymous namespace\)::(Enum|Unbound)| str: for name in sorted(expected.keys() | actual.keys()): if name in expected and name in actual and expected[name] == actual[name]: continue - kind = "Missing" if name not in actual else "Unexpected" if name not in expected else "Changed" + kind = ( + "Missing" + if name not in actual + else "Unexpected" + if name not in expected + else "Changed" + ) differences.append(f"{kind}: {name}\n") before = expected.get(name, b"").decode("utf-8", errors="backslashreplace") after = actual.get(name, b"").decode("utf-8", errors="backslashreplace") - differences.extend(difflib.unified_diff( - before.splitlines(keepends=True), after.splitlines(keepends=True), - fromfile=f"expected/{name}", tofile=f"actual/{name}", - )) + differences.extend( + difflib.unified_diff( + before.splitlines(keepends=True), + after.splitlines(keepends=True), + fromfile=f"expected/{name}", + tofile=f"actual/{name}", + ) + ) return "".join(differences) @@ -97,8 +107,12 @@ class SnapshotMismatch(HarnessError): def check_snapshot( - root: Path, profile: Path, actual: Snapshot, *, - update: bool = False, only: frozenset[str] | None = None, + root: Path, + profile: Path, + actual: Snapshot, + *, + update: bool = False, + only: frozenset[str] | None = None, ) -> tuple[str, ...]: for name, content in actual.items(): path = relative_file(name) @@ -124,15 +138,22 @@ def check_snapshot( return () if not update: raise SnapshotMismatch(difference) - changed = tuple(sorted( - name for name in expected.keys() | actual.keys() - if name not in expected or name not in actual or expected[name] != actual[name] - )) + changed = tuple( + sorted( + name + for name in expected.keys() | actual.keys() + if name not in expected + or name not in actual + or expected[name] != actual[name] + ) + ) for name in expected.keys() - actual.keys(): (destination / relative_file(name)).unlink() if only is None: directories = [path for path in destination.rglob("*") if path.is_dir()] - for directory in sorted(directories, key=lambda path: len(path.parts), reverse=True): + for directory in sorted( + directories, key=lambda path: len(path.parts), reverse=True + ): try: directory.rmdir() except OSError as error: @@ -147,13 +168,19 @@ def check_snapshot( def run_command( - argv: list[str], *, cwd: Path, log: Path, expected_status: int = 0, + argv: list[str], + *, + cwd: Path, + log: Path, + expected_status: int = 0, ) -> subprocess.CompletedProcess[bytes]: result = None stdout = stderr = b"" problem = None try: - result = subprocess.run(argv, cwd=cwd, capture_output=True, timeout=300, check=False) + result = subprocess.run( + argv, cwd=cwd, capture_output=True, timeout=300, check=False + ) stdout, stderr = result.stdout, result.stderr if result.returncode != expected_status: problem = f"exit {result.returncode}, expected {expected_status}" @@ -165,11 +192,19 @@ def run_command( log.parent.mkdir(parents=True, exist_ok=True) log.with_suffix(".stdout").write_bytes(stdout) log.with_suffix(".stderr").write_bytes(stderr) - log.with_suffix(".json").write_text(json.dumps({ - "command": argv, "cwd": str(cwd), - "returncode": result.returncode if result is not None else None, - "error": problem, - }, indent=2) + "\n", encoding="utf-8") + log.with_suffix(".json").write_text( + json.dumps( + { + "command": argv, + "cwd": str(cwd), + "returncode": result.returncode if result is not None else None, + "error": problem, + }, + indent=2, + ) + + "\n", + encoding="utf-8", + ) if problem is not None: raise HarnessError( f"{argv!r}: {problem}\nLogs: {log}\n" @@ -184,7 +219,9 @@ def normalize_stderr(stderr: bytes) -> bytes: return re.sub(rb"0x[0-9A-Fa-f]+", b"0x1234abcd5678", stderr) -def validate_error_run(result: subprocess.CompletedProcess[bytes], output: Path) -> bytes: +def validate_error_run( + result: subprocess.CompletedProcess[bytes], output: Path +) -> bytes: if result.returncode != 1: raise HarnessError(f"Expected fatal-error exit 1, got {result.returncode}") if b"Traceback (most recent call last)" in result.stderr: @@ -199,25 +236,43 @@ def validate_error_run(result: subprocess.CompletedProcess[bytes], output: Path) def ruff_version(repo: Path) -> str: text = (repo / ".pre-commit-config.yaml").read_text(encoding="utf-8") blocks = re.split(r"(?m)^\s*-\s+repo:\s*", text)[1:] - blocks = [block for block in blocks if block.splitlines()[0].strip().strip("\"'") - == "https://github.com/astral-sh/ruff-pre-commit"] + blocks = [ + block + for block in blocks + if block.splitlines()[0].strip().strip("\"'") + == "https://github.com/astral-sh/ruff-pre-commit" + ] if len(blocks) != 1: raise HarnessError("Cannot identify the Ruff pre-commit pin") - match = re.search(r'''(?m)^\s*rev:\s*["']?v?(\d+\.\d+\.\d+)["']?\s*$''', blocks[0]) + match = re.search(r"""(?m)^\s*rev:\s*["']?v?(\d+\.\d+\.\d+)["']?\s*$""", blocks[0]) if match is None: raise HarnessError("Cannot resolve the Ruff pre-commit version") return match.group(1) def format_stubs( - output: Path, repo: Path, workspace: Path, python_version: tuple[int, int], + output: Path, + repo: Path, + workspace: Path, + python_version: tuple[int, int], ) -> None: prefix = ["uvx", "--from", f"ruff=={ruff_version(repo)}", "ruff"] - options = ["--config", str(repo / "pyproject.toml"), "--target-version", - f"py{python_version[0]}{python_version[1]}", "--no-cache", str(output)] - run_command(prefix + ["format"] + options, cwd=workspace, log=workspace / "ruff-format") - run_command(prefix + ["check", "--select", "I,RUF022", "--fix"] + options, - cwd=workspace, log=workspace / "ruff-check") + options = [ + "--config", + str(repo / "pyproject.toml"), + "--target-version", + f"py{python_version[0]}{python_version[1]}", + "--no-cache", + str(output), + ] + run_command( + prefix + ["format"] + options, cwd=workspace, log=workspace / "ruff-format" + ) + run_command( + prefix + ["check", "--select", "I,RUF022", "--fix"] + options, + cwd=workspace, + log=workspace / "ruff-check", + ) def _write_diagnostic(path: Path, content: str) -> None: @@ -230,8 +285,13 @@ def _write_diagnostic(path: Path, content: str) -> None: @contextmanager def diagnostics( - workspace: Path, *, artifacts: Path | None, reference_roots: tuple[Path, ...], - case_id: str, check_name: str, expected: Path, + workspace: Path, + *, + artifacts: Path | None, + reference_roots: tuple[Path, ...], + case_id: str, + check_name: str, + expected: Path, ) -> Iterator[None]: workspace = workspace.resolve() for root in reference_roots: @@ -248,7 +308,9 @@ def diagnostics( if not artifact_parent.is_relative_to(base): raise HarnessError("Artifact identifiers escape their destination") for candidate in (base, artifact_parent): - if any(candidate.is_relative_to(root.resolve()) for root in reference_roots): + if any( + candidate.is_relative_to(root.resolve()) for root in reference_roots + ): raise HarnessError("Artifact destination is inside a reference tree") if artifact_parent.is_relative_to(workspace): raise HarnessError("Artifact destination is inside the working workspace") @@ -283,7 +345,9 @@ def diagnostics( target.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(source, target) except OSError as retention_error: - summary += f"\nArtifact retention failed ({destination}): {retention_error}" + summary += ( + f"\nArtifact retention failed ({destination}): {retention_error}" + ) else: summary += f"\nArtifacts: {destination}" raise HarnessError(summary) from error @@ -316,7 +380,11 @@ def errors_root(self) -> Path: @property def stub_profile(self) -> Path: major, minor = self.python_version - return Path(f"python-{major}.{minor}") / f"pybind11-{self.branch}" / self.numpy_format + return ( + Path(f"python-{major}.{minor}") + / f"pybind11-{self.branch}" + / self.numpy_format + ) @property def error_profile(self) -> Path: @@ -324,12 +392,17 @@ def error_profile(self) -> Path: def make_case( - repo: Path, python_version: tuple[int, int], branch: str | None, numpy_format: str | None, + repo: Path, + python_version: tuple[int, int], + branch: str | None, + numpy_format: str | None, ) -> DemoCase: if python_version < (3, 10): raise HarnessError("The test harness requires Python >=3.10") if branch not in BRANCHES or numpy_format not in NUMPY_FORMATS: - raise HarnessError("Native tests require --pybind11-branch and --numpy-format; use tox to build the matching demo") + raise HarnessError( + "Native tests require --pybind11-branch and --numpy-format; use tox to build the matching demo" + ) case = DemoCase(repo.resolve(), python_version, branch, numpy_format) resolve_profile(case.stubs_root, case.stub_profile) resolve_profile(case.errors_root, case.error_profile) diff --git a/tests/test_demo_errors.py b/tests/test_demo_errors.py index 6ba51753..4aa05b20 100644 --- a/tests/test_demo_errors.py +++ b/tests/test_demo_errors.py @@ -1,23 +1,53 @@ import sys -from snapshot_helpers import check_snapshot, diagnostics, run_command, validate_error_run +from snapshot_helpers import ( + check_snapshot, + diagnostics, + run_command, + validate_error_run, +) -def test_demo_errors(demo_case, tmp_path, update_snapshots, artifacts_dir, report_update): +def test_demo_errors( + demo_case, tmp_path, update_snapshots, artifacts_dir, report_update +): case = demo_case expected = case.errors_root / case.error_profile output = tmp_path / "output" filename = "demo.errors.stderr.txt" - with diagnostics(tmp_path, artifacts=artifacts_dir, - reference_roots=(case.stubs_root, case.errors_root), - case_id=case.id, check_name="errors", expected=expected / filename): - result = run_command([ - sys.executable, "-I", "-m", "pybind11_stubgen", "demo", - "--output-dir", str(output), "--exit-code", - ], cwd=tmp_path, log=tmp_path / "stubgen", expected_status=1) + with diagnostics( + tmp_path, + artifacts=artifacts_dir, + reference_roots=(case.stubs_root, case.errors_root), + case_id=case.id, + check_name="errors", + expected=expected / filename, + ): + result = run_command( + [ + sys.executable, + "-I", + "-m", + "pybind11_stubgen", + "demo", + "--output-dir", + str(output), + "--exit-code", + ], + cwd=tmp_path, + log=tmp_path / "stubgen", + expected_status=1, + ) stderr = validate_error_run(result, output) (tmp_path / "normalized.stderr").write_bytes(stderr) - changed = check_snapshot(case.errors_root, case.error_profile, {filename: stderr}, - update=update_snapshots, only=frozenset({filename})) + changed = check_snapshot( + case.errors_root, + case.error_profile, + {filename: stderr}, + update=update_snapshots, + only=frozenset({filename}), + ) if update_snapshots: - report_update(f"{case.id}: {expected} -> {expected.resolve()}; changed: {changed}") + report_update( + f"{case.id}: {expected} -> {expected.resolve()}; changed: {changed}" + ) diff --git a/tests/test_demo_stubs.py b/tests/test_demo_stubs.py index 38e6dcff..5503bbf2 100644 --- a/tests/test_demo_stubs.py +++ b/tests/test_demo_stubs.py @@ -1,31 +1,61 @@ import sys from snapshot_helpers import ( - HarnessError, check_snapshot, diagnostics, format_stubs, read_tree, run_command, + HarnessError, + check_snapshot, + diagnostics, + format_stubs, + read_tree, + run_command, ) -def test_demo_stubs(demo_case, tmp_path, update_snapshots, artifacts_dir, report_update): +def test_demo_stubs( + demo_case, tmp_path, update_snapshots, artifacts_dir, report_update +): case = demo_case expected = case.stubs_root / case.stub_profile output = tmp_path / "output" - with diagnostics(tmp_path, artifacts=artifacts_dir, - reference_roots=(case.stubs_root, case.errors_root), - case_id=case.id, check_name="stubs", expected=expected): - run_command([ - sys.executable, "-I", "-m", "pybind11_stubgen", "demo", - "--output-dir", str(output), f"--{case.numpy_format}", - r"--ignore-invalid-expressions=\(anonymous namespace\)::(Enum|Unbound)| {expected.resolve()}; changed: {changed}") + report_update( + f"{case.id}: {expected} -> {expected.resolve()}; changed: {changed}" + ) diff --git a/tests/test_snapshot_helpers.py b/tests/test_snapshot_helpers.py index 64ad334c..30723e50 100644 --- a/tests/test_snapshot_helpers.py +++ b/tests/test_snapshot_helpers.py @@ -102,17 +102,27 @@ def test_check_update_preserve_aliases_unrelated_files_and_index(tmp_path, updat def test_stderr_update_is_scoped_to_one_file(tmp_path): write_files(tmp_path / "case", {"stderr.txt": b"old", "unrelated.txt": b"keep"}) - h.check_snapshot(tmp_path, Path("case"), {"stderr.txt": b"new"}, - update=True, only=frozenset({"stderr.txt"})) + h.check_snapshot( + tmp_path, + Path("case"), + {"stderr.txt": b"new"}, + update=True, + only=frozenset({"stderr.txt"}), + ) assert h.read_tree(tmp_path / "case") == { - "stderr.txt": b"new", "unrelated.txt": b"keep", + "stderr.txt": b"new", + "unrelated.txt": b"keep", } -@pytest.mark.parametrize("actual", [ - {"../escape": b"bad"}, {"x": b"file", "x/child": b"collision"}, - {"x": "not bytes"}, -]) +@pytest.mark.parametrize( + "actual", + [ + {"../escape": b"bad"}, + {"x": b"file", "x/child": b"collision"}, + {"x": "not bytes"}, + ], +) def test_update_validates_all_content_before_writing(tmp_path, actual): write_files(tmp_path / "case", {"keep.pyi": b"original"}) with pytest.raises(h.HarnessError): @@ -136,7 +146,9 @@ def test_update_handles_file_directory_shape_changes(tmp_path): @pytest.mark.parametrize("error_number", [errno.EACCES, errno.EPERM, errno.EIO]) -def test_update_propagates_directory_cleanup_failures(tmp_path, monkeypatch, error_number): +def test_update_propagates_directory_cleanup_failures( + tmp_path, monkeypatch, error_number +): write_files(tmp_path / "case", {"keep.pyi": b"old"}) blocked = tmp_path / "case/empty" blocked.mkdir() @@ -168,7 +180,10 @@ def nonempty_rmdir(path): monkeypatch.setattr(Path, "rmdir", nonempty_rmdir) assert h.check_snapshot( - tmp_path, Path("case"), {"nested/keep.pyi": b"new"}, update=True, + tmp_path, + Path("case"), + {"nested/keep.pyi": b"new"}, + update=True, ) == ("nested/keep.pyi",) assert (retained / "keep.pyi").read_bytes() == b"new" @@ -176,27 +191,45 @@ def nonempty_rmdir(path): def test_scoped_update_rejects_extra_actual_files_before_writing(tmp_path): write_files(tmp_path / "case", {"stderr.txt": b"original", "unrelated": b"keep"}) with pytest.raises(h.HarnessError, match="scope"): - h.check_snapshot(tmp_path, Path("case"), {"stderr.txt": b"new", "extra": b"bad"}, - update=True, only=frozenset({"stderr.txt"})) - assert h.read_tree(tmp_path / "case") == {"stderr.txt": b"original", "unrelated": b"keep"} + h.check_snapshot( + tmp_path, + Path("case"), + {"stderr.txt": b"new", "extra": b"bad"}, + update=True, + only=frozenset({"stderr.txt"}), + ) + assert h.read_tree(tmp_path / "case") == { + "stderr.txt": b"original", + "unrelated": b"keep", + } @pytest.mark.parametrize("status", [0, 1, 2, 127]) def test_run_command_requires_exact_status_and_retains_logs(tmp_path, status): - command = [sys.executable, "-c", f"import sys; print('diagnostic', file=sys.stderr); sys.exit({status})"] + command = [ + sys.executable, + "-c", + f"import sys; print('diagnostic', file=sys.stderr); sys.exit({status})", + ] if status == 1: - result = h.run_command(command, cwd=tmp_path, log=tmp_path / "process", expected_status=1) + result = h.run_command( + command, cwd=tmp_path, log=tmp_path / "process", expected_status=1 + ) assert result.returncode == 1 else: with pytest.raises(h.HarnessError, match="expected 1"): - h.run_command(command, cwd=tmp_path, log=tmp_path / "process", expected_status=1) + h.run_command( + command, cwd=tmp_path, log=tmp_path / "process", expected_status=1 + ) assert b"diagnostic" in (tmp_path / "process.stderr").read_bytes() assert json.loads((tmp_path / "process.json").read_text())["returncode"] == status def test_missing_command_and_timeout_are_diagnostic(tmp_path, monkeypatch): with pytest.raises(h.HarnessError): - h.run_command([str(tmp_path / "missing-command")], cwd=tmp_path, log=tmp_path / "missing") + h.run_command( + [str(tmp_path / "missing-command")], cwd=tmp_path, log=tmp_path / "missing" + ) assert (tmp_path / "missing.json").is_file() def timeout(argv, **kwargs): @@ -210,13 +243,20 @@ def timeout(argv, **kwargs): assert (tmp_path / "timeout.stderr").read_bytes() == b"stuck" -@pytest.mark.parametrize("status,stderr,files", [ - (0, b"Terminating due to previous errors", {}), - (2, b"Terminating due to previous errors", {}), - (1, b"Traceback (most recent call last):\nTerminating due to previous errors", {}), - (1, b"unrelated failure", {}), - (1, b"Terminating due to previous errors", {"unexpected.pyi": b"x"}), -]) +@pytest.mark.parametrize( + "status,stderr,files", + [ + (0, b"Terminating due to previous errors", {}), + (2, b"Terminating due to previous errors", {}), + ( + 1, + b"Traceback (most recent call last):\nTerminating due to previous errors", + {}, + ), + (1, b"unrelated failure", {}), + (1, b"Terminating due to previous errors", {"unexpected.pyi": b"x"}), + ], +) def test_invalid_fatal_error_runs_are_rejected(tmp_path, status, stderr, files): write_files(tmp_path / "output", files) result = subprocess.CompletedProcess(["stubgen"], status, b"", stderr) @@ -230,18 +270,24 @@ def test_error_normalization_is_narrow(tmp_path): assert h.validate_error_run(result, tmp_path / "absent") == ( b"object at 0x1234abcd5678\nTerminating due to previous errors\n" ) - assert h.normalize_stderr(b"ordinary text 123 abc 0xZZ") == b"ordinary text 123 abc 0xZZ" + assert ( + h.normalize_stderr(b"ordinary text 123 abc 0xZZ") + == b"ordinary text 123 abc 0xZZ" + ) def test_ruff_pin_configuration_target_order_and_cache_policy(tmp_path, monkeypatch): repo = tmp_path / "repo" - write_files(repo, { - ".pre-commit-config.yaml": ( - b"repos:\n - repo: unrelated\n rev: v99.0.0\n" - b" - repo: https://github.com/astral-sh/ruff-pre-commit\n rev: v0.15.20\n" - ), - "pyproject.toml": b"[tool.ruff]\nline-length = 88\n", - }) + write_files( + repo, + { + ".pre-commit-config.yaml": ( + b"repos:\n - repo: unrelated\n rev: v99.0.0\n" + b" - repo: https://github.com/astral-sh/ruff-pre-commit\n rev: v0.15.20\n" + ), + "pyproject.toml": b"[tool.ruff]\nline-length = 88\n", + }, + ) commands = [] def record(argv, **kwargs): @@ -264,9 +310,14 @@ def test_invalid_ruff_pin_and_formatter_failure_are_not_ignored(tmp_path, monkey write_files(tmp_path, {".pre-commit-config.yaml": b"repos: []\n"}) with pytest.raises(h.HarnessError, match="Ruff"): h.ruff_version(tmp_path) - write_files(tmp_path, {".pre-commit-config.yaml": ( - b"repos:\n - repo: https://github.com/astral-sh/ruff-pre-commit\n rev: v0.15.20\n" - )}) + write_files( + tmp_path, + { + ".pre-commit-config.yaml": ( + b"repos:\n - repo: https://github.com/astral-sh/ruff-pre-commit\n rev: v0.15.20\n" + ) + }, + ) def fail(argv, **kwargs): raise h.HarnessError("formatter failed") @@ -286,9 +337,14 @@ def test_artifacts_cannot_target_reference_trees(tmp_path, through_alias): alias.symlink_to(refs, target_is_directory=True) destination = alias / "artifacts" with pytest.raises(h.HarnessError, match="reference"): - with h.diagnostics(tmp_path / "work", artifacts=destination, - reference_roots=(refs,), case_id="case", check_name="stubs", - expected=refs / "case"): + with h.diagnostics( + tmp_path / "work", + artifacts=destination, + reference_roots=(refs,), + case_id="case", + check_name="stubs", + expected=refs / "case", + ): raise AssertionError("body must not run") assert h.read_tree(refs) == {"case/x.pyi": b"keep"} @@ -298,9 +354,14 @@ def test_failure_artifacts_include_output_diff_and_context(tmp_path): refs = tmp_path / "refs" write_files(refs / "case", {"x.pyi": b"expected"}) with pytest.raises(h.HarnessError, match="Artifacts:"): - with h.diagnostics(workspace, artifacts=tmp_path / "artifacts", - reference_roots=(refs,), case_id="case", check_name="stubs", - expected=refs / "case"): + with h.diagnostics( + workspace, + artifacts=tmp_path / "artifacts", + reference_roots=(refs,), + case_id="case", + check_name="stubs", + expected=refs / "case", + ): write_files(workspace / "output", {"x.pyi": b"actual"}) raise h.SnapshotMismatch("Changed: x.pyi\n") runs = list((tmp_path / "artifacts/case/stubs").iterdir()) @@ -313,32 +374,50 @@ def test_failure_artifacts_include_output_diff_and_context(tmp_path): def test_artifacts_cannot_recurse_into_workspace(tmp_path): with pytest.raises(h.HarnessError, match="workspace"): - with h.diagnostics(tmp_path, artifacts=tmp_path / "artifacts", - reference_roots=(tmp_path / "refs",), case_id="case", - check_name="stubs", expected=tmp_path / "refs/case"): + with h.diagnostics( + tmp_path, + artifacts=tmp_path / "artifacts", + reference_roots=(tmp_path / "refs",), + case_id="case", + check_name="stubs", + expected=tmp_path / "refs/case", + ): raise AssertionError("body must not run") @pytest.mark.parametrize("component", ["case_id", "check_name"]) @pytest.mark.parametrize("value", ["", "..", "../escape", "/absolute", "nested/name"]) -def test_diagnostics_rejects_invalid_identifiers_before_writing(tmp_path, component, value): +def test_diagnostics_rejects_invalid_identifiers_before_writing( + tmp_path, component, value +): identifiers = {"case_id": "case", "check_name": "stubs", component: value} with pytest.raises(h.HarnessError): - with h.diagnostics(tmp_path / "work", artifacts=tmp_path / "artifacts", - reference_roots=(tmp_path / "refs",), - expected=tmp_path / "refs/case", **identifiers): + with h.diagnostics( + tmp_path / "work", + artifacts=tmp_path / "artifacts", + reference_roots=(tmp_path / "refs",), + expected=tmp_path / "refs/case", + **identifiers, + ): pytest.fail("body must not run") assert list(tmp_path.iterdir()) == [] @pytest.mark.parametrize("artifacts_enabled", [False, True]) -def test_success_records_context_without_retaining_artifacts(tmp_path, artifacts_enabled): +def test_success_records_context_without_retaining_artifacts( + tmp_path, artifacts_enabled +): workspace = tmp_path / "work" artifacts = tmp_path / "artifacts" expected = tmp_path / "refs/case" - with h.diagnostics(workspace, artifacts=artifacts if artifacts_enabled else None, - reference_roots=(tmp_path / "refs",), case_id="case", - check_name="stubs", expected=expected): + with h.diagnostics( + workspace, + artifacts=artifacts if artifacts_enabled else None, + reference_roots=(tmp_path / "refs",), + case_id="case", + check_name="stubs", + expected=expected, + ): assert (workspace / "context.txt").read_text() == ( f"Case: case/stubs\nReference: {expected}\nWorkspace: {workspace}\n" ) @@ -351,9 +430,14 @@ def test_failure_without_artifacts_preserves_original_diagnostics(tmp_path): workspace = tmp_path / "work" expected = tmp_path / "refs/case" with pytest.raises(h.HarnessError) as failure: - with h.diagnostics(workspace, artifacts=None, - reference_roots=(tmp_path / "refs",), case_id="case", - check_name="stubs", expected=expected): + with h.diagnostics( + workspace, + artifacts=None, + reference_roots=(tmp_path / "refs",), + case_id="case", + check_name="stubs", + expected=expected, + ): raise error summary = str(failure.value) assert failure.value.__cause__ is error @@ -370,9 +454,14 @@ def test_failure_artifacts_are_unique_and_skip_symlinks_and_special_files(tmp_pa for index in range(2): workspace = tmp_path / f"work-{index}" with pytest.raises(h.HarnessError, match="rejected output"): - with h.diagnostics(workspace, artifacts=tmp_path / "artifacts", - reference_roots=(refs,), case_id="case", check_name="stubs", - expected=refs / "case"): + with h.diagnostics( + workspace, + artifacts=tmp_path / "artifacts", + reference_roots=(refs,), + case_id="case", + check_name="stubs", + expected=refs / "case", + ): write_files(workspace / "output", {"x.pyi": str(index).encode()}) (workspace / "linked.pyi").symlink_to(refs / "case/x.pyi") (workspace / "linked-dir").symlink_to(refs, target_is_directory=True) @@ -389,7 +478,9 @@ def test_failure_artifacts_are_unique_and_skip_symlinks_and_special_files(tmp_pa @pytest.mark.parametrize("relation", ["inside", "same", "ancestor"]) @pytest.mark.parametrize("through_alias", [False, True]) -def test_diagnostics_rejects_reference_overlapping_workspaces(tmp_path, relation, through_alias): +def test_diagnostics_rejects_reference_overlapping_workspaces( + tmp_path, relation, through_alias +): refs = tmp_path / "refs" write_files(refs / "case", {"x.pyi": b"keep"}) workspace = {"inside": refs / "work", "same": refs, "ancestor": tmp_path}[relation] @@ -399,8 +490,14 @@ def test_diagnostics_rejects_reference_overlapping_workspaces(tmp_path, relation workspace = alias body_ran = False with pytest.raises(h.HarnessError) as failure: - with h.diagnostics(workspace, artifacts=None, reference_roots=(refs,), - case_id="case", check_name="stubs", expected=refs / "case"): + with h.diagnostics( + workspace, + artifacts=None, + reference_roots=(refs,), + case_id="case", + check_name="stubs", + expected=refs / "case", + ): body_ran = True raise h.SnapshotMismatch("Changed: x.pyi\n") assert h.read_tree(refs) == {"case/x.pyi": b"keep"} @@ -418,9 +515,14 @@ def test_diagnostic_writes_do_not_follow_reference_symlinks(tmp_path, name): (workspace / name).symlink_to(refs / "case/x.pyi") error = h.SnapshotMismatch("Changed: x.pyi\n") with pytest.raises(h.HarnessError) as failure: - with h.diagnostics(workspace, artifacts=tmp_path / "artifacts", - reference_roots=(refs,), case_id="case", check_name="stubs", - expected=refs / "case"): + with h.diagnostics( + workspace, + artifacts=tmp_path / "artifacts", + reference_roots=(refs,), + case_id="case", + check_name="stubs", + expected=refs / "case", + ): if name != "context.txt": (workspace / name).symlink_to(refs / "case/x.pyi") raise error @@ -439,9 +541,14 @@ def test_diagnostic_write_errors_preserve_context(tmp_path, name): error = h.SnapshotMismatch("Changed: x.pyi\n") body_ran = False with pytest.raises(h.HarnessError) as failure: - with h.diagnostics(workspace, artifacts=tmp_path / "artifacts", - reference_roots=(tmp_path / "refs",), case_id="case", - check_name="stubs", expected=expected): + with h.diagnostics( + workspace, + artifacts=tmp_path / "artifacts", + reference_roots=(tmp_path / "refs",), + case_id="case", + check_name="stubs", + expected=expected, + ): body_ran = True raise error summary = str(failure.value) @@ -465,9 +572,14 @@ def test_artifact_retention_errors_preserve_original_failure_and_locations(tmp_p expected = tmp_path / "refs/case" error = h.SnapshotMismatch("Changed: x.pyi\n") with pytest.raises(h.HarnessError) as failure: - with h.diagnostics(workspace, artifacts=artifacts, - reference_roots=(tmp_path / "refs",), case_id="case", - check_name="stubs", expected=expected): + with h.diagnostics( + workspace, + artifacts=artifacts, + reference_roots=(tmp_path / "refs",), + case_id="case", + check_name="stubs", + expected=expected, + ): raise error summary = str(failure.value) assert failure.value.__cause__ is error @@ -479,8 +591,10 @@ def test_artifact_retention_errors_preserve_original_failure_and_locations(tmp_p assert artifacts.read_bytes() == b"not a directory" -@pytest.mark.parametrize("branch,mode", [(None, None), ("v9.9", "numpy-array-use-type-var"), - ("v3.0", "invalid-format")]) +@pytest.mark.parametrize( + "branch,mode", + [(None, None), ("v9.9", "numpy-array-use-type-var"), ("v3.0", "invalid-format")], +) def test_case_requires_explicit_recognized_configuration(tmp_path, branch, mode): with pytest.raises(h.HarnessError): h.make_case(tmp_path, (3, 13), branch, mode) @@ -499,7 +613,9 @@ def test_case_requires_existing_profiles_and_uses_runtime_python(tmp_path): @pytest.mark.parametrize("failure_stage", ["generator", "formatter"]) -def test_success_update_never_accepts_failed_generation_or_formatting(tmp_path, monkeypatch, failure_stage): +def test_success_update_never_accepts_failed_generation_or_formatting( + tmp_path, monkeypatch, failure_stage +): import test_demo_stubs as integration repo = tmp_path / "repo" @@ -522,7 +638,9 @@ def format_failure(*args): monkeypatch.setattr(integration, "run_command", generate) monkeypatch.setattr(integration, "format_stubs", format_failure) with pytest.raises(h.HarnessError, match=f"{failure_stage} failed"): - integration.test_demo_stubs(case, tmp_path / "work", True, None, messages.append) + integration.test_demo_stubs( + case, tmp_path / "work", True, None, messages.append + ) assert h.read_tree(reference) == {"demo/__init__.pyi": b"original"} assert messages == [] @@ -537,7 +655,9 @@ def test_error_update_rejects_traceback_even_with_exit_one(tmp_path, monkeypatch def traceback_result(argv, **kwargs): assert argv[:4] == [sys.executable, "-I", "-m", "pybind11_stubgen"] assert kwargs["expected_status"] == 1 - return subprocess.CompletedProcess(argv, 1, b"", b"Traceback (most recent call last):\n") + return subprocess.CompletedProcess( + argv, 1, b"", b"Traceback (most recent call last):\n" + ) monkeypatch.setattr(integration, "run_command", traceback_result) with pytest.raises(h.HarnessError, match="traceback"): @@ -547,16 +667,30 @@ def traceback_result(argv, **kwargs): @pytest.mark.parametrize("kind", ["stubs", "errors"]) @pytest.mark.parametrize("update", [False, True]) -def test_integration_check_and_update_paths_with_synthetic_output(tmp_path, monkeypatch, kind, update): +def test_integration_check_and_update_paths_with_synthetic_output( + tmp_path, monkeypatch, kind, update +): import test_demo_errors import test_demo_stubs - case = h.DemoCase(tmp_path / "repo", (3, 13), "v3.0", "numpy-array-wrap-with-annotated") + case = h.DemoCase( + tmp_path / "repo", (3, 13), "v3.0", "numpy-array-wrap-with-annotated" + ) integration = test_demo_stubs if kind == "stubs" else test_demo_errors - run_test = integration.test_demo_stubs if kind == "stubs" else integration.test_demo_errors - reference = case.stubs_root / case.stub_profile if kind == "stubs" else case.errors_root / case.error_profile + run_test = ( + integration.test_demo_stubs if kind == "stubs" else integration.test_demo_errors + ) + reference = ( + case.stubs_root / case.stub_profile + if kind == "stubs" + else case.errors_root / case.error_profile + ) filename = "demo/__init__.pyi" if kind == "stubs" else "demo.errors.stderr.txt" - new_content = b"new stubs\n" if kind == "stubs" else b"object 0x1234abcd5678\nTerminating due to previous errors\n" + new_content = ( + b"new stubs\n" + if kind == "stubs" + else b"object 0x1234abcd5678\nTerminating due to previous errors\n" + ) write_files(reference, {filename: b"original"}) messages = [] @@ -564,7 +698,9 @@ def generate(argv, **kwargs): if kind == "stubs": write_files(kwargs["cwd"] / "output", {filename: new_content}) return subprocess.CompletedProcess(argv, 0, b"", b"") - return subprocess.CompletedProcess(argv, 1, b"", b"object 0xABCD\nTerminating due to previous errors\n") + return subprocess.CompletedProcess( + argv, 1, b"", b"object 0xABCD\nTerminating due to previous errors\n" + ) monkeypatch.setattr(integration, "run_command", generate) if kind == "stubs": @@ -584,7 +720,9 @@ def generate(argv, **kwargs): def test_empty_output_cannot_erase_references(tmp_path, monkeypatch, empty_at): import test_demo_stubs as integration - case = h.DemoCase(tmp_path / "repo", (3, 13), "v3.0", "numpy-array-wrap-with-annotated") + case = h.DemoCase( + tmp_path / "repo", (3, 13), "v3.0", "numpy-array-wrap-with-annotated" + ) reference = case.stubs_root / case.stub_profile write_files(reference, {"demo/__init__.pyi": b"original"}) @@ -601,3 +739,18 @@ def erase_output(output, repo, workspace, python_version): with pytest.raises(h.HarnessError, match="demo/__init__.pyi"): integration.test_demo_stubs(case, tmp_path / "work", True, None, print) assert h.read_tree(reference) == {"demo/__init__.pyi": b"original"} + + +def test_entry_points_use_pytest_without_legacy_mutating_checks(): + repo = Path(__file__).resolve().parents[1] + tox = (repo / "tox.ini").read_text() + ci = (repo / ".github/workflows/ci.yml").read_text() + assert "-m pytest" in tox and "{posargs}" in tox + assert "pytest>=8,<9" in tox + assert "PYTHON_TAG_FILE" not in tox + native_job = ci.split(" tests:\n", 1)[1].split(" test-cli-options:\n", 1)[0] + assert "-m pytest" in native_job + assert "tmp/pytest-artifacts/" in native_job + assert "--update-snapshots" not in native_job + for script in ("check-demo-stubs-generation.sh", "check-demo-errors-generation.sh"): + assert script not in tox and script not in ci diff --git a/tox.ini b/tox.ini index 2609c8c4..5459a4e9 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ skipsdist = true isolated_build = true [testenv] -description = Regenerate stubs in tests/stubs/ +description = Check generated demo stubs and errors package = skip setenv = @@ -30,8 +30,6 @@ setenv = PROJECT_ROOT = {toxinidir} TESTS_ROOT = {env:PROJECT_ROOT}/tests - STUBS_OUT = {toxinidir}/tests/stubs - PYTHON_TAG_FILE = {envtmpdir}/python_tag.env CMAKE = {envbindir}/cmake allowlist_externals = @@ -39,6 +37,7 @@ allowlist_externals = git deps = + pytest>=8,<9 cmeel==0.59.0 cmeel-eigen==3.4.0.2 numpy~=1.20 @@ -52,11 +51,7 @@ deps = commands_pre = bash -c 'cmake="${CMAKE}" {env:TESTS_ROOT}/install-demo-module.sh' - {envpython} -c "import os,sys; p=os.environ['PYTHON_TAG_FILE']; v=f'python-{sys.version_info.major}.{sys.version_info.minor}'; open(p,'w').write(f'PYTHON_TAG={v}\n'); print(v)" commands = {envpython} -m pip install . - bash -c 'set -a; source "{env:PYTHON_TAG_FILE}"; set +a; \ - {env:TESTS_ROOT}/check-demo-stubs-generation.sh --stubs-sub-dir "stubs/${PYTHON_TAG}/pybind11-{env:PYBIND11_BRANCH}/{env:NUMPY_FORMAT}" --{env:NUMPY_FORMAT}' - - bash {env:TESTS_ROOT}/check-demo-errors-generation.sh "pybind11-{env:PYBIND11_BRANCH}" + {envpython} -m pytest {toxinidir}/tests/test_snapshot_helpers.py {toxinidir}/tests/test_demo_stubs.py {toxinidir}/tests/test_demo_errors.py --pybind11-branch {env:PYBIND11_BRANCH} --numpy-format {env:NUMPY_FORMAT} --artifacts-dir {envtmpdir}/pytest-artifacts {posargs} From 72f58f76d33f4ca818c6435ac23f722f932acdb2 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 24 Sep 2026 05:29:43 +0000 Subject: [PATCH 13/40] test: preserve repository-root Ruff import classification --- tests/snapshot_helpers.py | 7 +++---- tests/test_snapshot_helpers.py | 7 +++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/snapshot_helpers.py b/tests/snapshot_helpers.py index ca75036b..7ca71317 100644 --- a/tests/snapshot_helpers.py +++ b/tests/snapshot_helpers.py @@ -265,12 +265,11 @@ def format_stubs( "--no-cache", str(output), ] - run_command( - prefix + ["format"] + options, cwd=workspace, log=workspace / "ruff-format" - ) + # Preserve repository-root first-party import discovery with explicit config. + run_command(prefix + ["format"] + options, cwd=repo, log=workspace / "ruff-format") run_command( prefix + ["check", "--select", "I,RUF022", "--fix"] + options, - cwd=workspace, + cwd=repo, log=workspace / "ruff-check", ) diff --git a/tests/test_snapshot_helpers.py b/tests/test_snapshot_helpers.py index 30723e50..500ec004 100644 --- a/tests/test_snapshot_helpers.py +++ b/tests/test_snapshot_helpers.py @@ -299,11 +299,14 @@ def record(argv, **kwargs): assert len(commands) == 2 assert commands[0][0][:5] == ["uvx", "--from", "ruff==0.15.20", "ruff", "format"] assert commands[1][0][4:8] == ["check", "--select", "I,RUF022", "--fix"] - for argv, kwargs in commands: + for (argv, kwargs), log_name in zip(commands, ("ruff-format", "ruff-check")): assert argv[argv.index("--config") + 1] == str(repo / "pyproject.toml") assert argv[argv.index("--target-version") + 1] == "py312" assert "--no-cache" in argv - assert kwargs["cwd"] == tmp_path + assert argv[-1] == str(tmp_path / "output") + # Ruff's cwd controls first-party discovery even with explicit config. + assert kwargs["cwd"] == repo + assert kwargs["log"] == tmp_path / log_name def test_invalid_ruff_pin_and_formatter_failure_are_not_ignored(tmp_path, monkeypatch): From 419468d4206b6c5665bc11dd6a368fc8dd2e9f99 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 24 Sep 2026 05:39:59 +0000 Subject: [PATCH 14/40] test: preserve process diagnostics when log writes fail --- tests/snapshot_helpers.py | 36 +++++++--- tests/test_snapshot_helpers.py | 121 +++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 8 deletions(-) diff --git a/tests/snapshot_helpers.py b/tests/snapshot_helpers.py index 7ca71317..31145b52 100644 --- a/tests/snapshot_helpers.py +++ b/tests/snapshot_helpers.py @@ -189,10 +189,7 @@ def run_command( problem = "timed out after 300 seconds" except OSError as error: problem = str(error) - log.parent.mkdir(parents=True, exist_ok=True) - log.with_suffix(".stdout").write_bytes(stdout) - log.with_suffix(".stderr").write_bytes(stderr) - log.with_suffix(".json").write_text( + metadata = ( json.dumps( { "command": argv, @@ -202,15 +199,38 @@ def run_command( }, indent=2, ) - + "\n", - encoding="utf-8", + + "\n" ) - if problem is not None: - raise HarnessError( + log_errors = [] + try: + log.parent.mkdir(parents=True, exist_ok=True) + except OSError as error: + log_errors.append(f"Log persistence failed ({log.parent}): {error}") + else: + # A failed log must not hide process diagnostics or prevent other writes. + for suffix, content in ((".stdout", stdout), (".stderr", stderr)): + path = log.with_suffix(suffix) + try: + path.write_bytes(content) + except OSError as error: + log_errors.append(f"Log persistence failed ({path}): {error}") + path = log.with_suffix(".json") + try: + path.write_text(metadata, encoding="utf-8") + except OSError as error: + log_errors.append(f"Log persistence failed ({path}): {error}") + if problem is not None or log_errors: + if problem is None: + assert result is not None + problem = f"exit {result.returncode}, expected {expected_status}" + summary = ( f"{argv!r}: {problem}\nLogs: {log}\n" f"stdout:\n{stdout.decode('utf-8', errors='replace')}\n" f"stderr:\n{stderr.decode('utf-8', errors='replace')}" ) + if log_errors: + summary += "\n" + "\n".join(log_errors) + raise HarnessError(summary) assert result is not None return result diff --git a/tests/test_snapshot_helpers.py b/tests/test_snapshot_helpers.py index 500ec004..ca29d598 100644 --- a/tests/test_snapshot_helpers.py +++ b/tests/test_snapshot_helpers.py @@ -243,6 +243,127 @@ def timeout(argv, **kwargs): assert (tmp_path / "timeout.stderr").read_bytes() == b"stuck" +@pytest.mark.parametrize("suffix", [".stdout", ".stderr", ".json"]) +@pytest.mark.parametrize("status", [0, 2]) +def test_run_command_log_failure_preserves_context_and_other_logs( + tmp_path, suffix, status +): + log = tmp_path / "process" + blocked = log.with_suffix(suffix) + blocked.mkdir() + command = [ + sys.executable, + "-c", + "import sys; print('captured output'); " + f"print('captured error', file=sys.stderr); sys.exit({status})", + ] + with pytest.raises(h.HarnessError) as failure: + h.run_command(command, cwd=tmp_path, log=log) + summary = str(failure.value) + assert repr(command) in summary + assert f"exit {status}, expected 0" in summary + assert "stdout:\ncaptured output\n" in summary + assert "stderr:\ncaptured error\n" in summary + assert "Log persistence failed" in summary + assert str(blocked) in summary and "[Errno" in summary + if suffix != ".stdout": + assert log.with_suffix(".stdout").read_bytes() == b"captured output\n" + if suffix != ".stderr": + assert log.with_suffix(".stderr").read_bytes() == b"captured error\n" + if suffix != ".json": + metadata = json.loads(log.with_suffix(".json").read_text()) + assert metadata == { + "command": command, + "cwd": str(tmp_path), + "returncode": status, + "error": None if status == 0 else "exit 2, expected 0", + } + + +@pytest.mark.parametrize("suffix", [".stdout", ".stderr", ".json"]) +def test_run_command_log_failure_preserves_timeout(tmp_path, monkeypatch, suffix): + log = tmp_path / "timeout" + blocked = log.with_suffix(suffix) + blocked.mkdir() + + def timeout(argv, **kwargs): + raise subprocess.TimeoutExpired(argv, 300, output=b"partial", stderr=b"stuck") + + monkeypatch.setattr(h.subprocess, "run", timeout) + with pytest.raises(h.HarnessError) as failure: + h.run_command(["fake"], cwd=tmp_path, log=log) + summary = str(failure.value) + assert "['fake']: timed out after 300 seconds" in summary + assert "stdout:\npartial" in summary and "stderr:\nstuck" in summary + assert "Log persistence failed" in summary and str(blocked) in summary + if suffix != ".json": + metadata = json.loads(log.with_suffix(".json").read_text()) + assert metadata["returncode"] is None + assert metadata["error"] == "timed out after 300 seconds" + + +@pytest.mark.parametrize("missing_command", [False, True]) +def test_run_command_log_directory_failure_preserves_process_context( + tmp_path, missing_command +): + blocked = tmp_path / "not-a-directory" + blocked.write_bytes(b"keep") + command = ( + [str(tmp_path / "missing-command")] + if missing_command + else [sys.executable, "-c", "import sys; print('captured'); sys.exit(2)"] + ) + with pytest.raises(h.HarnessError) as failure: + h.run_command(command, cwd=tmp_path, log=blocked / "process") + summary = str(failure.value) + assert repr(command) in summary + if missing_command: + assert "No such file or directory" in summary + else: + assert "exit 2, expected 0" in summary + assert "stdout:\ncaptured\n" in summary + assert "Log persistence failed" in summary and str(blocked) in summary + assert blocked.read_bytes() == b"keep" + + +def test_run_command_log_failure_survives_outer_diagnostics(tmp_path): + workspace = tmp_path / "work" + expected = tmp_path / "refs/case" + command = [ + sys.executable, + "-c", + "import sys; print('captured output'); " + "print('captured error', file=sys.stderr); sys.exit(2)", + ] + with pytest.raises(h.HarnessError) as failure: + with h.diagnostics( + workspace, + artifacts=tmp_path / "artifacts", + reference_roots=(tmp_path / "refs",), + case_id="case", + check_name="stubs", + expected=expected, + ): + (workspace / "process.stderr").mkdir() + h.run_command(command, cwd=workspace, log=workspace / "process") + summary = str(failure.value) + assert "case/stubs" in summary and str(expected) in summary + assert "Artifacts:" in summary + runs = list((tmp_path / "artifacts/case/stubs").iterdir()) + assert len(runs) == 1 + retained = (runs[0] / "failure.txt").read_text() + assert retained == (workspace / "failure.txt").read_text() + for text in (summary, retained): + assert repr(command) in text + assert "exit 2, expected 0" in text + assert "stdout:\ncaptured output\n" in text + assert "stderr:\ncaptured error\n" in text + assert "Log persistence failed" in text + assert str(workspace / "process.stderr") in text + assert (runs[0] / "process.stdout").read_bytes() == b"captured output\n" + assert json.loads((runs[0] / "process.json").read_text())["returncode"] == 2 + + @pytest.mark.parametrize( "status,stderr,files", [ From b23588fe444d3b047ab63c2b54210bdd391ce300 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 24 Sep 2026 13:16:28 +0000 Subject: [PATCH 15/40] test: install tox project with uv --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 5459a4e9..94f68297 100644 --- a/tox.ini +++ b/tox.ini @@ -35,6 +35,7 @@ setenv = allowlist_externals = bash git + uv deps = pytest>=8,<9 @@ -53,5 +54,5 @@ commands_pre = bash -c 'cmake="${CMAKE}" {env:TESTS_ROOT}/install-demo-module.sh' commands = - {envpython} -m pip install . + uv pip install --python "{envpython}" . {envpython} -m pytest {toxinidir}/tests/test_snapshot_helpers.py {toxinidir}/tests/test_demo_stubs.py {toxinidir}/tests/test_demo_errors.py --pybind11-branch {env:PYBIND11_BRANCH} --numpy-format {env:NUMPY_FORMAT} --artifacts-dir {envtmpdir}/pytest-artifacts {posargs} From 68287f9f638551c5e06159d11ebdd76216fc71bf Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 24 Sep 2026 13:36:08 +0000 Subject: [PATCH 16/40] test: align error expectations with registration order Correct only the two canonical stderr expectations. Commit e0aa238 (Resolve Inheritance Sort Issue, #238) intentionally replaced alphabetical inspect.getmembers() module traversal with definition/pybind11 registration order, but left these error files from 84fa6d7 unchanged. The investigation reproduced the old bytes by restoring alphabetical module traversal in a disposable process; current diagnostics differ only in their ordering. Exact 1-based old-line sequences, including unchanged tails: v2.9: 4,5,2,3,7,6,8,1,13,11,9,12,10,14,15,16,17,18,19,20,21 v3.0: 2,1,7,5,3,6,4,8,9,10,11,12,13,14,15 This follows eigen -> enum -> aliases -> flawed_bindings registration: dense_matrix_r before dense_matrix_c, four_row before four_col, and get_unbound_type, accept_unbound_type, accept_unbound_enum, accept_unbound_type_defaulted, accept_unbound_enum_defaulted. The v3.0 fixture has no Eigen dimension errors, as before. Preserve every line byte and multiplicity, both eight-line warning and termination tails, and the v2.13 -> v2.12 -> v2.11 -> v2.9 alias chain. No production, harness normalization, stub, or native fixture changes. Verification: existing focused demo_errors tests fail before and pass after in py313-pb29-naa and py313-pb30-naa. Full unfiltered serial tox passes all 13 environments, each with 97 self-tests and 2 native tests (99 passed; 1287 total). Checks preserve reference trees and index. Two-file pre-commit hooks and git diff --check pass. No snapshot-update flags or remote CI actions; remote CI remains separately unverified. --- tests/errors/pybind11-v2.9/demo.errors.stderr.txt | 14 +++++++------- tests/errors/pybind11-v3.0/demo.errors.stderr.txt | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/errors/pybind11-v2.9/demo.errors.stderr.txt b/tests/errors/pybind11-v2.9/demo.errors.stderr.txt index f8fd843d..fb4dbc47 100644 --- a/tests/errors/pybind11-v2.9/demo.errors.stderr.txt +++ b/tests/errors/pybind11-v2.9/demo.errors.stderr.txt @@ -1,16 +1,16 @@ -pybind11_stubgen - [ ERROR] In demo._bindings.aliases.foreign_enum_default : Invalid expression '' -pybind11_stubgen - [ ERROR] In demo._bindings.eigen.dense_matrix_c : Can't find/import 'm' -pybind11_stubgen - [ ERROR] In demo._bindings.eigen.dense_matrix_c : Can't find/import 'n' pybind11_stubgen - [ ERROR] In demo._bindings.eigen.dense_matrix_r : Can't find/import 'm' pybind11_stubgen - [ ERROR] In demo._bindings.eigen.dense_matrix_r : Can't find/import 'n' -pybind11_stubgen - [ ERROR] In demo._bindings.eigen.four_col_matrix_r : Can't find/import 'm' +pybind11_stubgen - [ ERROR] In demo._bindings.eigen.dense_matrix_c : Can't find/import 'm' +pybind11_stubgen - [ ERROR] In demo._bindings.eigen.dense_matrix_c : Can't find/import 'n' pybind11_stubgen - [ ERROR] In demo._bindings.eigen.four_row_matrix_r : Can't find/import 'n' +pybind11_stubgen - [ ERROR] In demo._bindings.eigen.four_col_matrix_r : Can't find/import 'm' pybind11_stubgen - [ ERROR] In demo._bindings.enum.accept_defaulted_enum : Invalid expression '' -pybind11_stubgen - [ ERROR] In demo._bindings.flawed_bindings.accept_unbound_enum : Invalid expression '(anonymous namespace)::Enum' -pybind11_stubgen - [ ERROR] In demo._bindings.flawed_bindings.accept_unbound_enum_defaulted : Invalid expression '' +pybind11_stubgen - [ ERROR] In demo._bindings.aliases.foreign_enum_default : Invalid expression '' +pybind11_stubgen - [ ERROR] In demo._bindings.flawed_bindings.get_unbound_type : Invalid expression '(anonymous namespace)::Unbound' pybind11_stubgen - [ ERROR] In demo._bindings.flawed_bindings.accept_unbound_type : Invalid expression '(anonymous namespace)::Unbound' +pybind11_stubgen - [ ERROR] In demo._bindings.flawed_bindings.accept_unbound_enum : Invalid expression '(anonymous namespace)::Enum' pybind11_stubgen - [ ERROR] In demo._bindings.flawed_bindings.accept_unbound_type_defaulted : Invalid expression '' -pybind11_stubgen - [ ERROR] In demo._bindings.flawed_bindings.get_unbound_type : Invalid expression '(anonymous namespace)::Unbound' +pybind11_stubgen - [ ERROR] In demo._bindings.flawed_bindings.accept_unbound_enum_defaulted : Invalid expression '' pybind11_stubgen - [WARNING] Enum-like str representations were found with no matching mapping to the enum class location. Use `--enum-class-locations` to specify full path to the following enum(s): - ConsoleForegroundColor diff --git a/tests/errors/pybind11-v3.0/demo.errors.stderr.txt b/tests/errors/pybind11-v3.0/demo.errors.stderr.txt index e858fa4a..ff615a21 100644 --- a/tests/errors/pybind11-v3.0/demo.errors.stderr.txt +++ b/tests/errors/pybind11-v3.0/demo.errors.stderr.txt @@ -1,10 +1,10 @@ -pybind11_stubgen - [ ERROR] In demo._bindings.aliases.foreign_enum_default : Invalid expression '' pybind11_stubgen - [ ERROR] In demo._bindings.enum.accept_defaulted_enum : Invalid expression '' -pybind11_stubgen - [ ERROR] In demo._bindings.flawed_bindings.accept_unbound_enum : Invalid expression '(anonymous namespace)::Enum' -pybind11_stubgen - [ ERROR] In demo._bindings.flawed_bindings.accept_unbound_enum_defaulted : Invalid expression '' +pybind11_stubgen - [ ERROR] In demo._bindings.aliases.foreign_enum_default : Invalid expression '' +pybind11_stubgen - [ ERROR] In demo._bindings.flawed_bindings.get_unbound_type : Invalid expression '(anonymous namespace)::Unbound' pybind11_stubgen - [ ERROR] In demo._bindings.flawed_bindings.accept_unbound_type : Invalid expression '(anonymous namespace)::Unbound' +pybind11_stubgen - [ ERROR] In demo._bindings.flawed_bindings.accept_unbound_enum : Invalid expression '(anonymous namespace)::Enum' pybind11_stubgen - [ ERROR] In demo._bindings.flawed_bindings.accept_unbound_type_defaulted : Invalid expression '' -pybind11_stubgen - [ ERROR] In demo._bindings.flawed_bindings.get_unbound_type : Invalid expression '(anonymous namespace)::Unbound' +pybind11_stubgen - [ ERROR] In demo._bindings.flawed_bindings.accept_unbound_enum_defaulted : Invalid expression '' pybind11_stubgen - [WARNING] Enum-like str representations were found with no matching mapping to the enum class location. Use `--enum-class-locations` to specify full path to the following enum(s): - ConsoleForegroundColor From 6fd7bae4f7548b3f064f0bbf815ba2bfd52d2314 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 03:33:52 +0000 Subject: [PATCH 17/40] docs: define compiler-free phase-two test design --- ...026-09-25-test-harness-phase-two-design.md | 290 ++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-25-test-harness-phase-two-design.md diff --git a/docs/superpowers/specs/2026-09-25-test-harness-phase-two-design.md b/docs/superpowers/specs/2026-09-25-test-harness-phase-two-design.md new file mode 100644 index 00000000..7b1279ac --- /dev/null +++ b/docs/superpowers/specs/2026-09-25-test-harness-phase-two-design.md @@ -0,0 +1,290 @@ +# Test harness phase two: compiler-free production tests + +## Status and starting point + +The scope, test structure, and execution/acceptance design below were approved +in conversation. This written specification is awaiting user review before an +implementation plan is written. No phase-two implementation is included here. + +Build on `test-harness-phase-one` at `a306491`, including the uv tox installation +fix and the separately reviewed stderr-order corrections. Use the existing +isolated worktree; do not merge or publish phase one as a prerequisite. + +Phase one's last recorded local verification passed all 13 native tox +configurations, 99 tests each. Remote CI and diagnostic artifact-upload +acceptance remain unverified. The user explicitly deferred those remote checks +and authorized proceeding with phase two; this is not a claim that remote +acceptance passed. + +This implements phase two of the +[phase-one roadmap](2026-09-24-test-harness-phase-one-design.md#later-phase-roadmap). +It does not include the shared orchestration/build changes or snapshot +reorganization reserved for phases three and four. + +## Goal + +Add fast, focused tests of existing generator behavior that run without a native +demo installation or compiler. Failures should identify a parser, annotation, +class-ordering, printer, or writer contract rather than require interpreting a +large generated-tree diff. + +Phase two is test-only: production behavior is preserved. Any newly discovered +production bugs are reported separately instead of being fixed or silently +accepted as new desired behavior. + +## Scope and invariants + +Allowed changes: + +- New tests and narrowly scoped test support under `tests/unit/`. +- Explicit compiler-free environments in `tox.ini`. +- A compiler-free job matrix and its gating dependency in + `.github/workflows/ci.yml`. +- Test documentation and the phase-two design/implementation-plan documents. + +Preserve unchanged: + +- All files under `pybind11_stubgen/`; no testability refactors or runtime fixes. +- Native source, build configuration, `tests/install-demo-module.sh`, and the + existing mixed Python/native demo package, including its Python fixtures. +- All reference contents and aliases under `tests/stubs/` and `tests/errors/`. +- Existing snapshot comparison, update, normalization, and diagnostic behavior. +- The 13 native tox configurations, their default environment list, and native + CI configurations. Keep their native execution serial locally. +- Existing gemmi CLI jobs and publication trigger/credentials. + +Do not add a snapshot framework, coverage-percentage target, production +dependency, broad dependency upgrade, or third-party fixture dependency. No +push, pull request, remote workflow execution, or publication is part of this +work. Configuring a future CI job does not authorize executing it remotely. + +## Chosen approach + +Use focused production tests with small independent Python fixtures. Prefer +explicit model and output assertions, with a few small parser-to-printer-to-writer +smoke tests to establish that the pieces fit together. + +Alternatives considered: + +1. Extract the existing Python demo fixtures. This would reuse examples but + risks changing module identities, native package layout, and snapshots. +2. Add only pure-Python end-to-end snapshots. This would provide broad coverage + but retain coarse failures and unnecessary expectation-management overhead. + +Neither alternative is needed for the first compiler-free production layer. + +## Test organization and data flow + +Use the following organization, with only small shared helpers where repeated +setup genuinely warrants them: + +```text +tests/unit/ + conftest.py + fixtures/ # Small independent Python modules/packages. + test_parser.py + test_annotations.py + test_class_ordering.py + test_printer.py + test_writer.py + test_pipeline.py +``` + +Keep the test directories from introducing an import path that shadows an +installed generator in wheel tests. Fixture packages use distinctive names and +are used only by tests, not imported by production. Do not change distribution +package discovery in this phase: the existing installed distribution already +contains test files, so excluding them would be a separate packaging change. + +The fixtures depend only on the standard library. They do not import `demo`, +its native bindings, NumPy, SciPy, or the existing `demo.pure_python` package: +importing that package initializes the native-dependent parent. Existing demo +fixtures remain untouched and continue to serve native integration coverage. + +Use production parser construction via `arg_parser`, `CLIArgs`, and +`stub_parser_from_args` for composed behavior, with a fresh parser per test. +Small direct tests may target a relevant parsing or ordering helper, but do not +recreate the production mixin stack in test code or substitute mocked parsing +for the behavior being tested. + +Tests follow these boundaries: + +- Parser/annotation tests: explicit Python objects or signature strings to + structured production models and expected diagnostics. +- Printer/order tests: explicitly constructed production models to exact output + lines and dependency-respecting order. +- Writer tests: explicit models plus the real printer to temporary paths and + exact file bytes. +- Pipeline tests: independent Python fixture modules through the real parser, + printer, and writer to a small, explicitly asserted output tree. + +Expected results are hand-specified, not generated by the implementation under +test. No formatter is needed to compare the printer's own output, and no +snapshot-update operation is introduced. + +## Coverage contracts + +### Parser + +Cover ordinary inspectable Python signatures and pybind11-style docstring +signatures supplied as strings or Python test objects; no compiled function is +required for the latter. + +Cases include argument annotations/defaults, positional-only and keyword-only +arguments, variadic arguments, overload structure/order, and malformed or +non-signature docstrings. Check model fields and documented fallback/diagnostic +behavior rather than only the number of returned functions. Add focused +module/class traversal coverage using independent Python fixtures where it +supports ordering and pipeline contracts. + +### Annotations + +Cover nested parameterized types, unions, literal values, and malformed +expressions. Assert structured annotations, preserving distinctions between +resolved types, values, and invalid expressions. Include relevant composed +normalization behavior without requiring NumPy/SciPy imports. + +Exercise Python-version-specific behavior on the appropriate interpreter. +Version conditions must be explicit and explained by syntax/runtime support, +not broad skips that hide a broken fixture. Newer syntax must not prevent test +collection on Python 3.10; keep it in strings or suitably isolated fixtures. + +### Class ordering + +Cover base-before-derived ordering, class-body aliases and print-safe field +values that reference sibling classes, dotted local references, and independent +classes retaining stable input priority. Check irrelevant external/unsafe +references and duplicate dependency edges without changing sorting policy. + +Include empty/singleton inputs, nested class scopes, and cyclic dependencies. +For cycles, assert the existing warning and deterministic fallback ordering; +do not redesign the fallback. A small printer/pipeline case must also exercise +ordering so that helper-only tests cannot miss broken integration. + +### Printer + +Construct minimal models to cover argument separators, annotations/defaults, +function/method/decorator rendering, representative property/class output, +imports, docstrings, and module structure. Verify invalid-expression rendering +and value-comment options with explicit expected output. + +Prefer one clear contract per case rather than a large all-features fixture. +For small complete outputs, supplement exact text checks with a syntax parse +where valid on the current Python version; parsing alone is not an oracle for +correct output. + +### Writer + +Use `tmp_path` and the real printer to cover a standalone module, a package +including an empty package, nested submodules, explicit output subdirectories, +and both supported `.pyi`/`.py` extensions. Assert the exact file set, UTF-8 +contents, and final newlines. Include the existing failure contract for invalid +output roots and propagation of write failures using portable setup. + +Do not add output cleanup, transactional writes, path validation, or other new +writer guarantees. Tests cover current intended behavior only. + +### Small pipeline checks + +Use a few compact independent fixtures to connect parsing, finalization, +printing, and writing. Assert meaningful imports/signatures/order and exact +small output trees. Keep these checks in-process unless a specific process +boundary is necessary; native CLI coverage already exists. + +## Isolation and error handling + +Create fresh mutable models and parser instances per test. Use pytest temporary +directories for output. Any fixture manipulation of `sys.modules`, import paths, +logging, or environment must be scoped and restored, including failure paths. +Do not rely on execution order or another test's imports. + +Expected invalid input is tested by asserting the relevant diagnostic and +fallback. Avoid global ignore-all-errors configuration or blanket log +suppression. Native configuration options are unnecessary for this suite, and +collecting/running it must not request the existing native demo fixtures. + +When a new case reveals an unexpected production failure: + +1. Reproduce it and distinguish a test-assumption error from a production bug. +2. Record the minimal input, expected/current result, affected versions, and + relevant code location separately in the implementation report. +3. Do not change production or update native references to make it pass. +4. Do not silently replace a valid assertion with observed buggy output or + introduce an unexplained skip/xfail. If it blocks an agreed contract, surface + that boundary for a user decision before claiming acceptance. + +## Local execution + +Document the direct source-checkout command: + +```sh +uv run --no-project --with 'pytest>=8,<9' python -m pytest \ + tests/unit tests/test_snapshot_helpers.py +``` + +This provides pytest without syncing the native-heavy development dependency +group. The suite needs only the standard library, pytest and its dependencies, +and the generator code. Running it must not build/install the demo, download a +formatter, or invoke a compiler. + +Add explicit tox environments named `py310-unit`, `py311-unit`, `py312-unit`, and +`py313-unit`, invoked together with: + +```sh +uv run --no-project --with tox --with tox-uv tox \ + -e py310-unit,py311-unit,py312-unit,py313-unit +``` + +Keep the default native `env_list` unchanged. Unit environments must override +inherited native setup, dependencies, environment settings, and commands; merely +changing the pytest selection is insufficient. Install the generator +non-editably using uv with the environment interpreter selected explicitly, +then run the same compiler-free selection against that installed package. +Do not require pip inside the tox environment. + +Use isolated Python invocation and test import layout that keep the checkout +from shadowing the installation. Verify the generator import origin in the +actual pytest process for installed-package runs, not only in an unrelated +preflight process. The direct source command remains intentionally distinct +from installed-package verification. + +## CI configuration + +Add a separate compiler-free matrix for Python 3.10, 3.11, 3.12, and 3.13. Consume +the existing build job's wheel artifact. Install only that wheel and pytest +(with its dependencies) into a fresh uv environment, then run the compiler-free +selection with installed-origin enforcement. Do not use `uv sync` or install +the native development group in these jobs. + +Preserve the existing native and gemmi jobs. Add the new job to the publication +job's dependency gate so future releases cannot bypass it; do not change the +publication condition or permissions. No remote jobs will be launched during +this phase. Report remote results as unverified, not inferred from local runs. + +## Verification and acceptance + +Implementation is ready for local acceptance when: + +- The compiler-free suite and harness self-tests pass on all four Python + versions via the explicit unit tox environments. +- A clean pytest-only source run succeeds without optional/native packages, and + the documented fast command does not invoke native setup or formatting. +- Installed-package runs enforce import origin during pytest. Locally built + wheel verification exercises the CI-style setup on Python 3.10–3.13 without + syncing development dependencies. Fixture imports do not substitute for the + installed generator or require the native demo. +- Tests cover all five focused areas plus small pipeline checks with explicit + expectations. Review checks that assertions are discriminating, not merely + successful execution or results recomputed by production helpers. +- All 13 existing native tox environments still pass unfiltered and serially. + These checks leave reference bytes, alias targets, and the Git index unchanged. +- Production, native/demo sources, installation script, and snapshots have no + changes from the phase-two base. Review configuration diffs for preserved + native/gemmi coverage and untouched publication triggers. +- Lock checks and existing pre-commit hooks pass. No dependency update is planned. +- Any discovered bugs and verification limits are reported explicitly. Remote + CI and actual artifact-upload validation remain deferred by user choice. + +These are behavioral and isolation criteria, not a target test count or coverage +percentage. The implementation plan will break this design into reviewable, +subagent-sized tasks after the user approves this written specification. From d25ee602e64c9484bd7d1fd9f8ebd43378a760db Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 03:50:11 +0000 Subject: [PATCH 18/40] docs: plan compiler-free production test implementation --- .../2026-09-25-test-harness-phase-two.md | 1117 +++++++++++++++++ ...026-09-25-test-harness-phase-two-design.md | 4 +- 2 files changed, 1119 insertions(+), 2 deletions(-) create mode 100644 docs/superpowers/plans/2026-09-25-test-harness-phase-two.md diff --git a/docs/superpowers/plans/2026-09-25-test-harness-phase-two.md b/docs/superpowers/plans/2026-09-25-test-harness-phase-two.md new file mode 100644 index 00000000..51607c82 --- /dev/null +++ b/docs/superpowers/plans/2026-09-25-test-harness-phase-two.md @@ -0,0 +1,1117 @@ +# Compiler-Free Production Tests Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add focused compiler-free tests of existing generator contracts, with independent Python fixtures and installed-wheel verification on Python 3.10–3.13. + +**Architecture:** Separate parser, annotation, class-ordering, printer, and writer tests under `tests/unit/`, plus small real-component pipeline checks. Use explicit expected models/text, standard-library fixtures, and existing pytest facilities rather than a second snapshot harness. Add explicit unit tox environments and a separate wheel-based CI matrix without changing the native matrix. + +**Tech Stack:** Python 3.10–3.13, pytest>=8,<9, uv, tox with tox-uv, existing production dataclasses/parser/printer/writer, GitHub Actions. + +**Spec:** `docs/superpowers/specs/2026-09-25-test-harness-phase-two-design.md` (approved). + +## Global Constraints + +- Phase two is test-only: production behavior is preserved. +- Preserve all files under `pybind11_stubgen/`; no testability refactors or runtime fixes. +- All reference contents and aliases under `tests/stubs/` and `tests/errors/` remain unchanged. +- Preserve native source/build configuration, `tests/install-demo-module.sh`, and the entire existing mixed Python/native demo package. +- Preserve existing snapshot comparison, update, normalization, and diagnostic behavior. +- Preserve the 13 native tox configurations, their default environment list, and native CI configurations. Keep their native execution serial locally. +- Preserve existing gemmi CLI jobs and publication trigger/credentials. +- Do not add a snapshot framework, coverage-percentage target, production dependency, broad dependency upgrade, or third-party fixture dependency. +- No push, pull request, remote workflow execution, or publication is part of this work. +- Do not change distribution package discovery: the existing installed distribution already contains test files. +- Report unexpected production bugs separately. No production fix, snapshot update, unexplained skip/xfail, or weaker expectation may make a task green. + +## Workspace, verification protocol, and file map + +Work in the existing `.worktrees/test-harness-phase-one` worktree on branch +`test-harness-phase-one`. Runtime/configuration baseline is `a306491`; the +approved spec was introduced in `640bccc`. Detect existing isolation with the +worktree skill rather than creating another worktree. Preserve unrelated work. +Read both the approved spec and this plan before implementing any task. + +Read `superpowers:test-driven-development` and its `writing-good-tests.md` +reference. This is coverage of existing production, not permission to rewrite +it: add tests first, observe missing test-support failures where relevant, and +prove assertion sensitivity with the process-local negative controls below. +They deliberately break a real behavior in an isolated disposable process, +then run selected new tests and require pytest exit code 1 with the expected +assertion failure. Exit 2/3/4/5 is not valid red evidence. Run the unpatched tests +again afterward. Never edit/stage production to manufacture a red cycle. + +For every test, state the realistic regression it detects in the task report. +Expected values must be literals or explicit dataclass construction, never +output recomputed by the production method under test. A support constructor +that only builds a dataclass is acceptable; a duplicate parser/sorter is not. +The examples below are complete starting test bodies; formatting may be +adjusted by existing hooks. Unexpected behavioral differences require diagnosis, +not copying observed output into the expectation. + +All source-test and negative-control commands below run from the worktree root. +Unset inherited `VIRTUAL_ENV` to avoid accidentally using the root checkout's +development environment. Use these commands for focused development: + +```sh +env -u VIRTUAL_ENV uv run --no-project --python 3.10 --with 'pytest>=8,<9' python -m pytest tests/unit -q +env -u VIRTUAL_ENV uv run --no-project --python 3.13 --with 'pytest>=8,<9' python -m pytest tests/unit tests/test_snapshot_helpers.py -q +``` + +Before new files exist, run only `tests/test_snapshot_helpers.py` with both +commands. The last recorded baseline was 97 passing tests on each interpreter; +record fresh results rather than relying on that history. + +Each task ends with focused tests on 3.10 and 3.13, the cumulative fast suite, +`git diff --check`, hooks on changed files, a scoped commit, and review. Keep +commands/results, negative-control evidence and bug reports in ignored +`.superpowers/sdd/2026-09-25-test-harness-phase-two/`. Verify that directory is +ignored before writing reports. No new general-purpose report runner is shipped. + +### Files and responsibilities + +| Task | Files | Responsibility | +| --- | --- | --- | +| 1 | `tests/unit/conftest.py`, `tests/unit/unit_support.py`, `tests/unit/fixtures/stubgen_test_python.py`, `tests/unit/test_parser.py` | Fresh real parser, isolated fixture imports, installed-origin guard, signatures and traversal | +| 2 | `tests/unit/test_annotations.py` | Structured annotation normalization and invalid-input diagnostics | +| 3 | `tests/unit/test_class_ordering.py` | Stable dependency ordering, warnings, nested printer integration | +| 4 | `tests/unit/test_printer.py` | Exact rendering and options from explicit models | +| 5 | `tests/unit/test_writer.py` | Output paths, bytes and write failures with the real printer | +| 6 | `tests/unit/fixtures/stubgen_test_package/__init__.py`, `tests/unit/fixtures/stubgen_test_package/leaf.py`, `tests/unit/test_pipeline.py` | Small independently importable package and end-to-end file assertions | +| 7 | `tox.ini`, `.github/workflows/ci.yml`, `tests/README.md` | Compiler-free installed runs, CI wiring, documentation and full acceptance | + +Do not add `__init__.py` to `tests/`, `tests/unit/`, or `tests/unit/fixtures/`. +Pytest's ordinary prepend import mode then adds test/support directories rather +than the repository root. Only the actual package fixture has `__init__.py`. +Installed-origin verification in the pytest process catches any shadowing. + +Task 1 owns shared helpers and their names. Tasks 2–6 consume them but do not +independently redesign them. Run tasks sequentially with fresh implementer and +reviewer contexts; native acceptance must remain serial. + +## Task 1: Real parser tests and isolated test support + +**Files:** Create `tests/unit/test_parser.py`, `tests/unit/conftest.py`, +`tests/unit/unit_support.py`, and `tests/unit/fixtures/stubgen_test_python.py`. +Read `pybind11_stubgen/__init__.py:stub_parser_from_args`, +`parser/mixins/parse.py:BaseParser, ExtractSignaturesFromPybind11Docstrings, +ParserDispatchMixin`, and `parser/mixins/error_handlers.py:LocalErrors`. + +**Interfaces:** +- `q(name: str) -> QualifiedName` and `rt(name: str, *parameters: Annotation) -> ResolvedType` in `unit_support` construct expected models only. +- `make_parser(*options: str) -> IParser` uses the real CLI factory, never a copied mixin stack. +- Pytest fixture `parser` yields a fresh composed parser inside a `LocalErrors` scope named `unit`; tests that exercise finalization call it explicitly. +- Fixture `load_python_fixture` yields a callable `(name: str) -> ModuleType` using normal imports from the independent fixture directory; all fixture module entries/import paths are restored after each test. +- `STUBGEN_TEST_INSTALLED=1` enables same-pytest-process installed-origin enforcement; unset means the documented source mode. + +- [ ] **Step 1: Write the parser tests first.** Create `test_parser.py` with: + +```python +import logging +import sys + +from pybind11_stubgen.structs import Argument, Decorator, Docstring, Function, Identifier, Value +from unit_support import q, rt + + +def test_python_signature(parser, caplog): + def sample(value: int = 2, *args: str, enabled: bool = True, **kwargs: int) -> str: + """Convert.""" + return str(value) + + assert parser.handle_function(q("unit.sample"), sample) == [ + Function( + Identifier("sample"), + args=[ + Argument(Identifier("value"), annotation=rt("int"), default=Value("2", True)), + Argument(Identifier("args"), annotation=rt("str"), variadic=True), + Argument(Identifier("enabled"), annotation=rt("bool"), default=Value("True", True), kw_only=True), + Argument(Identifier("kwargs"), annotation=rt("int"), kw_variadic=True), + ], + returns=rt("str"), + doc=Docstring("Convert."), + ) + ] + assert not [r for r in caplog.records if r.levelno >= logging.WARNING] + + +def test_docstring_argument_kinds(parser): + args = parser.parse_args_str("x: int, /, count: int = 2, *, enabled: bool = True") + assert args == [ + Argument(Identifier("x"), pos_only=True, annotation=rt("int")), + Argument(Identifier("count"), annotation=rt("int"), default=Value("2", True)), + Argument(Identifier("enabled"), kw_only=True, annotation=rt("bool"), default=Value("True", True)), + ] + assert parser.parse_args_str("*args: int, **kwargs: str") == [ + Argument(Identifier("args"), variadic=True, annotation=rt("int")), + Argument(Identifier("kwargs"), kw_variadic=True, annotation=rt("str")), + ] + + +def test_docstring_overloads_reach_handle_function(parser): + def choose(*args, **kwargs): + pass + + choose.__doc__ = "\n".join([ + "choose(*args, **kwargs)", "Overloaded function.", "", + "1. choose(x: int) -> str", "", "Integer.", "", + "2. choose(x: str) -> int", "", "Text.", + ]) + assert parser.handle_function(q("unit.choose"), choose) == [ + Function(Identifier("choose"), args=[Argument(Identifier("x"), annotation=rt("int"))], returns=rt("str"), doc=Docstring("Integer."), decorators=[Decorator("typing.overload")]), + Function(Identifier("choose"), args=[Argument(Identifier("x"), annotation=rt("str"))], returns=rt("int"), doc=Docstring("Text."), decorators=[Decorator("typing.overload")]), + ] + + +def test_non_signature_preserves_generic_fallback(parser): + def unknown(*args, **kwargs): + """Not a signature.""" + + assert parser.parse_function_docstring(Identifier("unknown"), []) == [] + assert parser.parse_function_docstring(Identifier("unknown"), ["unknown("]) == [] + assert parser.handle_function(q("unit.unknown"), unknown) == [ + Function(Identifier("unknown"), args=[Argument(Identifier("args"), variadic=True), Argument(Identifier("kwargs"), kw_variadic=True)], doc=Docstring("Not a signature.")) + ] + + +def test_generic_docstring_version_boundary(parser): + actual = parser.parse_function_docstring(Identifier("identity"), ["identity[T](value: T) -> T"]) + if sys.version_info < (3, 12): + assert actual == [] # The production parser rejects PEP 695 syntax here. + else: + assert actual == [Function(Identifier("identity"), args=[Argument(Identifier("value"), annotation=rt("T"))], returns=rt("T"), type_vars=["T"])] + + +def test_module_definition_order(parser, load_python_fixture): + module = load_python_fixture("stubgen_test_python") + parsed = parser.handle_module(q(module.__name__), module) + assert parsed is not None + assert [str(c.name) for c in parsed.classes] == ["ZBase", "ADerived"] + assert parsed.classes[1].bases == [q("ZBase")] + assert [str(f.name) for f in parsed.functions] == ["typed"] + + +def test_class_members_use_definition_order_and_resolve_descriptors(parser): + class Parent: + inherited = 1 + + class Child(Parent): + zed = 2 + alpha = 3 + + @staticmethod + def answer(): + return 42 + + members = list(parser._iter_class_members(Child)) + names = [name for name, _ in members] + assert names.index("zed") < names.index("alpha") < names.index("inherited") + assert names.count("inherited") == 1 + assert dict(members)["answer"] is Child.answer +``` + +- [ ] **Step 2: Run `test_parser.py` before support exists.** Use the focused + Python 3.10 command. Expect a missing `unit_support` collection error. Record + this as setup-red only, not as evidence of a production regression. + +- [ ] **Step 3: Add the minimal shared support.** `unit_support.py`: + +```python +from pybind11_stubgen import CLIArgs, arg_parser, stub_parser_from_args +from pybind11_stubgen.parser.interface import IParser +from pybind11_stubgen.structs import Annotation, QualifiedName, ResolvedType + + +def q(name: str) -> QualifiedName: + return QualifiedName.from_str(name) + + +def rt(name: str, *parameters: Annotation) -> ResolvedType: + return ResolvedType(q(name), list(parameters) if parameters else None) + + +def make_parser(*options: str) -> IParser: + args = arg_parser().parse_args([*options, "stubgen_test_python"], namespace=CLIArgs()) + return stub_parser_from_args(args) +``` + +`conftest.py`: + +```python +import importlib +import os +import sys +from pathlib import Path + +import pytest + +from pybind11_stubgen.parser.mixins.error_handlers import LocalErrors +from unit_support import make_parser, q + + +@pytest.fixture(scope="session", autouse=True) +def enforce_installed_origin(): + if os.environ.get("STUBGEN_TEST_INSTALLED") != "1": + return + prefix = Path(sys.prefix).resolve() + for name, module in list(sys.modules.items()): + if name == "pybind11_stubgen" or name.startswith("pybind11_stubgen."): + origin = getattr(module, "__file__", None) + assert origin is not None, (name, origin) + path = Path(origin).resolve() + assert path.is_relative_to(prefix), (name, path, prefix) + + +@pytest.fixture +def parser(): + result = make_parser() + with LocalErrors(q("unit"), set(), result.stack): + yield result + assert result.stack == [] + + +@pytest.fixture +def load_python_fixture(monkeypatch): + def is_fixture(name): + return name.startswith("stubgen_test_") + + saved = {name: module for name, module in sys.modules.items() if is_fixture(name)} + with monkeypatch.context() as scoped: + scoped.syspath_prepend(str(Path(__file__).parent / "fixtures")) + for name in saved: + sys.modules.pop(name) + try: + yield importlib.import_module + finally: + for name in list(sys.modules): + if is_fixture(name): + sys.modules.pop(name) + sys.modules.update(saved) + importlib.invalidate_caches() +``` + +`fixtures/stubgen_test_python.py`: + +```python +__all__ = ["ADerived", "ZBase", "typed"] + + +class ZBase: + pass + + +class ADerived(ZBase): + pass + + +def typed(value: int, *, enabled: bool = True) -> str: + """Convert.""" + return str(value) +``` + +- [ ] **Step 4: Run the focused tests on 3.10 and 3.13.** Require a green run + against unchanged production. Do not add a `from __future__ import annotations` + to the ordinary fixture merely for style: it intentionally covers real runtime + type annotations. The generic docstring stays a string for floor compatibility. + +- [ ] **Step 5: Prove parser assertion sensitivity without writing production.** + +```sh +env -u VIRTUAL_ENV uv run --no-project --python 3.10 --with 'pytest>=8,<9' python - <<'PY' +from unittest.mock import patch +import pytest +from pybind11_stubgen.parser.mixins.parse import BaseParser +with patch.object(BaseParser, 'handle_function', return_value=[]): + code = pytest.main(['tests/unit/test_parser.py::test_python_signature', '-q']) +assert code == pytest.ExitCode.TESTS_FAILED, code +PY +``` + + Confirm an expected function/model comparison failure, then rerun the original + test normally. Also exercise the installed guard's red path: + +```sh +STUBGEN_TEST_INSTALLED=1 env -u VIRTUAL_ENV uv run --no-project --python 3.10 --with 'pytest>=8,<9' python -m pytest tests/unit/test_parser.py::test_python_signature -q +``` + + Expect exit 1 with the source origin rejected by `enforce_installed_origin`. + Rerun without `STUBGEN_TEST_INSTALLED` to restore the green source case. A + genuine installed green path is verified in Task 7. + +- [ ] **Step 6: Review isolation, run cumulative checks, and commit.** Check no + native fixture imports or new dependencies. Commit only these four files as + `test: cover parser contracts with independent Python fixtures`. + +**Known boundary to investigate separately:** `BaseParser.handle_function` uses +`inspect.getfullargspec`, which does not distinguish positional-only names in +its `args` list. Do not assert that a real Python `/` signature is reconstructed +correctly without checking it. The planned positional-only contract is the +existing docstring route. Record any confirmed loss on the ordinary Python +route as a separate bug rather than codifying loss as a desired assertion. + +## Task 2: Structured annotations and expected diagnostics + +**Files:** Create `tests/unit/test_annotations.py`. +Read `parse.py:parse_annotation_str, parse_type_str, _parse_expression_str` and +`fix.py:FixPEP585CollectionNames, FixMissingImports`. + +**Interfaces:** Consume Task 1's `parser`, `q`, and `rt`; produce no shared helper. +Direct parsing uses the fixture's real logger context so invalid-input tests +can assert diagnostics without suppressing them or hitting an empty logger stack. + +- [ ] **Step 1: Add exact annotation and malformed-input cases.** + +```python +import logging + +import pytest + +from pybind11_stubgen.structs import InvalidExpression, Value +from unit_support import rt + + +@pytest.mark.parametrize(("text", "expected"), [ + ("int", rt("int")), + ("typing.Dict[str, typing.List[int | None]]", rt("dict", rt("str"), rt("list", rt("typing.Union", rt("int"), rt("None"))))), + ("int | str", rt("typing.Union", rt("int"), rt("str"))), + ("typing.Optional[int]", rt("typing.Optional", rt("int"))), + ("typing.Literal['x,y', 3]", rt("typing.Literal", Value("'x,y'", True), Value("3", True))), + ("typing.Annotated[int, 'units']", rt("typing.Annotated", rt("int"), Value("'units'", True))), +]) +def test_annotation_structure(parser, caplog, text, expected): + assert parser.parse_annotation_str(text) == expected + assert not [r for r in caplog.records if r.levelno >= logging.WARNING] + + +@pytest.mark.parametrize("text", ["list[", "std::vector"]) +def test_invalid_annotation_reports_expression(parser, caplog, text): + with caplog.at_level(logging.ERROR, logger="pybind11_stubgen"): + assert parser.parse_annotation_str(text) == InvalidExpression(text) + assert [r.getMessage() for r in caplog.records if r.levelno >= logging.ERROR] == [ + f"In unit : Invalid expression '{text}'" + ] + + +def test_invalid_expression_is_not_a_valid_value(parser, caplog): + assert parser.parse_value_str("'a,b'") == Value("'a,b'", True) + assert parser.parse_value_str("len('abc')") == Value("len('abc')", False) + assert parser.parse_value_str("[") == InvalidExpression("[") + assert any("Invalid expression '['" in r.getMessage() for r in caplog.records) +``` + +- [ ] **Step 2: Run on Python 3.10 and 3.13.** If a case reports an unexpected + name-resolution error, inspect the actual parser/import context first; do not + add ignore-all-errors or optional packages. + +- [ ] **Step 3: Prove invalid-input assertions detect a lost error result.** + +```sh +env -u VIRTUAL_ENV uv run --no-project --python 3.10 --with 'pytest>=8,<9' python - <<'PY' +from unittest.mock import patch +import pytest +from pybind11_stubgen.parser.mixins.parse import BaseParser +from pybind11_stubgen.structs import Value +with patch.object(BaseParser, '_parse_expression_str', return_value=Value('wrong', True)): + code = pytest.main(['tests/unit/test_annotations.py::test_invalid_annotation_reports_expression', '-q']) +assert code == pytest.ExitCode.TESTS_FAILED, code +PY +``` + +- [ ] **Step 4: Rerun unpatched, cumulative tests and hooks; commit** as + `test: cover annotation structure and invalid expressions`. + +## Task 3: Stable dependency ordering and printer integration + +**Files:** Create `tests/unit/test_class_ordering.py`. +Read `printer.py:_topological_sort_classes, _referenced_local_dependency_name, +Printer.print_class_body, Printer.print_module`. + +**Interfaces:** Consume `q`; use `_topological_sort_classes` for focused graph +contracts and `Printer` for consumer-visible ordering. Produce no shared helper. + +- [ ] **Step 1: Add graph contracts with literal expected orders.** + +```python +import logging + +import pytest + +from pybind11_stubgen.printer import Printer, _topological_sort_classes +from pybind11_stubgen.structs import Alias, Attribute, Class, Field, Identifier, Module, Value +from unit_support import q + + +def names(classes): + return [str(c.name) for c in classes] + + +@pytest.mark.parametrize("count", [0, 1]) +def test_empty_and_singleton(count): + classes = [Class(Identifier("Only"))] if count else [] + assert names(_topological_sort_classes(classes)) == (["Only"] if count else []) + + +def test_inheritance_and_stable_ready_priority(): + classes = [Class(Identifier("Child"), bases=[q("Base")]), Class(Identifier("Peer")), Class(Identifier("Base")), Class(Identifier("Tail"))] + ordered = _topological_sort_classes(classes) + assert names(ordered) == ["Peer", "Base", "Child", "Tail"] + assert names(classes) == ["Child", "Peer", "Base", "Tail"] + assert ordered[2] is classes[0] + + +@pytest.mark.parametrize("kind", ["base", "alias", "field"]) +def test_dotted_sibling_reference(kind): + consumer = Class(Identifier("Consumer")) + if kind == "base": + consumer.bases = [q("Outer.Inner")] + elif kind == "alias": + consumer.aliases = [Alias(Identifier("Item"), q("Outer.Inner"))] + else: + consumer.fields = [Field(Attribute(Identifier("Item"), Value("Outer.Inner", True)), "static")] + classes = [consumer, Class(Identifier("Outer"))] + assert names(_topological_sort_classes(classes)) == ["Outer", "Consumer"] + + +@pytest.mark.parametrize(("expression", "safe"), [("External.Item", True), ("Target", False), ("Target()", True), ("'Target'", True), ("Self.Item", True)]) +def test_irrelevant_field_references_keep_order(expression, safe): + consumer = Class(Identifier("Self"), fields=[Field(Attribute(Identifier("Item"), Value(expression, safe)), "static")]) + assert names(_topological_sort_classes([consumer, Class(Identifier("Target"))])) == ["Self", "Target"] + + +def test_external_base_and_alias_do_not_add_local_edges(): + consumer = Class(Identifier("Consumer"), bases=[q("external.Base")], aliases=[Alias(Identifier("Item"), q("external.Item")), Alias(Identifier("Self"), q("Consumer"))]) + assert names(_topological_sort_classes([consumer, Class(Identifier("Peer"))])) == ["Consumer", "Peer"] + + +def test_repeated_edges_do_not_leave_a_false_cycle(caplog): + consumer = Class(Identifier("Child"), bases=[q("Base")], aliases=[Alias(Identifier("Item"), q("Base"))], fields=[Field(Attribute(Identifier("Other"), Value("Base", True)), "static")]) + assert names(_topological_sort_classes([consumer, Class(Identifier("Base"))])) == ["Base", "Child"] + assert not [r for r in caplog.records if r.levelno >= logging.WARNING] + + +def test_cycle_warns_and_appends_remaining_input_order(caplog): + classes = [Class(Identifier("B"), bases=[q("A")]), Class(Identifier("Free")), Class(Identifier("A"), bases=[q("B")])] + with caplog.at_level(logging.WARNING, logger="pybind11_stubgen"): + assert names(_topological_sort_classes(classes)) == ["Free", "B", "A"] + messages = [r.getMessage() for r in caplog.records if r.levelno == logging.WARNING] + assert len(messages) == 1 + assert "Cycle detected" in messages[0] and "['B', 'A']" in messages[0] + + +def test_module_and_nested_classes_use_dependency_order(): + classes = [Class(Identifier("Child"), bases=[q("Base")]), Class(Identifier("Base"))] + printer = Printer(invalid_expr_as_ellipses=True) + assert printer.print_module(Module(Identifier("sample"), classes=classes)) == ["class Base:", " pass", "class Child(Base):", " pass"] + assert printer.print_class(Class(Identifier("Outer"), classes=classes)) == ["class Outer:", " class Base:", " pass", " class Child(Base):", " pass"] +``` + +- [ ] **Step 2: Run focused tests on 3.10 and 3.13.** The expected stable order + is hand-derived: after Base is emitted, the newly ready Child has earlier + original input priority than Tail. Do not sort expected names alphabetically. + +- [ ] **Step 3: Check the consumer catches bypassed sorting.** + +```sh +env -u VIRTUAL_ENV uv run --no-project --python 3.10 --with 'pytest>=8,<9' python - <<'PY' +from unittest.mock import patch +import pytest +from pybind11_stubgen.printer import Printer +with patch.object(Printer, '_order_classes', lambda self, classes: classes): + code = pytest.main(['tests/unit/test_class_ordering.py::test_module_and_nested_classes_use_dependency_order', '-q']) +assert code == pytest.ExitCode.TESTS_FAILED, code +PY +``` + +- [ ] **Step 4: Rerun unpatched, cumulative tests and hooks; commit** as + `test: cover stable class dependencies and cycle fallback`. + +## Task 4: Exact printer rendering from explicit models + +**Files:** Create `tests/unit/test_printer.py`. +Read all rendering methods in `pybind11_stubgen/printer.py` and the relevant +model fields in `pybind11_stubgen/structs.py`. + +**Interfaces:** Consume `q` and `rt`; construct `Printer` explicitly per test. +No production parser is used to construct the expected printer inputs. + +- [ ] **Step 1: Add the following renderer tests.** + +```python +import ast + +import pytest + +from pybind11_stubgen.printer import Printer +from pybind11_stubgen.structs import Argument, Attribute, Class, Decorator, Docstring, Function, Identifier, Import, InvalidExpression, Method, Module, Property, Value +from unit_support import q, rt + + +@pytest.fixture +def printer(): + return Printer(invalid_expr_as_ellipses=True) + + +def test_argument_separators_annotations_defaults_and_decorator(printer): + function = Function(Identifier("choose"), args=[ + Argument(Identifier("x"), annotation=rt("int"), pos_only=True), + Argument(Identifier("count"), annotation=rt("int"), default=Value("2", True)), + Argument(Identifier("flag"), annotation=rt("bool"), default=Value("True", True), kw_only=True), + ], returns=rt("str"), decorators=[Decorator("typing.overload")]) + lines = printer.print_function(function) + assert lines == ["@typing.overload", "def choose(x: int, /, count: int = 2, *, flag: bool = True) -> str:", " ..."] + ast.parse("\n".join(lines)) + + +def test_variadic_arguments_do_not_gain_an_extra_star(printer): + function = Function(Identifier("collect"), args=[Argument(Identifier("args"), variadic=True), Argument(Identifier("flag"), kw_only=True), Argument(Identifier("kwargs"), kw_variadic=True)]) + assert printer.print_function(function) == ["def collect(*args, flag, **kwargs):", " ..."] + + +@pytest.mark.parametrize(("modifier", "expected"), [(None, []), ("static", ["@staticmethod"]), ("class", ["@classmethod"])]) +def test_method_modifier(printer, modifier, expected): + assert printer.print_method(Method(Function(Identifier("f")), modifier)) == [*expected, "def f():", " ..."] + + +@pytest.mark.parametrize(("annotation", "expected"), [ + (rt("typing.Optional", rt("int")), "int | None"), + (rt("typing.Union", rt("int"), rt("str")), "int | str"), + (rt("dict", rt("str"), rt("list", rt("int"))), "dict[str, list[int]]"), +]) +def test_type_rendering(printer, annotation, expected): + assert printer.print_annotation(annotation) == expected + + +@pytest.mark.parametrize(("ellipses", "expected"), [(True, "..."), (False, "C++")]) +def test_invalid_expression_policy(ellipses, expected): + printer = Printer(invalid_expr_as_ellipses=ellipses) + assert printer.print_annotation(InvalidExpression("C++")) == expected + assert printer.print_argument(Argument(Identifier("x"), default=InvalidExpression("C++"))) == f"x = {expected}" + + +@pytest.mark.parametrize(("comments", "expected"), [(False, "value: int"), (True, "value: int # value = ")]) +def test_unsafe_value_comments(comments, expected): + printer = Printer(invalid_expr_as_ellipses=True, print_value_comments=comments) + assert printer.print_attribute(Attribute(Identifier("value"), Value("", False), rt("int"))) == [expected] + assert printer.print_argument(Argument(Identifier("x"), default=Value("", False))) == "x = ..." + + +def test_property_getter_setter_and_input_preservation(printer): + getter = Function(Identifier("get_value"), args=[Argument(Identifier("self"))], returns=rt("int")) + setter = Function(Identifier("set_value"), args=[Argument(Identifier("self")), Argument(Identifier("value"), annotation=rt("int"))], returns=rt("None")) + prop = Property(Identifier("value"), None, getter=getter, setter=setter) + assert printer.print_property(prop) == ["@property", "def value(self) -> int:", " ...", "@value.setter", "def value(self, value: int) -> None:", " ..."] + assert getter.name == "get_value" and setter.name == "set_value" + + +def test_docstring_escaping_round_trips(printer): + text = 'path \\data and """quote"""' + lines = printer.print_docstring(Docstring(text)) + assert lines == ['"""', 'path \\\\data and \\"\\"\\"quote\\"\\"\\"', '"""'] + assert ast.get_docstring(ast.parse("\n".join(lines)), clean=False) == "\n" + text + "\n" + + +def test_module_imports_all_classes_functions_and_attributes(printer): + module = Module(Identifier("sample"), imports={Import(None, q("typing")), Import(Identifier("annotations"), q("__future__.annotations")), Import(Identifier("P"), q("pathlib.Path"))}, classes=[Class(Identifier("Thing"))], functions=[Function(Identifier("z")), Function(Identifier("a"))], attributes=[Attribute(Identifier("value"), Value("3", True), rt("int")), Attribute(Identifier("__all__"), Value("['Thing', 'a', 'z']", True))]) + lines = printer.print_module(module) + assert lines == ["from __future__ import annotations", "from pathlib import Path as P", "import typing", "__all__ = ['Thing', 'a', 'z']", "class Thing:", " pass", "def a():", " ...", "def z():", " ...", "value: int = 3"] + ast.parse("\n".join(lines)) +``` + +- [ ] **Step 2: Run on 3.10 and 3.13.** Check the docstring string literal + carefully: the expectation represents doubled backslashes and escaped triple + quotes, while `ast.get_docstring(..., clean=False)` independently validates + the underlying string. A syntax check never replaces exact output assertions. + +- [ ] **Step 3: Show the invalid-expression option test detects ignored policy.** + +```sh +env -u VIRTUAL_ENV uv run --no-project --python 3.10 --with 'pytest>=8,<9' python - <<'PY' +from unittest.mock import patch +import pytest +from pybind11_stubgen.printer import Printer +with patch.object(Printer, 'print_invalid_exp', return_value='...'): + code = pytest.main(['tests/unit/test_printer.py::test_invalid_expression_policy', '-q']) +assert code == pytest.ExitCode.TESTS_FAILED, code +PY +``` + +- [ ] **Step 4: Rerun unpatched, cumulative tests and hooks; commit** as + `test: cover exact printer output and rendering options`. + +## Task 5: Writer paths, bytes, and failures + +**Files:** Create `tests/unit/test_writer.py`. +Read `pybind11_stubgen/writer.py` in full. + +**Interfaces:** Use the real `Writer`, real `Printer`, pytest `tmp_path`, and +explicit `Module` models. No global snapshot helper or external formatter. + +- [ ] **Step 1: Add portable filesystem contract tests.** + +```python +from pathlib import Path + +import pytest + +from pybind11_stubgen.printer import Printer +from pybind11_stubgen.structs import Docstring, Identifier, Module +from pybind11_stubgen.writer import Writer + + +def files(root: Path) -> dict[str, bytes]: + return {p.relative_to(root).as_posix(): p.read_bytes() for p in root.rglob("*") if p.is_file()} + + +@pytest.mark.parametrize("extension", ["pyi", "py"]) +def test_module_utf8_and_final_newline(tmp_path, extension): + module = Module(Identifier("sample"), doc=Docstring("café")) + Writer(extension).write_module(module, Printer(True), to=tmp_path) + assert files(tmp_path) == {f"sample.{extension}": '"""\ncafé\n"""\n'.encode("utf-8")} + + +@pytest.mark.parametrize("extension", ["pyi", "py"]) +def test_empty_package_gets_init_file(tmp_path, extension): + Writer(extension).write_module(Module(Identifier("empty"), is_package=True), Printer(True), to=tmp_path) + assert files(tmp_path) == {f"empty/__init__.{extension}": b""} + + +@pytest.mark.parametrize("extension", ["pyi", "py"]) +def test_nested_submodule_layout(tmp_path, extension): + leaf = Module(Identifier("leaf"), doc=Docstring("Leaf")) + nested = Module(Identifier("nested"), sub_modules=[leaf]) + root = Module(Identifier("root"), sub_modules=[nested]) + Writer(extension).write_module(root, Printer(True), to=tmp_path) + assert files(tmp_path) == { + f"root/__init__.{extension}": b"from . import nested\n", + f"root/nested/__init__.{extension}": b"from . import leaf\n", + f"root/nested/leaf.{extension}": b'"""\nLeaf\n"""\n', + } + + +def test_explicit_subdirectory_uses_init(tmp_path): + Writer().write_module(Module(Identifier("sample"), doc=Docstring("Renamed")), Printer(True), to=tmp_path, sub_dir=Path("renamed")) + assert files(tmp_path) == {"renamed/__init__.pyi": b'"""\nRenamed\n"""\n'} + + +@pytest.mark.parametrize("kind", ["missing", "file"]) +def test_invalid_output_root_is_rejected(tmp_path, kind): + root = tmp_path / "output" + if kind == "file": + root.write_bytes(b"sentinel") + with pytest.raises(AssertionError): + Writer().write_module(Module(Identifier("sample")), Printer(True), to=root) + assert files(tmp_path) == ({"output": b"sentinel"} if kind == "file" else {}) + + +def test_write_error_propagates(tmp_path): + # A directory at the output filename fails even when tests run as root. + target = tmp_path / "sample.pyi" + target.mkdir() + with pytest.raises(OSError): + Writer().write_module(Module(Identifier("sample")), Printer(True), to=tmp_path) + assert target.is_dir() + assert files(tmp_path) == {} +``` + +- [ ] **Step 2: Run on 3.10 and 3.13.** Empty modules intentionally produce empty + files, not an invented newline. Nonempty rendered output ends in a newline. + No chmod-based permission test or writer mock is needed. + +- [ ] **Step 3: Demonstrate missing writes are caught.** + +```sh +env -u VIRTUAL_ENV uv run --no-project --python 3.10 --with 'pytest>=8,<9' python - <<'PY' +from unittest.mock import patch +import pytest +from pybind11_stubgen.writer import Writer +with patch.object(Writer, 'write_module', return_value=None): + code = pytest.main(['tests/unit/test_writer.py::test_module_utf8_and_final_newline', '-q']) +assert code == pytest.ExitCode.TESTS_FAILED, code +PY +``` + +- [ ] **Step 4: Rerun unpatched, cumulative tests and hooks; commit** as + `test: cover writer layouts contents and error propagation`. + +## Task 6: Compact pure-Python pipeline checks + +**Files:** Create `tests/unit/test_pipeline.py`, +`tests/unit/fixtures/stubgen_test_package/__init__.py`, and +`tests/unit/fixtures/stubgen_test_package/leaf.py`. +Read `pybind11_stubgen/__init__.py:run` and `fix.py:FixMissing__all__Attribute`. + +**Interfaces:** Consume `load_python_fixture`, `make_parser`, and the existing +`stubgen_test_python` fixture from Task 1. Use `run` so production finalization +and module-to-output orchestration are covered rather than reimplemented. + +- [ ] **Step 1: Write pipeline tests with exact hand-checked output trees.** + +```python +import ast +import logging + +from pybind11_stubgen import run +from pybind11_stubgen.printer import Printer +from pybind11_stubgen.writer import Writer +from unit_support import make_parser + + +def test_python_module_pipeline(tmp_path, load_python_fixture, caplog): + module = load_python_fixture("stubgen_test_python") + run(make_parser(), Printer(True), [module.__name__], str(tmp_path), root_suffix=None, dry_run=False, writer=Writer()) + expected = ( + "from __future__ import annotations\n" + "__all__: list = ['ADerived', 'ZBase', 'typed']\n" + "class ZBase:\n" + " pass\n" + "class ADerived(ZBase):\n" + " pass\n" + "def typed(value: int, *, enabled: bool = True) -> str:\n" + ' """\n' + " Convert.\n" + ' """\n' + ) + assert {p.relative_to(tmp_path).as_posix(): p.read_bytes() for p in tmp_path.rglob("*") if p.is_file()} == {"stubgen_test_python.pyi": expected.encode("utf-8")} + ast.parse(expected) + assert not [r for r in caplog.records if r.levelno >= logging.WARNING] + + +def test_package_pipeline(tmp_path, load_python_fixture, caplog): + module = load_python_fixture("stubgen_test_package") + run(make_parser(), Printer(True), [module.__name__], str(tmp_path), root_suffix=None, dry_run=False, writer=Writer()) + expected = { + "stubgen_test_package/__init__.pyi": b"from __future__ import annotations\nfrom . import leaf\n__all__: list = ['leaf']\n", + "stubgen_test_package/leaf.pyi": b"from __future__ import annotations\n__all__: list = ['double']\ndef double(value: int) -> int:\n ...\n", + } + actual = {p.relative_to(tmp_path).as_posix(): p.read_bytes() for p in tmp_path.rglob("*") if p.is_file()} + assert actual == expected + for text in actual.values(): + ast.parse(text.decode("utf-8")) + assert not [r for r in caplog.records if r.levelno >= logging.WARNING] +``` + +- [ ] **Step 2: Run the package test before its fixture exists.** Expect + `ModuleNotFoundError` for `stubgen_test_package`, not a native import failure. + +- [ ] **Step 3: Add the package fixture.** `__init__.py`: + +```python +from . import leaf + +__all__ = ["leaf"] +``` + +`leaf.py`: + +```python +__all__ = ["double"] + + +def double(value: int) -> int: + return value * 2 +``` + +- [ ] **Step 4: Run both tests and the cumulative suite on 3.10 and 3.13.** + Confirm these tests need neither demo configuration nor optional packages. + Run the pipeline file before the parser file, then in the reverse order, as + separate pytest invocations; both orders must pass without import-state leaks. + +- [ ] **Step 5: Show orchestration omission is detected, then restore by process exit.** + +```sh +env -u VIRTUAL_ENV uv run --no-project --python 3.10 --with 'pytest>=8,<9' python - <<'PY' +from unittest.mock import patch +import pytest +from pybind11_stubgen.writer import Writer +with patch.object(Writer, 'write_module', return_value=None): + code = pytest.main(['tests/unit/test_pipeline.py', '-q']) +assert code == pytest.ExitCode.TESTS_FAILED, code +PY +``` + +- [ ] **Step 6: Rerun unpatched, cumulative checks and hooks; commit** as + `test: exercise pure Python generation pipeline without native bindings`. + +## Task 7: Compiler-free runners, CI configuration, and acceptance + +**Files:** Append a unit section to `tox.ini`; add one job and one dependency +entry to `.github/workflows/ci.yml`; update `tests/README.md`. +Read those three files in full before editing. Do not alter existing native +commands, dependencies, matrix rows, build workflow, or gemmi steps. + +**Interfaces:** Consume `STUBGEN_TEST_INSTALLED=1` and the no-package test import +layout from Task 1. Every new runner executes exactly `tests/unit` and +`tests/test_snapshot_helpers.py`; installed runs use `python -I -m pytest`. + +- [ ] **Step 1: Capture the pre-configuration failures.** A clean wheel/pytest + environment can already run the tests manually, but requesting `py310-unit` + before the explicit section exists either fails or inherits native setup. + Use `tox list`/`tox config -e py310-unit` to inspect it; do not deliberately + launch a native build with missing factors. Record the missing dedicated + compiler-free configuration as setup-red, then verify actual runs after editing. + +- [ ] **Step 2: Add this dedicated tox section, leaving the default list intact.** + +```ini +[testenv:py{310,311,312,313}-unit] +description = Check compiler-free production contracts and snapshot helpers +package = skip +setenv = + PYTHONUNBUFFERED = 1 + STUBGEN_TEST_INSTALLED = 1 +allowlist_externals = + uv +deps = + pytest>=8,<9 +commands_pre = +commands = + uv pip install --python "{envpython}" . + {envpython} -I -m pytest {toxinidir}/tests/unit {toxinidir}/tests/test_snapshot_helpers.py {posargs} +``` + + The explicit empty `commands_pre` overrides the inherited demo installer. + `deps` and `setenv` likewise replace the native defaults. Render `tox config` + for all four unit environments and one native environment to inspect resolved + values, but require actual execution, not config text tests, for acceptance. + +- [ ] **Step 3: Add this CI job alongside, not inside, the native job.** + +```yaml + unit-tests: + name: "Compiler-free tests • Python ${{ matrix.python }}" + runs-on: ubuntu-latest + needs: [build] + strategy: + fail-fast: false + matrix: + python: ["3.10", "3.11", "3.12", "3.13"] + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + - uses: astral-sh/setup-uv@v7 + - uses: actions/download-artifact@v6 + with: + name: dist + path: dist + - name: Install wheel and pytest only + shell: bash + run: | + uv venv .venv + uv pip install --python .venv/bin/python 'pytest>=8,<9' dist/*.whl + - name: Test installed wheel without native fixtures + env: + STUBGEN_TEST_INSTALLED: "1" + run: .venv/bin/python -I -m pytest tests/unit tests/test_snapshot_helpers.py +``` + + Change only the publication job's `needs` list to + `[check, build, unit-tests, tests, test-cli-options]`. Leave its condition, + permissions, artifacts, and publishing steps untouched. No remote action is + authorized. Do not add pytest annotation packages or `uv sync` to the new job. + +- [ ] **Step 4: Document the new test layers and exact commands.** Add a + `Compiler-free production tests` section before `Native checks` in + `tests/README.md` with: + +````markdown +## Compiler-free production tests + +`tests/unit/` covers parser/signature and annotation behavior, class ordering, +printer output, writer paths/contents, and small pure-Python generation flows. +Its fixtures are independent of `demo`; no native installation or NumPy/SciPy +is needed. The following command tests source edits directly without syncing +the development dependency group: + +```sh +uv run --no-project --with 'pytest>=8,<9' python -m pytest \ + tests/unit tests/test_snapshot_helpers.py +``` + +To test a non-editable installation on each supported matrix interpreter: + +```sh +uv run --no-project --with tox --with tox-uv tox \ + -e py310-unit,py311-unit,py312-unit,py313-unit +``` + +The unit environments install with uv, run isolated Python, and verify the +actual pytest process imports the installed generator. The default tox list +still runs the existing 13 native configurations; unit environments are explicit. +CI adds a separate compiler-free wheel matrix while retaining native/gemmi jobs. +```` + + Replace the existing `Add a regression` section with this routing advice; + leave the preceding deliberate-update instructions unchanged: + +```markdown +## Add a regression + +For harness behavior, add a small synthetic test to `test_snapshot_helpers.py`. +For compiler-free generator behavior, prefer a focused test in `tests/unit/` +with explicit expected models or text. Neither route requires a compiler. + +When the regression requires real binding behavior, add or adjust the native +demo fixture, run a relevant native environment, inspect its failure, then +explicitly update that environment's expectations. Review only the intended +changes and run affected shared profiles and the compatibility matrix before +merging. + +During test-only phase two, report newly discovered production bugs separately. +Do not hide them with changed expectations, unexplained skips, or snapshot +updates; production fixes require a separate scope. +``` + +- [ ] **Step 5: Run all four unit tox environments serially.** + +```sh +env -u VIRTUAL_ENV uv run --no-project --with tox --with tox-uv tox \ + -e py310-unit,py311-unit,py312-unit,py313-unit +``` + + Require pytest success with the installed-origin fixture enabled. No native + build/install step or formatter invocation may appear in their logs. Inspect + these environments with isolated Python to assert `find_spec('demo')`, + `find_spec('numpy')`, and `find_spec('scipy')` are all `None`. Record actual + package origins and Python versions. These are environment acceptance probes, + not tests that demand optional packages be absent in every developer environment. + +- [ ] **Step 6: Prove source-mode purity in fresh pytest-only environments.** + Use ignored verification paths, not the native `.tox` environments. For each + Python 3.10–3.13, create a fresh uv venv, install only pytest, assert optional + module absence and invoke Python from the worktree with ordinary `-m pytest`. + This deliberately tests source, so do not set `STUBGEN_TEST_INSTALLED` here. + +```sh +mkdir -p .superpowers/sdd/2026-09-25-test-harness-phase-two/verification +for version in 3.10 3.11 3.12 3.13; do + envdir=".superpowers/sdd/2026-09-25-test-harness-phase-two/verification/source-$version" + test ! -e "$envdir" || exit 1 + uv venv --python "$version" "$envdir" || exit 1 + uv pip install --python "$envdir/bin/python" 'pytest>=8,<9' || exit 1 + "$envdir/bin/python" -I -c 'from importlib.util import find_spec; assert all(find_spec(n) is None for n in ("demo", "numpy", "scipy", "pybind11_stubgen"))' || exit 1 + env -u STUBGEN_TEST_INSTALLED "$envdir/bin/python" -m pytest tests/unit tests/test_snapshot_helpers.py -q || exit 1 +done +``` + + These commands intentionally refuse to overwrite a previous environment; + choose a new ignored run directory when repeating them. Do not turn this + acceptance probe into a new packaged dependency manager. + +- [ ] **Step 7: Reproduce CI-style wheel runs locally on all four versions.** + Build the wheel once into an ignored directory; do not sync project dependencies. + +```sh +test ! -e .superpowers/sdd/2026-09-25-test-harness-phase-two/verification/dist || exit 1 +env -u VIRTUAL_ENV uv build --wheel --out-dir .superpowers/sdd/2026-09-25-test-harness-phase-two/verification/dist || exit 1 +for version in 3.10 3.11 3.12 3.13; do + envdir=".superpowers/sdd/2026-09-25-test-harness-phase-two/verification/wheel-$version" + test ! -e "$envdir" || exit 1 + uv venv --python "$version" "$envdir" || exit 1 + uv pip install --python "$envdir/bin/python" 'pytest>=8,<9' .superpowers/sdd/2026-09-25-test-harness-phase-two/verification/dist/*.whl || exit 1 + "$envdir/bin/python" -I -c 'from importlib.util import find_spec; import sys, pybind11_stubgen; from pathlib import Path; p=Path(pybind11_stubgen.__file__).resolve(); assert p.is_relative_to(Path(sys.prefix).resolve()), p; assert all(find_spec(n) is None for n in ("demo", "numpy", "scipy")); print(sys.version, p)' || exit 1 + STUBGEN_TEST_INSTALLED=1 "$envdir/bin/python" -I -m pytest tests/unit tests/test_snapshot_helpers.py -q || exit 1 +done +``` + + Confirm an independent same-process guard rejects checkout shadowing: + +```sh +STUBGEN_TEST_INSTALLED=1 .superpowers/sdd/2026-09-25-test-harness-phase-two/verification/wheel-3.10/bin/python -m pytest tests/unit/test_parser.py::test_python_signature -q +``` + + Expect exit 1 with an origin assertion failure because `-I` is intentionally + absent. Rerun that environment's isolated command to prove green. Do not claim + the separate `-c` preflight alone establishes installed test coverage. + +- [ ] **Step 8: Recheck all 13 native configurations with an immutability audit.** + Use the retained phase-one `task-6-logs/verify_run.py` if present, or create the + following equivalent ignored audit script as + `.superpowers/sdd/2026-09-25-test-harness-phase-two/verify_native.py`: + +```python +import hashlib +import json +import os +from pathlib import Path +import subprocess +import sys + +ROOT = Path.cwd() +REPORT = ROOT / '.superpowers/sdd/2026-09-25-test-harness-phase-two' + + +def references(): + result = {} + def visit(path): + key = path.relative_to(ROOT).as_posix() + if path.is_symlink(): + result[key] = ['link', os.readlink(path)] + elif path.is_dir(): + result[key] = ['directory'] + for child in sorted(path.iterdir()): + visit(child) + else: + result[key] = ['file', hashlib.sha256(path.read_bytes()).hexdigest()] + for name in ('tests/stubs', 'tests/errors'): + visit(ROOT / name) + return result + + +before = references() +index = subprocess.check_output(['git', 'ls-files', '--stage', '-z']) +command = ['env', '-u', 'VIRTUAL_ENV', 'uv', 'run', '--no-project', '--with', 'tox', '--with', 'tox-uv', 'tox'] +with (REPORT / 'native.log').open('wb') as log: + process = subprocess.run(command, stdout=log, stderr=subprocess.STDOUT, timeout=1200) +audit = { + 'command': command, + 'returncode': process.returncode, + 'references_unchanged': references() == before, + 'index_unchanged': subprocess.check_output(['git', 'ls-files', '--stage', '-z']) == index, +} +(REPORT / 'native-audit.json').write_text(json.dumps(audit, indent=2) + '\n') +print(json.dumps(audit, indent=2)) +assert audit['references_unchanged'] and audit['index_unchanged'] +sys.exit(process.returncode) +``` + + Run that script from the worktree root. Inspect `native.log` to verify exactly + the original 13 environments passed both native comparisons and all harness + self-tests without deselection or snapshot updates. Stop on timeout/failure; + retain logs and diagnose, never rerun with checks excluded to claim acceptance. + A timeout is not a successful immutability audit. + +- [ ] **Step 9: Verify preservation, lock, formatting, and scope.** + +```sh +git diff --exit-code a306491 -- pybind11_stubgen tests/demo-lib tests/py-demo tests/install-demo-module.sh tests/stubs tests/errors tests/conftest.py tests/snapshot_helpers.py tests/test_snapshot_helpers.py tests/test_demo_stubs.py tests/test_demo_errors.py pyproject.toml uv.lock +env -u VIRTUAL_ENV uv lock --check +env -u VIRTUAL_ENV uv run pre-commit run --all-files +git diff --check +git diff a306491 -- tox.ini .github/workflows/ci.yml +git status --short +``` + + Review the last configuration diff for unchanged default/native matrix, + dependencies, gemmi, build, publication trigger and permissions. YAML parsing + hooks establish syntax only; actual remote behavior remains unverified. If + hooks reformat tests, repeat the compiler-free matrix before committing. + +- [ ] **Step 10: Commit only configuration/documentation changes and request review.** + Commit as `test: run compiler-free contracts across supported Python versions`. + Report source/unit-tox/wheel/native results separately, installed origins, + reference/index audits, known bug reproductions, hooks, commit range and + unexecuted remote CI. Do not describe phase one or two as remotely accepted. + +## Plan self-review and handoff + +Spec mapping: parser + isolation/support → Task 1; annotations/invalid input → +Task 2; graph ordering and nested consumer → Task 3; renderer/options → Task 4; +writer/error propagation → Task 5; independent packages + pipeline → Task 6; +Python matrix, wheel origins, native preservation, CI, documentation and final +acceptance → Task 7. Every task is independently reviewable after its prerequisites. + +The plan does not authorize broader fixture extraction, packaging cleanup, +production bug fixes, snapshot regeneration, build simplification, or remote +execution. Those require a separately approved scope. diff --git a/docs/superpowers/specs/2026-09-25-test-harness-phase-two-design.md b/docs/superpowers/specs/2026-09-25-test-harness-phase-two-design.md index 7b1279ac..78ee53f2 100644 --- a/docs/superpowers/specs/2026-09-25-test-harness-phase-two-design.md +++ b/docs/superpowers/specs/2026-09-25-test-harness-phase-two-design.md @@ -3,8 +3,8 @@ ## Status and starting point The scope, test structure, and execution/acceptance design below were approved -in conversation. This written specification is awaiting user review before an -implementation plan is written. No phase-two implementation is included here. +in conversation. The user also approved this written specification before +implementation planning. No phase-two implementation is included here. Build on `test-harness-phase-one` at `a306491`, including the uv tox installation fix and the separately reviewed stderr-order corrections. Use the existing From c7ac28c928f7d692cd55d170ce342c5db598c1ee Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 04:05:00 +0000 Subject: [PATCH 19/40] test: cover parser contracts with independent Python fixtures --- tests/unit/conftest.py | 50 +++++++ tests/unit/fixtures/stubgen_test_python.py | 14 ++ tests/unit/test_parser.py | 157 +++++++++++++++++++++ tests/unit/unit_support.py | 18 +++ 4 files changed, 239 insertions(+) create mode 100644 tests/unit/conftest.py create mode 100644 tests/unit/fixtures/stubgen_test_python.py create mode 100644 tests/unit/test_parser.py create mode 100644 tests/unit/unit_support.py diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py new file mode 100644 index 00000000..538ca3f1 --- /dev/null +++ b/tests/unit/conftest.py @@ -0,0 +1,50 @@ +import importlib +import os +import sys +from pathlib import Path + +import pytest + +from pybind11_stubgen.parser.mixins.error_handlers import LocalErrors +from unit_support import make_parser, q + + +@pytest.fixture(scope="session", autouse=True) +def enforce_installed_origin(): + if os.environ.get("STUBGEN_TEST_INSTALLED") != "1": + return + prefix = Path(sys.prefix).resolve() + for name, module in list(sys.modules.items()): + if name == "pybind11_stubgen" or name.startswith("pybind11_stubgen."): + origin = getattr(module, "__file__", None) + assert origin is not None, (name, origin) + path = Path(origin).resolve() + assert path.is_relative_to(prefix), (name, path, prefix) + + +@pytest.fixture +def parser(): + result = make_parser() + with LocalErrors(q("unit"), set(), result.stack): + yield result + assert result.stack == [] + + +@pytest.fixture +def load_python_fixture(monkeypatch): + def is_fixture(name): + return name.startswith("stubgen_test_") + + saved = {name: module for name, module in sys.modules.items() if is_fixture(name)} + with monkeypatch.context() as scoped: + scoped.syspath_prepend(str(Path(__file__).parent / "fixtures")) + for name in saved: + sys.modules.pop(name) + try: + yield importlib.import_module + finally: + for name in list(sys.modules): + if is_fixture(name): + sys.modules.pop(name) + sys.modules.update(saved) + importlib.invalidate_caches() diff --git a/tests/unit/fixtures/stubgen_test_python.py b/tests/unit/fixtures/stubgen_test_python.py new file mode 100644 index 00000000..342a88d0 --- /dev/null +++ b/tests/unit/fixtures/stubgen_test_python.py @@ -0,0 +1,14 @@ +__all__ = ["ADerived", "ZBase", "typed"] + + +class ZBase: + pass + + +class ADerived(ZBase): + pass + + +def typed(value: int, *, enabled: bool = True) -> str: + """Convert.""" + return str(value) diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py new file mode 100644 index 00000000..4ca34cba --- /dev/null +++ b/tests/unit/test_parser.py @@ -0,0 +1,157 @@ +import logging +import sys + +from pybind11_stubgen.structs import ( + Argument, + Decorator, + Docstring, + Function, + Identifier, + Value, +) +from unit_support import q, rt + + +def test_python_signature(parser, caplog): + def sample(value: int = 2, *args: str, enabled: bool = True, **kwargs: int) -> str: + """Convert.""" + return str(value) + + assert parser.handle_function(q("unit.sample"), sample) == [ + Function( + Identifier("sample"), + args=[ + Argument( + Identifier("value"), annotation=rt("int"), default=Value("2", True) + ), + Argument(Identifier("args"), annotation=rt("str"), variadic=True), + Argument( + Identifier("enabled"), + annotation=rt("bool"), + default=Value("True", True), + kw_only=True, + ), + Argument(Identifier("kwargs"), annotation=rt("int"), kw_variadic=True), + ], + returns=rt("str"), + doc=Docstring("Convert."), + ) + ] + assert not [r for r in caplog.records if r.levelno >= logging.WARNING] + + +def test_docstring_argument_kinds(parser): + args = parser.parse_args_str("x: int, /, count: int = 2, *, enabled: bool = True") + assert args == [ + Argument(Identifier("x"), pos_only=True, annotation=rt("int")), + Argument(Identifier("count"), annotation=rt("int"), default=Value("2", True)), + Argument( + Identifier("enabled"), + kw_only=True, + annotation=rt("bool"), + default=Value("True", True), + ), + ] + assert parser.parse_args_str("*args: int, **kwargs: str") == [ + Argument(Identifier("args"), variadic=True, annotation=rt("int")), + Argument(Identifier("kwargs"), kw_variadic=True, annotation=rt("str")), + ] + + +def test_docstring_overloads_reach_handle_function(parser): + def choose(*args, **kwargs): + pass + + choose.__doc__ = "\n".join( + [ + "choose(*args, **kwargs)", + "Overloaded function.", + "", + "1. choose(x: int) -> str", + "", + "Integer.", + "", + "2. choose(x: str) -> int", + "", + "Text.", + ] + ) + assert parser.handle_function(q("unit.choose"), choose) == [ + Function( + Identifier("choose"), + args=[Argument(Identifier("x"), annotation=rt("int"))], + returns=rt("str"), + doc=Docstring("Integer."), + decorators=[Decorator("typing.overload")], + ), + Function( + Identifier("choose"), + args=[Argument(Identifier("x"), annotation=rt("str"))], + returns=rt("int"), + doc=Docstring("Text."), + decorators=[Decorator("typing.overload")], + ), + ] + + +def test_non_signature_preserves_generic_fallback(parser): + def unknown(*args, **kwargs): + """Not a signature.""" + + assert parser.parse_function_docstring(Identifier("unknown"), []) == [] + assert parser.parse_function_docstring(Identifier("unknown"), ["unknown("]) == [] + assert parser.handle_function(q("unit.unknown"), unknown) == [ + Function( + Identifier("unknown"), + args=[ + Argument(Identifier("args"), variadic=True), + Argument(Identifier("kwargs"), kw_variadic=True), + ], + doc=Docstring("Not a signature."), + ) + ] + + +def test_generic_docstring_version_boundary(parser): + actual = parser.parse_function_docstring( + Identifier("identity"), ["identity[T](value: T) -> T"] + ) + if sys.version_info < (3, 12): + assert actual == [] # The production parser rejects PEP 695 syntax here. + else: + assert actual == [ + Function( + Identifier("identity"), + args=[Argument(Identifier("value"), annotation=rt("T"))], + returns=rt("T"), + type_vars=["T"], + ) + ] + + +def test_module_definition_order(parser, load_python_fixture): + module = load_python_fixture("stubgen_test_python") + parsed = parser.handle_module(q(module.__name__), module) + assert parsed is not None + assert [str(c.name) for c in parsed.classes] == ["ZBase", "ADerived"] + assert parsed.classes[1].bases == [q("ZBase")] + assert [str(f.name) for f in parsed.functions] == ["typed"] + + +def test_class_members_use_definition_order_and_resolve_descriptors(parser): + class Parent: + inherited = 1 + + class Child(Parent): + zed = 2 + alpha = 3 + + @staticmethod + def answer(): + return 42 + + members = list(parser._iter_class_members(Child)) + names = [name for name, _ in members] + assert names.index("zed") < names.index("alpha") < names.index("inherited") + assert names.count("inherited") == 1 + assert dict(members)["answer"] is Child.answer diff --git a/tests/unit/unit_support.py b/tests/unit/unit_support.py new file mode 100644 index 00000000..b35790ec --- /dev/null +++ b/tests/unit/unit_support.py @@ -0,0 +1,18 @@ +from pybind11_stubgen import CLIArgs, arg_parser, stub_parser_from_args +from pybind11_stubgen.parser.interface import IParser +from pybind11_stubgen.structs import Annotation, QualifiedName, ResolvedType + + +def q(name: str) -> QualifiedName: + return QualifiedName.from_str(name) + + +def rt(name: str, *parameters: Annotation) -> ResolvedType: + return ResolvedType(q(name), list(parameters) if parameters else None) + + +def make_parser(*options: str) -> IParser: + args = arg_parser().parse_args( + [*options, "stubgen_test_python"], namespace=CLIArgs() + ) + return stub_parser_from_args(args) From 15a1a6323f5e7e91d980151f60eff599d1d322ee Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 04:09:36 +0000 Subject: [PATCH 20/40] test: cover annotation structure and invalid expressions --- tests/unit/test_annotations.py | 51 ++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/unit/test_annotations.py diff --git a/tests/unit/test_annotations.py b/tests/unit/test_annotations.py new file mode 100644 index 00000000..a36db991 --- /dev/null +++ b/tests/unit/test_annotations.py @@ -0,0 +1,51 @@ +import logging + +import pytest + +from pybind11_stubgen.structs import InvalidExpression, Value +from unit_support import rt + + +@pytest.mark.parametrize( + ("text", "expected"), + [ + ("int", rt("int")), + ( + "typing.Dict[str, typing.List[int | None]]", + rt( + "dict", + rt("str"), + rt("list", rt("typing.Union", rt("int"), rt("None"))), + ), + ), + ("int | str", rt("typing.Union", rt("int"), rt("str"))), + ("typing.Optional[int]", rt("typing.Optional", rt("int"))), + ( + "typing.Literal['x,y', 3]", + rt("typing.Literal", Value("'x,y'", True), Value("3", True)), + ), + ( + "typing.Annotated[int, 'units']", + rt("typing.Annotated", rt("int"), Value("'units'", True)), + ), + ], +) +def test_annotation_structure(parser, caplog, text, expected): + assert parser.parse_annotation_str(text) == expected + assert not [r for r in caplog.records if r.levelno >= logging.WARNING] + + +@pytest.mark.parametrize("text", ["list[", "std::vector"]) +def test_invalid_annotation_reports_expression(parser, caplog, text): + with caplog.at_level(logging.ERROR, logger="pybind11_stubgen"): + assert parser.parse_annotation_str(text) == InvalidExpression(text) + assert [r.getMessage() for r in caplog.records if r.levelno >= logging.ERROR] == [ + f"In unit : Invalid expression '{text}'" + ] + + +def test_invalid_expression_is_not_a_valid_value(parser, caplog): + assert parser.parse_value_str("'a,b'") == Value("'a,b'", True) + assert parser.parse_value_str("len('abc')") == Value("len('abc')", False) + assert parser.parse_value_str("[") == InvalidExpression("[") + assert any("Invalid expression '['" in r.getMessage() for r in caplog.records) From cc788f7d870d730d8e0c3f7b5ce275a5037aa61e Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 04:13:21 +0000 Subject: [PATCH 21/40] test: cover stable class dependencies and cycle fallback --- tests/unit/test_class_ordering.py | 141 ++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 tests/unit/test_class_ordering.py diff --git a/tests/unit/test_class_ordering.py b/tests/unit/test_class_ordering.py new file mode 100644 index 00000000..fa04a0b3 --- /dev/null +++ b/tests/unit/test_class_ordering.py @@ -0,0 +1,141 @@ +import logging + +import pytest + +from pybind11_stubgen.printer import Printer, _topological_sort_classes +from pybind11_stubgen.structs import ( + Alias, + Attribute, + Class, + Field, + Identifier, + Module, + Value, +) +from unit_support import q + + +def names(classes): + return [str(c.name) for c in classes] + + +@pytest.mark.parametrize("count", [0, 1]) +def test_empty_and_singleton(count): + classes = [Class(Identifier("Only"))] if count else [] + assert names(_topological_sort_classes(classes)) == (["Only"] if count else []) + + +def test_inheritance_and_stable_ready_priority(): + classes = [ + Class(Identifier("Child"), bases=[q("Base")]), + Class(Identifier("Peer")), + Class(Identifier("Base")), + Class(Identifier("Tail")), + ] + ordered = _topological_sort_classes(classes) + assert names(ordered) == ["Peer", "Base", "Child", "Tail"] + assert names(classes) == ["Child", "Peer", "Base", "Tail"] + assert ordered[2] is classes[0] + + +@pytest.mark.parametrize("kind", ["base", "alias", "field"]) +def test_dotted_sibling_reference(kind): + consumer = Class(Identifier("Consumer")) + if kind == "base": + consumer.bases = [q("Outer.Inner")] + elif kind == "alias": + consumer.aliases = [Alias(Identifier("Item"), q("Outer.Inner"))] + else: + consumer.fields = [ + Field(Attribute(Identifier("Item"), Value("Outer.Inner", True)), "static") + ] + classes = [consumer, Class(Identifier("Outer"))] + assert names(_topological_sort_classes(classes)) == ["Outer", "Consumer"] + + +@pytest.mark.parametrize( + ("expression", "safe"), + [ + ("External.Item", True), + ("Target", False), + ("Target()", True), + ("'Target'", True), + ("Self.Item", True), + ], +) +def test_irrelevant_field_references_keep_order(expression, safe): + consumer = Class( + Identifier("Self"), + fields=[ + Field(Attribute(Identifier("Item"), Value(expression, safe)), "static") + ], + ) + assert names( + _topological_sort_classes([consumer, Class(Identifier("Target"))]) + ) == [ + "Self", + "Target", + ] + + +def test_external_base_and_alias_do_not_add_local_edges(): + consumer = Class( + Identifier("Consumer"), + bases=[q("external.Base")], + aliases=[ + Alias(Identifier("Item"), q("external.Item")), + Alias(Identifier("Self"), q("Consumer")), + ], + ) + assert names(_topological_sort_classes([consumer, Class(Identifier("Peer"))])) == [ + "Consumer", + "Peer", + ] + + +def test_repeated_edges_do_not_leave_a_false_cycle(caplog): + consumer = Class( + Identifier("Child"), + bases=[q("Base")], + aliases=[Alias(Identifier("Item"), q("Base"))], + fields=[Field(Attribute(Identifier("Other"), Value("Base", True)), "static")], + ) + assert names(_topological_sort_classes([consumer, Class(Identifier("Base"))])) == [ + "Base", + "Child", + ] + assert not [r for r in caplog.records if r.levelno >= logging.WARNING] + + +def test_cycle_warns_and_appends_remaining_input_order(caplog): + classes = [ + Class(Identifier("B"), bases=[q("A")]), + Class(Identifier("Free")), + Class(Identifier("A"), bases=[q("B")]), + ] + with caplog.at_level(logging.WARNING, logger="pybind11_stubgen"): + assert names(_topological_sort_classes(classes)) == ["Free", "B", "A"] + messages = [r.getMessage() for r in caplog.records if r.levelno == logging.WARNING] + assert len(messages) == 1 + assert "Cycle detected" in messages[0] and "['B', 'A']" in messages[0] + + +def test_module_and_nested_classes_use_dependency_order(): + classes = [ + Class(Identifier("Child"), bases=[q("Base")]), + Class(Identifier("Base")), + ] + printer = Printer(invalid_expr_as_ellipses=True) + assert printer.print_module(Module(Identifier("sample"), classes=classes)) == [ + "class Base:", + " pass", + "class Child(Base):", + " pass", + ] + assert printer.print_class(Class(Identifier("Outer"), classes=classes)) == [ + "class Outer:", + " class Base:", + " pass", + " class Child(Base):", + " pass", + ] From 4c3c9245d6c376005c212bc13363eb7cdb0ed590 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 04:17:34 +0000 Subject: [PATCH 22/40] test: cover exact printer output and rendering options --- tests/unit/test_printer.py | 189 +++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 tests/unit/test_printer.py diff --git a/tests/unit/test_printer.py b/tests/unit/test_printer.py new file mode 100644 index 00000000..99428909 --- /dev/null +++ b/tests/unit/test_printer.py @@ -0,0 +1,189 @@ +import ast + +import pytest + +from pybind11_stubgen.printer import Printer +from pybind11_stubgen.structs import ( + Argument, + Attribute, + Class, + Decorator, + Docstring, + Function, + Identifier, + Import, + InvalidExpression, + Method, + Module, + Property, + Value, +) +from unit_support import q, rt + + +@pytest.fixture +def printer(): + return Printer(invalid_expr_as_ellipses=True) + + +def test_argument_separators_annotations_defaults_and_decorator(printer): + function = Function( + Identifier("choose"), + args=[ + Argument(Identifier("x"), annotation=rt("int"), pos_only=True), + Argument( + Identifier("count"), annotation=rt("int"), default=Value("2", True) + ), + Argument( + Identifier("flag"), + annotation=rt("bool"), + default=Value("True", True), + kw_only=True, + ), + ], + returns=rt("str"), + decorators=[Decorator("typing.overload")], + ) + lines = printer.print_function(function) + assert lines == [ + "@typing.overload", + "def choose(x: int, /, count: int = 2, *, flag: bool = True) -> str:", + " ...", + ] + ast.parse("\n".join(lines)) + + +def test_variadic_arguments_do_not_gain_an_extra_star(printer): + function = Function( + Identifier("collect"), + args=[ + Argument(Identifier("args"), variadic=True), + Argument(Identifier("flag"), kw_only=True), + Argument(Identifier("kwargs"), kw_variadic=True), + ], + ) + assert printer.print_function(function) == [ + "def collect(*args, flag, **kwargs):", + " ...", + ] + + +@pytest.mark.parametrize( + ("modifier", "expected"), + [(None, []), ("static", ["@staticmethod"]), ("class", ["@classmethod"])], +) +def test_method_modifier(printer, modifier, expected): + assert printer.print_method(Method(Function(Identifier("f")), modifier)) == [ + *expected, + "def f():", + " ...", + ] + + +@pytest.mark.parametrize( + ("annotation", "expected"), + [ + (rt("typing.Optional", rt("int")), "int | None"), + (rt("typing.Union", rt("int"), rt("str")), "int | str"), + (rt("dict", rt("str"), rt("list", rt("int"))), "dict[str, list[int]]"), + ], +) +def test_type_rendering(printer, annotation, expected): + assert printer.print_annotation(annotation) == expected + + +@pytest.mark.parametrize(("ellipses", "expected"), [(True, "..."), (False, "C++")]) +def test_invalid_expression_policy(ellipses, expected): + printer = Printer(invalid_expr_as_ellipses=ellipses) + assert printer.print_annotation(InvalidExpression("C++")) == expected + assert ( + printer.print_argument( + Argument(Identifier("x"), default=InvalidExpression("C++")) + ) + == f"x = {expected}" + ) + + +@pytest.mark.parametrize( + ("comments", "expected"), + [(False, "value: int"), (True, "value: int # value = ")], +) +def test_unsafe_value_comments(comments, expected): + printer = Printer(invalid_expr_as_ellipses=True, print_value_comments=comments) + assert printer.print_attribute( + Attribute(Identifier("value"), Value("", False), rt("int")) + ) == [expected] + assert ( + printer.print_argument( + Argument(Identifier("x"), default=Value("", False)) + ) + == "x = ..." + ) + + +def test_property_getter_setter_and_input_preservation(printer): + getter = Function( + Identifier("get_value"), + args=[Argument(Identifier("self"))], + returns=rt("int"), + ) + setter = Function( + Identifier("set_value"), + args=[ + Argument(Identifier("self")), + Argument(Identifier("value"), annotation=rt("int")), + ], + returns=rt("None"), + ) + prop = Property(Identifier("value"), None, getter=getter, setter=setter) + assert printer.print_property(prop) == [ + "@property", + "def value(self) -> int:", + " ...", + "@value.setter", + "def value(self, value: int) -> None:", + " ...", + ] + assert getter.name == "get_value" and setter.name == "set_value" + + +def test_docstring_escaping_round_trips(printer): + text = 'path \\data and """quote"""' + lines = printer.print_docstring(Docstring(text)) + assert lines == ['"""', 'path \\\\data and \\"\\"\\"quote\\"\\"\\"', '"""'] + assert ( + ast.get_docstring(ast.parse("\n".join(lines)), clean=False) + == "\n" + text + "\n" + ) + + +def test_module_imports_all_classes_functions_and_attributes(printer): + module = Module( + Identifier("sample"), + imports={ + Import(None, q("typing")), + Import(Identifier("annotations"), q("__future__.annotations")), + Import(Identifier("P"), q("pathlib.Path")), + }, + classes=[Class(Identifier("Thing"))], + functions=[Function(Identifier("z")), Function(Identifier("a"))], + attributes=[ + Attribute(Identifier("value"), Value("3", True), rt("int")), + Attribute(Identifier("__all__"), Value("['Thing', 'a', 'z']", True)), + ], + ) + lines = printer.print_module(module) + assert lines == [ + "from __future__ import annotations", + "from pathlib import Path as P", + "import typing", + "__all__ = ['Thing', 'a', 'z']", + "class Thing:", + " pass", + "def a():", + " ...", + "def z():", + " ...", + "value: int = 3", + ] + ast.parse("\n".join(lines)) From 6870c2864bcaf5379f008baf0344504cfafc60d3 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 04:21:43 +0000 Subject: [PATCH 23/40] test: cover writer layouts contents and error propagation --- tests/unit/test_writer.py | 75 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 tests/unit/test_writer.py diff --git a/tests/unit/test_writer.py b/tests/unit/test_writer.py new file mode 100644 index 00000000..68e6e935 --- /dev/null +++ b/tests/unit/test_writer.py @@ -0,0 +1,75 @@ +from pathlib import Path + +import pytest + +from pybind11_stubgen.printer import Printer +from pybind11_stubgen.structs import Docstring, Identifier, Module +from pybind11_stubgen.writer import Writer + + +def files(root: Path) -> dict[str, bytes]: + return { + p.relative_to(root).as_posix(): p.read_bytes() + for p in root.rglob("*") + if p.is_file() + } + + +@pytest.mark.parametrize("extension", ["pyi", "py"]) +def test_module_utf8_and_final_newline(tmp_path, extension): + module = Module(Identifier("sample"), doc=Docstring("café")) + Writer(extension).write_module(module, Printer(True), to=tmp_path) + assert files(tmp_path) == { + f"sample.{extension}": '"""\ncafé\n"""\n'.encode("utf-8") + } + + +@pytest.mark.parametrize("extension", ["pyi", "py"]) +def test_empty_package_gets_init_file(tmp_path, extension): + Writer(extension).write_module( + Module(Identifier("empty"), is_package=True), Printer(True), to=tmp_path + ) + assert files(tmp_path) == {f"empty/__init__.{extension}": b""} + + +@pytest.mark.parametrize("extension", ["pyi", "py"]) +def test_nested_submodule_layout(tmp_path, extension): + leaf = Module(Identifier("leaf"), doc=Docstring("Leaf")) + nested = Module(Identifier("nested"), sub_modules=[leaf]) + root = Module(Identifier("root"), sub_modules=[nested]) + Writer(extension).write_module(root, Printer(True), to=tmp_path) + assert files(tmp_path) == { + f"root/__init__.{extension}": b"from . import nested\n", + f"root/nested/__init__.{extension}": b"from . import leaf\n", + f"root/nested/leaf.{extension}": b'"""\nLeaf\n"""\n', + } + + +def test_explicit_subdirectory_uses_init(tmp_path): + Writer().write_module( + Module(Identifier("sample"), doc=Docstring("Renamed")), + Printer(True), + to=tmp_path, + sub_dir=Path("renamed"), + ) + assert files(tmp_path) == {"renamed/__init__.pyi": b'"""\nRenamed\n"""\n'} + + +@pytest.mark.parametrize("kind", ["missing", "file"]) +def test_invalid_output_root_is_rejected(tmp_path, kind): + root = tmp_path / "output" + if kind == "file": + root.write_bytes(b"sentinel") + with pytest.raises(AssertionError): + Writer().write_module(Module(Identifier("sample")), Printer(True), to=root) + assert files(tmp_path) == ({"output": b"sentinel"} if kind == "file" else {}) + + +def test_write_error_propagates(tmp_path): + # A directory at the output filename fails even when tests run as root. + target = tmp_path / "sample.pyi" + target.mkdir() + with pytest.raises(OSError): + Writer().write_module(Module(Identifier("sample")), Printer(True), to=tmp_path) + assert target.is_dir() + assert files(tmp_path) == {} From 832f328b448d1a0ca6cbccb82532a2ece23c867b Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 04:27:52 +0000 Subject: [PATCH 24/40] test: exercise pure Python generation pipeline without native bindings --- .../fixtures/stubgen_test_package/__init__.py | 3 + .../fixtures/stubgen_test_package/leaf.py | 5 ++ tests/unit/test_pipeline.py | 65 +++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 tests/unit/fixtures/stubgen_test_package/__init__.py create mode 100644 tests/unit/fixtures/stubgen_test_package/leaf.py create mode 100644 tests/unit/test_pipeline.py diff --git a/tests/unit/fixtures/stubgen_test_package/__init__.py b/tests/unit/fixtures/stubgen_test_package/__init__.py new file mode 100644 index 00000000..a2f3ad6d --- /dev/null +++ b/tests/unit/fixtures/stubgen_test_package/__init__.py @@ -0,0 +1,3 @@ +from . import leaf + +__all__ = ["leaf"] diff --git a/tests/unit/fixtures/stubgen_test_package/leaf.py b/tests/unit/fixtures/stubgen_test_package/leaf.py new file mode 100644 index 00000000..8d47dbb2 --- /dev/null +++ b/tests/unit/fixtures/stubgen_test_package/leaf.py @@ -0,0 +1,5 @@ +__all__ = ["double"] + + +def double(value: int) -> int: + return value * 2 diff --git a/tests/unit/test_pipeline.py b/tests/unit/test_pipeline.py new file mode 100644 index 00000000..888dc922 --- /dev/null +++ b/tests/unit/test_pipeline.py @@ -0,0 +1,65 @@ +import ast +import logging + +from pybind11_stubgen import run +from pybind11_stubgen.printer import Printer +from pybind11_stubgen.writer import Writer +from unit_support import make_parser + + +def test_python_module_pipeline(tmp_path, load_python_fixture, caplog): + module = load_python_fixture("stubgen_test_python") + run( + make_parser(), + Printer(True), + [module.__name__], + str(tmp_path), + root_suffix=None, + dry_run=False, + writer=Writer(), + ) + expected = ( + "from __future__ import annotations\n" + "__all__: list = ['ADerived', 'ZBase', 'typed']\n" + "class ZBase:\n" + " pass\n" + "class ADerived(ZBase):\n" + " pass\n" + "def typed(value: int, *, enabled: bool = True) -> str:\n" + ' """\n' + " Convert.\n" + ' """\n' + ) + assert { + p.relative_to(tmp_path).as_posix(): p.read_bytes() + for p in tmp_path.rglob("*") + if p.is_file() + } == {"stubgen_test_python.pyi": expected.encode("utf-8")} + ast.parse(expected) + assert not [r for r in caplog.records if r.levelno >= logging.WARNING] + + +def test_package_pipeline(tmp_path, load_python_fixture, caplog): + module = load_python_fixture("stubgen_test_package") + run( + make_parser(), + Printer(True), + [module.__name__], + str(tmp_path), + root_suffix=None, + dry_run=False, + writer=Writer(), + ) + expected = { + "stubgen_test_package/__init__.pyi": b"from __future__ import annotations\nfrom . import leaf\n__all__: list = ['leaf']\n", + "stubgen_test_package/leaf.pyi": b"from __future__ import annotations\n__all__: list = ['double']\ndef double(value: int) -> int:\n ...\n", + } + actual = { + p.relative_to(tmp_path).as_posix(): p.read_bytes() + for p in tmp_path.rglob("*") + if p.is_file() + } + assert actual == expected + for text in actual.values(): + ast.parse(text.decode("utf-8")) + assert not [r for r in caplog.records if r.levelno >= logging.WARNING] From f5b47e9ecba122e3b1a50b49d235b26448aba012 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 04:43:17 +0000 Subject: [PATCH 25/40] test: run compiler-free contracts across supported Python versions --- .github/workflows/ci.yml | 30 ++++++++++++- .../2026-09-25-test-harness-phase-two.md | 8 +++- ...026-09-25-test-harness-phase-two-design.md | 6 ++- tests/README.md | 43 ++++++++++++++++--- tox.ini | 15 +++++++ 5 files changed, 93 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 553585f0..7f19f323 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,34 @@ jobs: name: dist path: dist + unit-tests: + name: "Compiler-free tests • Python ${{ matrix.python }}" + runs-on: ubuntu-latest + needs: [build] + strategy: + fail-fast: false + matrix: + python: ["3.10", "3.11", "3.12", "3.13"] + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + - uses: astral-sh/setup-uv@v7 + - uses: actions/download-artifact@v6 + with: + name: dist + path: dist + - name: Install wheel and pytest only + shell: bash + run: | + uv venv .venv + uv pip install --python .venv/bin/python 'pytest>=8,<9' dist/*.whl + - name: Test installed wheel without native fixtures + env: + STUBGEN_TEST_INSTALLED: "1" + run: .venv/bin/python -I -m pytest tests/unit tests/test_snapshot_helpers.py + tests: name: "Test 🐍 ${{ matrix.config.python }} • pybind-${{ matrix.config.pybind11-branch }} • ${{ matrix.config.numpy-format }}" runs-on: ubuntu-latest @@ -259,7 +287,7 @@ jobs: publish: name: Publish distribution - needs: [check, build, tests, test-cli-options] + needs: [check, build, unit-tests, tests, test-cli-options] if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') runs-on: ubuntu-latest permissions: diff --git a/docs/superpowers/plans/2026-09-25-test-harness-phase-two.md b/docs/superpowers/plans/2026-09-25-test-harness-phase-two.md index 51607c82..79536094 100644 --- a/docs/superpowers/plans/2026-09-25-test-harness-phase-two.md +++ b/docs/superpowers/plans/2026-09-25-test-harness-phase-two.md @@ -58,6 +58,12 @@ env -u VIRTUAL_ENV uv run --no-project --python 3.10 --with 'pytest>=8,<9' pytho env -u VIRTUAL_ENV uv run --no-project --python 3.13 --with 'pytest>=8,<9' python -m pytest tests/unit tests/test_snapshot_helpers.py -q ``` +Implementation-time correction (Task 7): Task 6 found that `--no-project` +can still reuse the worktree's `.venv` even with `VIRTUAL_ENV` unset. Add +`--isolated` to uv-managed source verification commands going forward; the +commands above and earlier task evidence are retained as historical context. +Fresh explicit source/wheel venvs and tox commands are unchanged. + Before new files exist, run only `tests/test_snapshot_helpers.py` with both commands. The last recorded baseline was 97 passing tests on each interpreter; record fresh results rather than relying on that history. @@ -925,7 +931,7 @@ is needed. The following command tests source edits directly without syncing the development dependency group: ```sh -uv run --no-project --with 'pytest>=8,<9' python -m pytest \ +uv run --no-project --isolated --with 'pytest>=8,<9' python -m pytest \ tests/unit tests/test_snapshot_helpers.py ``` diff --git a/docs/superpowers/specs/2026-09-25-test-harness-phase-two-design.md b/docs/superpowers/specs/2026-09-25-test-harness-phase-two-design.md index 78ee53f2..cb67ad24 100644 --- a/docs/superpowers/specs/2026-09-25-test-harness-phase-two-design.md +++ b/docs/superpowers/specs/2026-09-25-test-harness-phase-two-design.md @@ -218,12 +218,14 @@ When a new case reveals an unexpected production failure: Document the direct source-checkout command: ```sh -uv run --no-project --with 'pytest>=8,<9' python -m pytest \ +uv run --no-project --isolated --with 'pytest>=8,<9' python -m pytest \ tests/unit tests/test_snapshot_helpers.py ``` This provides pytest without syncing the native-heavy development dependency -group. The suite needs only the standard library, pytest and its dependencies, +group. `--isolated` prevents uv from reusing an existing worktree environment; +`--no-project` alone does not guarantee that isolation. The suite needs only +the standard library, pytest and its dependencies, and the generator code. Running it must not build/install the demo, download a formatter, or invoke a compiler. diff --git a/tests/README.md b/tests/README.md index e31f98cd..615ef57b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -32,6 +32,31 @@ Or let uv supply only pytest, without syncing the project's native dependencies: uv run --no-project --with 'pytest>=8,<9' python -m pytest tests/test_snapshot_helpers.py ``` +## Compiler-free production tests + +`tests/unit/` covers parser/signature and annotation behavior, class ordering, +printer output, writer paths/contents, and small pure-Python generation flows. +Its fixtures are independent of `demo`; no native installation or NumPy/SciPy +is needed. The following command tests source edits directly without syncing +the development dependency group: + +```sh +uv run --no-project --isolated --with 'pytest>=8,<9' python -m pytest \ + tests/unit tests/test_snapshot_helpers.py +``` + +To test a non-editable installation on each supported matrix interpreter: + +```sh +uv run --no-project --with tox --with tox-uv tox \ + -e py310-unit,py311-unit,py312-unit,py313-unit +``` + +The unit environments install with uv, run isolated Python, and verify the +actual pytest process imports the installed generator. The default tox list +still runs the existing 13 native configurations; unit environments are explicit. +CI adds a separate compiler-free wheel matrix while retaining native/gemmi jobs. + ## Native checks ```sh @@ -87,8 +112,16 @@ reference trees. Tox uses its environment temporary directory; CI uploads ## Add a regression -For harness behavior, add a small synthetic test to `test_snapshot_helpers.py`; -no compiler is required. For generated-output behavior, add or adjust the demo -fixture, run a relevant native environment, inspect its failure, then explicitly -update that environment's expectations. Review only the intended changes and -run affected shared profiles and the compatibility matrix before merging. +For harness behavior, add a small synthetic test to `test_snapshot_helpers.py`. +For compiler-free generator behavior, prefer a focused test in `tests/unit/` +with explicit expected models or text. Neither route requires a compiler. + +When the regression requires real binding behavior, add or adjust the native +demo fixture, run a relevant native environment, inspect its failure, then +explicitly update that environment's expectations. Review only the intended +changes and run affected shared profiles and the compatibility matrix before +merging. + +During test-only phase two, report newly discovered production bugs separately. +Do not hide them with changed expectations, unexplained skips, or snapshot +updates; production fixes require a separate scope. diff --git a/tox.ini b/tox.ini index 94f68297..8481434b 100644 --- a/tox.ini +++ b/tox.ini @@ -56,3 +56,18 @@ commands_pre = commands = uv pip install --python "{envpython}" . {envpython} -m pytest {toxinidir}/tests/test_snapshot_helpers.py {toxinidir}/tests/test_demo_stubs.py {toxinidir}/tests/test_demo_errors.py --pybind11-branch {env:PYBIND11_BRANCH} --numpy-format {env:NUMPY_FORMAT} --artifacts-dir {envtmpdir}/pytest-artifacts {posargs} + +[testenv:py{310,311,312,313}-unit] +description = Check compiler-free production contracts and snapshot helpers +package = skip +setenv = + PYTHONUNBUFFERED = 1 + STUBGEN_TEST_INSTALLED = 1 +allowlist_externals = + uv +deps = + pytest>=8,<9 +commands_pre = +commands = + uv pip install --python "{envpython}" . + {envpython} -I -m pytest {toxinidir}/tests/unit {toxinidir}/tests/test_snapshot_helpers.py {posargs} From 91bfa7f63aa05cfb901eb1632d1d95d7f96f963d Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 05:25:52 +0000 Subject: [PATCH 26/40] docs: define shared native test orchestration design --- ...6-09-25-test-harness-phase-three-design.md | 395 ++++++++++++++++++ 1 file changed, 395 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-25-test-harness-phase-three-design.md diff --git a/docs/superpowers/specs/2026-09-25-test-harness-phase-three-design.md b/docs/superpowers/specs/2026-09-25-test-harness-phase-three-design.md new file mode 100644 index 00000000..450f9ea0 --- /dev/null +++ b/docs/superpowers/specs/2026-09-25-test-harness-phase-three-design.md @@ -0,0 +1,395 @@ +# Test harness phase three: shared orchestration and native builds + +## Status and starting point + +The orchestration, fixture-build, and acceptance sections were approved in +conversation. This written specification awaits user review. The runtime +NumPy reconciliation described below was discovered while preparing the document +and is explicitly part of that review, not a silently approved dependency change. +No phase-three implementation is included here. + +Continue on `test-harness-phase-one` in its existing isolated worktree. The +current baseline is `807e7e44a1536699faf0070978303f58caa45648`; earlier handoff +hashes predate rewritten history and are not this phase's preservation anchor. +Do not merge, push, or publish earlier phases as a prerequisite. + +This implements phase three of the +[phase-one roadmap](2026-09-24-test-harness-phase-one-design.md#later-phase-roadmap). +The [phase-two test layer](2026-09-25-test-harness-phase-two-design.md) remains in +place. Fresh source runs during specification preparation passed all 155 +compiler-free tests on Python 3.10 and 3.13. Earlier recorded acceptance includes +all four supported interpreters and all 13 native cases; repeat the native +baseline on the current revision before implementation changes its setup. + +Remote CI, gemmi execution on GitHub, and actual diagnostic artifact-upload +acceptance remain deferred by user choice. Local evidence must not be described +as remote acceptance. + +## Goal and chosen approach + +Make local and CI native checks use the same configuration and commands. Build +one native fixture for each Python/pybind11 combination, then use it for all +configured NumPy rendering modes. Simplify fixture packaging without changing +the generator, demo behavior, or expectations. + +Use tox as the shared driver and authoritative native execution configuration. +CI derives its native matrix from tox instead of maintaining a second list of +versions, dependency choices, installation commands, and pytest commands. +Replace the fixture's handwritten setuptools/CMake bridge with scikit-build-core +if the compatibility evaluation passes. Retain cmeel-provided Eigen. + +Alternatives considered: + +1. Keep 13 environments and share compiled wheels through a cache. This retains + existing names but adds cache keys, invalidation, and concurrency concerns. +2. Introduce a standalone orchestration framework shared by tox and CI. This + duplicates responsibilities tox already provides. + +The user selected grouping the modes within 11 tox environments. Small helpers +may translate resolved tox configuration to CI JSON or prepare a fixture build; +they must not become an independent configuration system or environment manager. + +## Scope and invariants + +Allowed changes: + +- Native environment definitions and package-install orchestration in `tox.ini`. +- Native matrix generation, invocation, and failure-artifact wiring in + `.github/workflows/ci.yml`. +- Fixture build metadata under `tests/py-demo/`, removal of its custom `setup.py`, + and narrowly necessary CMake integration/install rules. +- Replacement or removal of `tests/install-demo-module.sh` and small test-side + build/configuration helpers that replace its remaining responsibilities. +- Test fixtures/options needed to parameterize native modes and enforce installed + provenance; focused compiler-free tests of the new support code. +- Test documentation and this phase's specification and implementation plan. + +Preserve: + +- Every production file under `pybind11_stubgen/`. +- Native C++ fixture source/header contents and existing Python files under + `tests/py-demo/demo/`, including module identities and exported behavior. +- Reference bytes and symlink aliases under `tests/stubs/` and `tests/errors/`. +- Snapshot comparison, normalization, update guards, process contracts, + existing 300-second snapshot subprocess timeouts, and diagnostic retention. +- All 13 logical compatibility cases and both native checks for each case. +- The phase-two compiler-free coverage, four explicit unit environments, and + their independence from native/optional dependencies and formatter downloads. +- Generator build backend/package discovery, the root development dependency + group and lockfile, gemmi jobs, workflow triggers, and publication conditions + and permissions. Native tests stop using the root development group; that + group is not the new native dependency authority. + +No production fixes, broad dependency upgrades, snapshot regeneration or +reorganization, new platform matrix, or persistent cross-environment native +wheel cache. No push, PR, remote workflow execution, or publication. Fixture +wheels are local test inputs, not a newly published distribution. Making a +standalone fixture sdist that includes sibling C++ sources is not a goal. + +## Native environment and case model + +Remove the NumPy suffix from native environment names. The default tox list +contains exactly these 11 build profiles: + +- `py310-pb30`, `py311-pb30`, `py312-pb30`, `py313-pb30`. +- `py310-pb213`, `py311-pb213`, `py312-pb213`, `py313-pb213`. +- `py313-pb29`, `py313-pb211`, `py313-pb212`. + +| Python | pybind11 series | Modes | Build profiles | Logical cases | +| --- | --- | --- | --- | --- | +| 3.10–3.12 | 2.13, 3.0 | Annotated | 6 | 6 | +| 3.13 | 2.13, 3.0 | Annotated, type-variable | 2 | 4 | +| 3.13 | 2.9, 2.11, 2.12 | Annotated | 3 | 3 | + +The exact mode names remain `numpy-array-wrap-with-annotated` and +`numpy-array-use-type-var`. Keep the existing `v2.9`, `v2.11`, `v2.12`, `v2.13`, +and `v3.0` labels for reference selection. Those labels identify compatibility +series, not floating Git inputs after this migration. + +Old native tox names need not remain as aliases; the user approved regrouping +and renaming. Document their replacements. The four `py310-unit` through +`py313-unit` environments remain explicitly selectable and outside the default +native list. + +Tox configuration owns environment membership, interpreter selection, native +dependency pins, and default modes. Matrix-generation support consumes tox's +resolved configuration/default environment list rather than reimplementing +brace expansion or carrying another hard-coded matrix. Generated JSON contains +the environment name and interpreter information needed by CI. Reject malformed, +duplicate, or unsupported native profile data with an actionable error instead +of silently omitting it. Tests may have an independent explicit expected case +inventory: that is a regression oracle, not another execution configuration. + +## Dependency policy and evaluation inputs + +### pybind11 and CMake + +The existing cached pybind11 checkouts are at the release tags below. All five +releases have published Python wheels containing their headers and CMake +configuration. Availability was checked during design; compatibility with the +new build route has not yet been demonstrated. + +| Series label | pybind11 pin | CMake evaluation pin | +| --- | --- | --- | +| `v2.9` | `2.9.2` | `3.31.10` | +| `v2.11` | `2.11.2` | `3.28.3` | +| `v2.12` | `2.12.1` | `3.28.3` | +| `v2.13` | `2.13.6` | `4.2.3` | +| `v3.0` | `3.0.4` | `4.2.3` | + +These CMake versions match the currently installed local tox tools. Choosing +3.31.10 for 2.9 deliberately reconciles the existing local 3.31-series setting +with CI's separate 3.28.3 choice; it must pass the same acceptance gate, not be +assumed interchangeable. Do not upgrade all families to one recent CMake merely +to simplify a table. + +Install the pinned pybind11 distribution rather than cloning and installing +its CMake project. Select its CMake configuration explicitly and verify the +resolved version/location in build evidence, so an ambient installation cannot +silently supply different headers. Retain `cmeel==0.59.0` and +`cmeel-eigen==3.4.0.2`, with explicit discovery of that Eigen installation. + +The initial new-backend/tool evaluation candidates are +`scikit-build-core==1.0.3` and `ninja==1.13.2`; their published Python requirements +permit every supported interpreter. These are newly needed explicit execution +pins, not a claim that the migration has been tested. Tox owns the execution +pins; fixture build metadata declares compatible backend requirements rather +than another interpreter/pybind11 version matrix. + +### Runtime dependencies: reconcile declarations with actual tests + +Inspection of all 13 existing environments found that their installed NumPy +versions do not satisfy the `numpy~=1.20` declaration in tox. The root lockfile +also records NumPy 1.26.4, whereas the native checks run with NumPy 2.x. + +The existing installer runs `uv pip install --force-reinstall` on the fixture, +whose `setup.py` declares unbounded `install_requires=["numpy"]`. That second +installation is unconstrained by tox's original NumPy requirement. Retained tox +installation logs show NumPy being reinstalled at this stage. Therefore the +configured requirement alone is not a reliable description of native runtime +coverage; the mismatch is present before phase three. + +The proposed policy is to pin the actual observed runtime versions, preserving +what the accepted native tests exercise rather than silently switching them to +NumPy 1.x or upgrading them further: + +| Python | NumPy | SciPy | +| --- | --- | --- | +| 3.10 | `2.2.6` | `1.15.3` | +| 3.11 | `2.4.6` | `1.17.1` | +| 3.12–3.13 | `2.5.3` | `1.17.1` | + +All existing environments for a given interpreter agree on these versions. +This NumPy declaration change requires the user's explicit written-spec review. +It changes declared native setup policy, not the observed runtime baseline. +Keep the current pytest and typing-extensions requirements; unrelated package +or root development-group changes are not included. + +Install runtime dependencies through tox, then install the fixture without +resolving/reinstalling its dependencies. Verify the installed NumPy/SciPy +versions before and after fixture installation and test execution. A drift +check is required: matching snapshots alone must not hide another replacement. +Do not claim that matching top-level pins constitute a fully locked transitive +environment. + +If any candidate cannot preserve the case inventory and existing expectations, +investigate and report the specific incompatibility. No silent fallback to a +floating branch, different dependency version, skipped case, or regenerated +reference is acceptable. Material design changes require user approval. + +## Fixture build architecture + +The current roles are distinct: cmeel supplies Eigen and its CMake prefix; +setuptools plus `tests/py-demo/setup.py` currently drive CMake and assemble the +fixture wheel. Replace the latter, not the former. + +The new fixture path is: + +1. Tox creates a profile-specific environment and installs its selected build + tools, pybind11, Eigen provider, and runtime/test dependencies. +2. A small build invocation selects that interpreter and the installed + dependency locations explicitly. It uses a build directory beneath that + environment's temporary directory. +3. scikit-build-core configures/builds the existing bindings CMake project. Its + existing `add_subdirectory` relationship builds `demo-lib` as an extension + dependency. Remove the separate standalone demo-library build/install pass. +4. CMake install rules and backend wheel configuration package the existing + `demo` Python files and native extension. Do not include the static library, + Eigen headers, build trees, or unrelated fixture-development files. +5. Install the fixture non-editably into the same tox environment, without + changing its preinstalled runtime dependencies. + +Use the tox-provisioned build environment explicitly, without a second PEP 517 +build-isolation environment resolving different pybind11/CMake/backend versions. +This is deliberate dependency control inside an isolated tox environment, not +an instruction to build against arbitrary system tools. Fixture build metadata +still declares its build backend requirements. Do not require pip inside tox. + +Keep the `py-demo` distribution identity/version, the `demo` package/import +layout, extension identity, and NumPy runtime requirement. Limit CMake changes +to build integration, dependency selection, and installation: no C++ fixture +semantics or compiler-standard changes. + +Separate native environments share no mutable CMake build or install tree. +Their two NumPy modes, where configured, reuse one installed extension without +an intervening build. No cross-environment compiled-wheel cache or locking +framework is introduced. Re-running tox must incorporate current fixture +sources, including the sibling demo-library headers/sources; frontend wheel +caching must not hide their changes. A repeat run may use correctly scoped +incremental compilation, but correctness does not depend on doing so. + +Do not delete the source tree's shared `tests/py-demo/build` or old +`tmp/pybind11-*` directories as part of the new runner. They are no longer inputs. +Any cleanup is confined to the invoking environment's owned build paths. Do not +inherit ambient CMake search paths in a way that overrides the selected inputs. +Log the actual interpreter, tool versions, dependency locations, and build path. + +## Pytest execution and installed provenance + +Run one pytest session per native environment, parameterizing both native checks +across its selected modes. Keep the existing case-to-reference mapping and +comparison/update helpers. Both stubs and errors execute for all 13 logical +cases even though some error expectations are shared. + +Use stable, mode-specific test IDs and the existing full case identity for +failure workspaces. Harness self-tests need run only once per environment, not +once per mode. Default execution must not stop after the first mode's ordinary +test failure; no shell-command sequencing that drops the second mode. + +Retain `--pybind11-branch` for explicit reference-series selection. Retain +`--numpy-format` for single-mode debugging; a command-line selection overrides +the environment's default mode set rather than appending to it. Reject invalid +selections clearly. Plain compiler-free collection must not need native options, +import `demo`, or trigger dependency installation. + +Installed-generator verification occurs inside the actual pytest process, not +only in a separate preflight interpreter. Preserve the phase-two source-testing +mode while sharing or extending its installed-origin enforcement for native +runs. Native fixtures also check that the imported demo/extension are installed +in the intended environment, not resolved from the fixture checkout or another +profile. Keep isolated Python invocation for generator subprocesses. + +Local native tox builds and installs a generator wheel through tox's package +mechanism. CI supplies its already-built wheel through tox's `--installpkg` +interface. A supplied artifact must not be replaced by a checkout build or +editable install. Record the supplied wheel identity and inspect the resolved +installation path/commands as well as runtime provenance; site-packages origin +alone does not prove which artifact was installed. + +## CI and local interfaces + +The native CI matrix has 11 jobs, each selecting one tox environment from the +derived matrix. Bootstrap tox/tox-uv through the existing uv setup. Remove the +native job's separate CMake action, development-group sync, direct fixture +installer call, and independently maintained pytest command. Those choices now +come from the same tox environment used locally. + +Continue downloading the generator wheel from the existing build job. Preserve +the native job's required publication gate, matrix failure independence, the +separate four-version compiler-free wheel job, gemmi jobs, and release controls. +Generating a matrix may add an output/step to an existing prerequisite job; it +must not compile fixtures or create a second native dependency matrix. + +Failure artifact collection must find the new environment-scoped locations. +Retain tox command/build logs when failure occurs before pytest, as well as the +existing per-case pytest diagnostics. Do not upload whole virtual environments +or confuse build logs with snapshot differences. Missing pytest artifacts on an +early build failure do not make that build successful. + +Document normal local use, a single grouped profile, filtering a native check, +a single NumPy mode, an installed-fixture rerun, and supplying a prebuilt generator +wheel. Update old environment names and explain the retained series labels. +Keep the compiler-free source/unit commands and their no-native-dependency +contract. Snapshot updates remain explicit, reviewed, and serial; concurrent +reference mutation is not supported even after builds become independent. + +## Error handling and regression tests + +Dependency resolution, configuration, compilation, installation, and pytest +failures propagate as nonzero results with their phase, command, and useful logs. +Do not catch an error and continue using an old installed fixture. Unknown matrix +entries and missing required interpreters/dependencies are errors, not skipped +coverage. Keep bounded build concurrency for local parallel verification. + +Add focused compiler-free tests for support introduced by this phase, including: + +- Matrix derivation and an independent exact inventory of 11 profiles/13 cases; + rejection of invalid or duplicate profile data. +- Default mode selection, single-mode override, stable IDs, and execution of + remaining modes after an ordinary failure. +- Profile-specific build/diagnostic paths and rejection of unsafe cleanup paths + wherever cleanup support is introduced. +- Correct interpreter, dependency locations, and wheel-install arguments at + process boundaries; nonzero results and diagnostic retention on failure. +- Installed-origin rejection, explicit source-mode behavior, and runtime + dependency drift detection. + +Use real support functions and temporary paths. Synthetic subprocesses or +process-local negative controls may test orchestration failures; do not replace +production parser/printer behavior with mocks or add text-only tests as a +substitute for actual tox/build execution. New support tests belong in the +compiler-free selection and require only pytest and the standard library beyond +the generator. Avoid additional package requirements merely to parse the matrix. + +## Verification and local acceptance + +Before implementation, capture the actual revision's current native baseline, +selected dependency versions, case inventory, reference-tree/alias fingerprints, +and Git index state. Run the existing 13 environments serially; a new baseline +failure is investigated rather than attributed to this migration without proof. + +Then require all of the following on the implemented configuration: + +1. **Compiler-free preservation:** source checks and installed unit/wheel runs on + Python 3.10–3.13 pass, including the existing 155 tests and new support tests. + Clean environments lack the demo, NumPy, SciPy, and native build tools where + those are unnecessary. No formatter download or native setup occurs. +2. **Full native parity:** fresh profile environments build successfully and run + the exact 13-case inventory across 11 profiles, with both native checks for + every case and no skips/xfails/deselection masking missing coverage. Inspect + collection/results, not only an aggregate test count. +3. **Actual fixture reuse:** evidence for each dual-mode environment shows one + installed extension used by both modes and no intervening fixture build. + The standalone demo-library build/install pass is gone. +4. **Isolation and freshness:** repeat native runs pass; a source-change probe in + a disposable copy demonstrates that sibling fixture changes are rebuilt. + Two distinct representative profiles also pass with bounded concurrency and + disjoint build/diagnostic paths. Capture failures independently. This is not + permission for parallel snapshot updates or an unbounded compiler load. +5. **Supplied-wheel route:** locally exercise the same tox external-wheel route + CI will use across all 11 profiles. Verify artifact identity, installed + imports, and the absence of a replacement generator source build. A wheel + built from clean tracked sources avoids confusing the known dirty-build + packaging observation with this migration's results. +6. **Dependencies and payload:** build evidence identifies the selected tools, + pybind11 and Eigen locations/versions. Runtime pins remain unchanged through + fixture installation and checks. Inspect fixture wheels for correct module + placement and absence of incidental static libraries, headers, or build trees. +7. **Failure sensitivity:** negative controls establish wrong-origin rejection, + dependency-drift detection, nonzero build/install/test propagation, surviving + diagnostics, and continuation to the second mode after an ordinary failure. + Restore the unmodified path and re-run green checks afterward. +8. **Preservation:** check-mode runs leave all reference bytes, symlink targets, + and Git index entries unchanged. Production and fixture source invariants + have empty diffs against the current baseline. Review the remaining changes + against the explicit allowed scope, including unchanged release controls. +9. **Repository checks:** existing lock checks, applicable all-file pre-commit + hooks, and whitespace checks pass. No reference updates or unrelated lockfile + changes are accepted as a way to obtain a green result. + +A normal-run performance observation may document reduced build work, but there +is no wall-clock target or coverage-percentage target. Do not count reduced +repetition of harness self-tests as lost native-case coverage or require old +aggregate totals to remain identical after grouping. + +Keep the previously reported ordinary-Python positional-only parsing issue and +repeated dirty-tree generator packaging issue separate; neither is fixed by +this phase. If another production bug or dependency incompatibility appears, +record a minimal reproduction, expected/current behavior, affected inputs, and +verification limits. Stop for a scope decision if it blocks an agreed contract. + +Only local acceptance is required here. Remote workflow execution, gemmi remote +acceptance, and actual artifact upload remain unverified until separately +authorized. After the user approves this written specification, prepare a +reviewable implementation plan; do not start implementation from this document's +conversation approvals alone. From a8e3b39693fc95fbd8b6923e630a94d555d16e2a Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 06:29:17 +0000 Subject: [PATCH 27/40] docs: plan shared native test orchestration implementation --- .../2026-09-25-test-harness-phase-three.md | 1442 +++++++++++++++++ ...6-09-25-test-harness-phase-three-design.md | 12 +- 2 files changed, 1448 insertions(+), 6 deletions(-) create mode 100644 docs/superpowers/plans/2026-09-25-test-harness-phase-three.md diff --git a/docs/superpowers/plans/2026-09-25-test-harness-phase-three.md b/docs/superpowers/plans/2026-09-25-test-harness-phase-three.md new file mode 100644 index 00000000..e3a1da0e --- /dev/null +++ b/docs/superpowers/plans/2026-09-25-test-harness-phase-three.md @@ -0,0 +1,1442 @@ +# Shared Native Test Orchestration Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Run the same 13 native compatibility cases through 11 independently built tox profiles locally and in CI, using a standard fixture backend and explicit dependencies without changing generator behavior or snapshots. + +**Architecture:** Tox owns the native environment list, dependency choices, and default NumPy modes. Pytest parameterizes modes within an installed environment; a small stdlib helper builds and installs one fixture wheel using tox-provisioned tools. A separate stdlib adapter translates tox's expanded default environment names into CI matrix JSON; it does not manage builds or environments. + +**Tech Stack:** Python 3.10–3.13, pytest 8, tox 4 with tox-uv, uv, scikit-build-core 1.0.3, Ninja 1.13.2, CMake, pybind11, cmeel/Eigen, GitHub Actions. + +**Spec:** `docs/superpowers/specs/2026-09-25-test-harness-phase-three-design.md` (approved, including the NumPy/SciPy reconciliation). + +## Global Constraints + +- Continue in `.worktrees/test-harness-phase-one` on `test-harness-phase-one`; preserve runtime/source invariants against `807e7e44a1536699faf0070978303f58caa45648`. +- Every production file under `pybind11_stubgen/` remains unchanged. +- Native C++ fixture source/header contents and existing Python files under `tests/py-demo/demo/` remain unchanged. +- Reference bytes and symlink aliases under `tests/stubs/` and `tests/errors/` remain unchanged. +- Preserve snapshot comparison, normalization, update guards, process contracts, existing 300-second snapshot subprocess timeouts, and diagnostic retention. +- Preserve all 13 logical compatibility cases and both native checks for each case. The default native list becomes exactly 11 profiles; the four `py310-unit` through `py313-unit` environments remain explicit. +- Preserve generator build backend/package discovery, the root development dependency group and lockfile, gemmi jobs, workflow triggers, and publication conditions and permissions. +- Preserve compiler-free independence from native/optional dependencies and formatter downloads; new support tests require only pytest and the standard library beyond the generator. +- pybind11 pins: `2.9.2`, `2.11.2`, `2.12.1`, `2.13.6`, `3.0.4` for series `v2.9`, `v2.11`, `v2.12`, `v2.13`, `v3.0` respectively. +- CMake evaluation pins: `3.31.10`, `3.28.3`, `3.28.3`, `4.2.3`, `4.2.3` for those same series respectively. +- Retain `cmeel==0.59.0` and `cmeel-eigen==3.4.0.2`; new backend/tool pins are `scikit-build-core==1.0.3` and `ninja==1.13.2`. +- NumPy/SciPy pins: Python 3.10 uses `2.2.6`/`1.15.3`; Python 3.11 uses `2.4.6`/`1.17.1`; Python 3.12–3.13 use `2.5.3`/`1.17.1`. +- These runtime pins preserve the observed baseline. The old fixture installer replaces NumPy despite tox declaring `numpy~=1.20`; do not reproduce that dependency drift. +- No broad dependency upgrades, snapshot regeneration/reorganization, production fixes, new platform matrix, or persistent cross-environment native wheel cache. +- No push, PR, remote workflow execution, or publication. Remote acceptance remains explicitly unverified. +- Do not silently change an evaluation pin or weaken an assertion to accommodate a failure. Investigate, report, and obtain a scope decision when necessary. + +## File map and boundaries + +| File | Responsibility | +| --- | --- | +| `tests/native_support.py` (new) | Pure mode selection, origin validation, and runtime-version checks; no imports of native packages. | +| `tests/conftest.py` | Shared installed-generator guard, native fixture provenance/reuse evidence, mode parameterization. | +| `tests/unit/conftest.py` | Keep parser/fixture isolation; remove only the installed guard moved to the parent. | +| `tests/unit/test_native_support.py` (new) | Pure contracts plus a real subprocess pytest check of grouped-mode execution. | +| `tests/build_native.py` (new) | One explicitly targeted fixture wheel build/install, dependency checks, owned run directories, retained logs/payload evidence. | +| `tests/unit/test_build_native.py` (new) | Command boundaries, filesystem isolation, payload and failure contracts without a compiler. | +| `tests/py-demo/pyproject.toml` | Standard fixture backend and existing package metadata. | +| `tests/py-demo/bindings/CMakeLists.txt` | Explicit Python/pybind11 discovery and component-scoped extension installation. | +| `tests/py-demo/setup.py`, `tests/install-demo-module.sh` | Remove after their replacements are integrated and validated. | +| `tox.ini` | Single native profile/dependency/mode authority, local wheel packaging, installed pytest execution. | +| `tests/native_matrix.py` (new) | Validate expanded tox names and emit compact CI matrix JSON. | +| `tests/unit/test_native_matrix.py` (new) | Independent case inventory, adapter validation, real CLI failures. | +| `.github/workflows/ci.yml` | Derive native matrix, invoke tox with the downloaded wheel, retain early and late failure diagnostics. | +| `tests/README.md` | Commands, renamed profiles, dependency policy, serial updates, and verification boundaries. | + +No new `__init__.py` files in test/support directories. Do not refactor `tests/snapshot_helpers.py` or rewrite the native test bodies: parameterize their existing `demo_case` fixture. + +## Common verification and evidence + +Use an ignored workspace belonging to this plan only: + +```sh +E="$(pwd)/.superpowers/sdd/2026-09-25-test-harness-phase-three" +git check-ignore -q "$E" +mkdir -p "$E" +``` + +Set `E` in each shell invocation that uses it; tool calls do not share shell variables. After building a generator wheel, save its absolute path in `$E/generator-wheel.path` and reload `WHEEL` from that file in later commands. Keep logs, disposable source copies, command/status records, and per-task reports there. Do not read or reuse another plan's scratch helpers. Capture before/after reference and index states for every native verification batch. Intentional commits change the index between batches; comparisons are within each batch, not against the pre-implementation index forever. + +Fast checks, repeated on both endpoint interpreters during each task: + +```sh +for version in 3.10 3.13; do + env -u VIRTUAL_ENV uv run --no-project --isolated --python "$version" \ + --with 'pytest>=8,<9' python -m pytest tests/unit tests/test_snapshot_helpers.py -q +done +``` + +Use `--isolated`: `--no-project` alone can reuse a worktree environment. Native tools are bootstrap dependencies of the orchestrator, not dependencies of the compiler-free suite: + +```sh +env -u VIRTUAL_ENV uv run --no-project --isolated --with tox --with tox-uv tox --version +``` + +Planning inspected tox 4.64.2/tox-uv 1.36.0. The production adapter uses the longstanding `tox list -d --no-desc` interface, not private tox APIs or its newer JSON configuration serializer. `tox config --format json` below is a verification aid; use the inspected tox version for those checks if an older local tool lacks it. + +At each task's end, run applicable pre-commit hooks, `git diff --check`, inspect scope, self-review, and commit only the task's files. Include focused/cumulative results, expected negative-control failures, and limits in its report. A setup/import failure before implementation is not sufficient sensitivity evidence: exercise a broken behavioral path too. + +### Native batch audit helper (ignored evidence, not shipped support) + +Create `$E/audit_native.py` before the first baseline run. It audits any command after `--`; it is not an alternate native runner or matrix source. + +```python +import argparse +import hashlib +import json +import os +from pathlib import Path +import subprocess +import time + +p = argparse.ArgumentParser() +p.add_argument("--label", required=True) +p.add_argument("command", nargs=argparse.REMAINDER) +a = p.parse_args() +command = a.command[1:] if a.command[:1] == ["--"] else a.command +if not command or not a.label.replace("-", "").isalnum(): + p.error("provide a simple label and a command after --") +root = Path.cwd() +out = root / ".superpowers/sdd/2026-09-25-test-harness-phase-three" +out.mkdir(parents=True, exist_ok=True) + +def state(): + entries = {} + def visit(path): + key = path.relative_to(root).as_posix() + if path.is_symlink(): + entries[key] = ["link", os.readlink(path)] + elif path.is_dir(): + entries[key] = ["directory"] + for child in sorted(path.iterdir()): + visit(child) + else: + entries[key] = ["file", hashlib.sha256(path.read_bytes()).hexdigest()] + for name in ("tests/stubs", "tests/errors"): + visit(root / name) + entries["INDEX"] = subprocess.check_output( + ["git", "ls-files", "--stage", "-z"] + ).decode() + return entries + +before = state() +env = dict(os.environ) +env.pop("VIRTUAL_ENV", None) +env["UV_LINK_MODE"] = "copy" +start = time.monotonic() +status = None +error = None +try: + with (out / (a.label + ".log")).open("xb") as log: + status = subprocess.run( + command, env=env, stdout=log, stderr=subprocess.STDOUT, timeout=3600 + ).returncode +except Exception as exc: + error = repr(exc) +after = state() +report = { + "head": subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip(), + "command": command, "returncode": status, "error": error, + "seconds": time.monotonic() - start, "unchanged": before == after, + "before": before, "after": after, +} +(out / (a.label + ".json")).write_text(json.dumps(report, indent=2) + "\n") +assert error is None and status == 0 and before == after, report["error"] or a.label +``` + +Use unique labels; existing logs must not be overwritten. Read the actual test summaries and case inventories as well as the audit result. + +--- + +### Task 1: Grouped-mode pytest contracts and shared installed provenance + +**Files:** +- Create: `tests/native_support.py`, `tests/unit/test_native_support.py`. +- Modify: `tests/conftest.py`, `tests/unit/conftest.py`. + +**Interfaces:** +- Consumes: `snapshot_helpers.NUMPY_FORMATS`, `make_case(repo, python_version, branch, numpy_format)`; existing native tests request `demo_case`. +- Produces: `select_modes(explicit: str | None, defaults: str | None) -> tuple[str, ...]`; `require_origin(name: str, origin: str | None, prefix: Path) -> Path`; `check_versions(expected: Mapping[str, str], actual: Mapping[str, str]) -> None`; `runtime_pins(environ: Mapping[str, str]) -> dict[str, str]`. +- Environment contract: `STUBGEN_NUMPY_FORMATS` is whitespace-separated default modes; `STUBGEN_NUMPY_VERSION` and `STUBGEN_SCIPY_VERSION` are either both present or both absent. CLI `--numpy-format` overrides defaults. `STUBGEN_TEST_INSTALLED=1` enables the parent installed-generator guard. +- Produces native JUnit properties for extension path/hash, interpreter, and runtime versions; no new writes to caller-supplied artifact directories. + +- [ ] **Step 1: Capture the unchanged baseline before editing test support.** + +Verify a clean worktree and the spec's preservation anchor. Run the fast selection on 3.10/3.13 and the existing 13 native profiles serially: + +```sh +python "$E/audit_native.py" --label baseline -- \ + uv run --no-project --isolated --with tox --with tox-uv tox +``` + +Expected: 13 native environments, 99 tests each, both native comparisons in every environment, unchanged references/index. Record actual NumPy/SciPy and CMake versions with each environment's `importlib.metadata.version`; compare them to the spec's tables. Investigate baseline failures rather than starting the migration over them. + +- [ ] **Step 2: Write the pure contract and actual pytest-execution tests first.** + +Use this test core in `tests/unit/test_native_support.py`: + +```python +import os +from pathlib import Path +import shutil +import subprocess +import sys +import xml.etree.ElementTree as ET + +import pytest + +from native_support import check_versions, require_origin, runtime_pins, select_modes + +A = "numpy-array-wrap-with-annotated" +T = "numpy-array-use-type-var" + + +def test_modes_preserve_order_and_cli_overrides(): + assert select_modes(None, f"{A} {T}") == (A, T) + assert select_modes(T, f"{A} {T}") == (T,) + assert select_modes(A, None) == (A,) + + +@pytest.mark.parametrize("value", [None, "", "unknown", f"{A} {A}"]) +def test_invalid_defaults_are_errors_not_empty_parameters(value): + with pytest.raises(ValueError): + select_modes(None, value) + + +def test_invalid_explicit_mode(): + with pytest.raises(ValueError): + select_modes("unknown", A) + + +def test_origin_checks_path_components_and_missing_origins(tmp_path): + prefix = tmp_path / "env" + origin = prefix / "lib" / "module.py" + assert require_origin("module", str(origin), prefix) == origin + for outside in (None, str(tmp_path / "env-other" / "module.py")): + with pytest.raises(RuntimeError, match="module"): + require_origin("module", outside, prefix) + + +def test_runtime_pins_and_drift(): + env = {"STUBGEN_NUMPY_VERSION": "2.2.6", "STUBGEN_SCIPY_VERSION": "1.15.3"} + expected = {"numpy": "2.2.6", "scipy": "1.15.3"} + assert runtime_pins(env) == expected + assert runtime_pins({}) == {} + check_versions(expected, expected) + with pytest.raises(ValueError): + runtime_pins({"STUBGEN_NUMPY_VERSION": "2.2.6"}) + for actual in ({}, {**expected, "numpy": "1.26.4"}): + with pytest.raises(RuntimeError, match="numpy"): + check_versions(expected, actual) + + +@pytest.mark.parametrize("explicit, expected_count", [(None, 2), (T, 1)]) +def test_real_pytest_runs_remaining_mode_after_failure(tmp_path, explicit, expected_count): + tests_root = Path(__file__).resolve().parents[1] + for name in ("conftest.py", "snapshot_helpers.py", "native_support.py"): + shutil.copyfile(tests_root / name, tmp_path / name) + (tmp_path / "test_modes.py").write_text( + "import pytest\n" + "@pytest.fixture\n" + "def demo_case(request): return request.param\n" + "def test_mode(demo_case):\n" + f" assert demo_case != {A!r}\n" + ) + env = {k: v for k, v in os.environ.items() if not k.startswith("STUBGEN_")} + env.pop("PYTEST_ADDOPTS", None) + env["STUBGEN_NUMPY_FORMATS"] = f"{A} {T}" + env["PYTEST_DISABLE_PLUGIN_AUTOLOAD"] = "1" + command = [sys.executable, "-I", "-m", "pytest", "test_modes.py", "-q", + "--confcutdir", str(tmp_path), "--junitxml", "result.xml"] + if explicit: + command += ["--numpy-format", explicit] + result = subprocess.run(command, cwd=tmp_path, env=env, capture_output=True, + text=True, timeout=30) + assert result.returncode == (1 if explicit is None else 0), result.stdout + result.stderr + cases = ET.parse(tmp_path / "result.xml").findall(".//testcase") + assert len(cases) == expected_count + assert [c.attrib["name"] for c in cases] == ( + [f"test_mode[{A}]", f"test_mode[{T}]"] if explicit is None else [f"test_mode[{T}]"] + ) + assert sum(c.find("failure") is not None for c in cases) == (explicit is None) + assert not any(c.find("skipped") is not None for c in cases) +``` + +- [ ] **Step 3: Run the new file and observe missing-support failure, then implement the pure helper.** + +```sh +env -u VIRTUAL_ENV uv run --no-project --isolated --python 3.10 \ + --with 'pytest>=8,<9' python -m pytest tests/unit/test_native_support.py -q +``` + +Create `tests/native_support.py` with these contracts (stdlib plus the existing stdlib-only snapshot helper): + +```python +from collections.abc import Mapping +from pathlib import Path + +from snapshot_helpers import NUMPY_FORMATS + + +def select_modes(explicit: str | None, defaults: str | None) -> tuple[str, ...]: + modes = (explicit,) if explicit is not None else tuple((defaults or "").split()) + if not modes or any(mode not in NUMPY_FORMATS for mode in modes): + raise ValueError("Native tests require --numpy-format or valid STUBGEN_NUMPY_FORMATS") + if len(set(modes)) != len(modes): + raise ValueError("Duplicate NumPy modes") + return modes + + +def require_origin(name: str, origin: str | None, prefix: Path) -> Path: + prefix = prefix.resolve() + if origin is None or not Path(origin).resolve().is_relative_to(prefix): + raise RuntimeError(f"{name} must be installed under {prefix}; found {origin}") + return Path(origin).resolve() + + +def check_versions(expected: Mapping[str, str], actual: Mapping[str, str]) -> None: + for name, version in expected.items(): + if actual.get(name) != version: + raise RuntimeError(f"Dependency drift: {name}: expected {version}, found {actual.get(name)}") + + +def runtime_pins(environ: Mapping[str, str]) -> dict[str, str]: + result = { + name: environ[key] + for name, key in (("numpy", "STUBGEN_NUMPY_VERSION"), ("scipy", "STUBGEN_SCIPY_VERSION")) + if key in environ + } + if len(result) == 1 or any(not version for version in result.values()): + raise ValueError("Set both NumPy and SciPy pins, or neither for a direct rerun") + return result +``` + +- [ ] **Step 4: Integrate the helpers into the existing pytest fixtures.** + +Move the installed guard from `tests/unit/conftest.py` into the root conftest; remove its now-unused `os` import from the unit conftest, leaving all parser/import-isolation behavior untouched. Add these imports to the parent and retain its existing imports: + +```python +import hashlib +import importlib +import importlib.metadata +import os + +from native_support import check_versions, require_origin, runtime_pins, select_modes +``` + +Keep the existing options, update/artifact/report fixtures. Replace only `demo_case` and add the following hooks/fixtures: + +```python +def pytest_generate_tests(metafunc): + if "demo_case" not in metafunc.fixturenames: + return + try: + modes = select_modes( + metafunc.config.getoption("numpy_format"), + os.environ.get("STUBGEN_NUMPY_FORMATS"), + ) + except ValueError as exc: + raise pytest.UsageError(str(exc)) from exc + metafunc.parametrize("demo_case", modes, indirect=True, ids=modes) + + +@pytest.fixture(scope="session", autouse=True) +def enforce_installed_origin(): + if os.environ.get("STUBGEN_TEST_INSTALLED") != "1": + return + importlib.import_module("pybind11_stubgen") + for name, module in list(sys.modules.items()): + if name == "pybind11_stubgen" or name.startswith("pybind11_stubgen."): + require_origin(name, getattr(module, "__file__", None), Path(sys.prefix)) + + +@pytest.fixture(scope="session") +def installed_native(record_testsuite_property): + prefix = Path(sys.prefix) + demo = importlib.import_module("demo") + extension = importlib.import_module("demo._bindings") + require_origin("demo", demo.__file__, prefix) + path = require_origin("demo._bindings", extension.__file__, prefix) + expected = runtime_pins(os.environ) + before = {name: importlib.metadata.version(name) for name in ("numpy", "scipy")} + check_versions(expected, before) + digest = hashlib.sha256(path.read_bytes()).hexdigest() + record_testsuite_property("native_extension", str(path)) + record_testsuite_property("native_extension_sha256", digest) + record_testsuite_property("native_python", f"{sys.version_info.major}.{sys.version_info.minor}") + for name, version in before.items(): + record_testsuite_property(name, version) + yield + after = {name: importlib.metadata.version(name) for name in before} + check_versions(before, after) + check_versions(expected, after) + assert hashlib.sha256(path.read_bytes()).hexdigest() == digest, path + + +@pytest.fixture +def demo_case(request, pytestconfig, installed_native): + return make_case( + Path(__file__).resolve().parents[1], sys.version_info[:2], + pytestconfig.getoption("pybind11_branch"), request.param, + ) +``` + +The runtime pin environment is intentionally optional for an explicit direct rerun and old-profile transition; tox will always provide both pins after Task 3. The native module checks do not require every pybind11-created submodule to have `__file__`; validate the package and actual extension, not virtual C++ submodules. + +- [ ] **Step 5: Verify sensitivity and compatibility, then commit.** + +Run the focused file and cumulative fast selection on 3.10/3.13. The synthetic two-mode test must produce one failure and one pass internally while its outer regression test passes. Run this process-local negative control with isolated uv supplying pytest, then rerun the unpatched focused file: + +```python +import sys +from pathlib import Path +from unittest.mock import patch + +sys.path.insert(0, str(Path("tests").resolve())) +import native_support +import pytest + +original = native_support.select_modes +with patch.object(native_support, "select_modes", side_effect=lambda e, d: original(e, d)[:1]): + code = pytest.main(["tests/unit/test_native_support.py::test_modes_preserve_order_and_cli_overrides", "-q"]) +assert code == pytest.ExitCode.TESTS_FAILED, code +``` + +Run at least `py313-pb30-naa` and `py313-pb29-naa` through the unchanged legacy installer using the batch audit. They still receive the old single-mode CLI option. Do not rename environments or alter dependencies in this task. + +```sh +git add tests/native_support.py tests/conftest.py tests/unit/conftest.py tests/unit/test_native_support.py +git commit -m "test: parameterize native modes and share installed-origin guards" +``` + +--- + +### Task 2: Isolated fixture-wheel build helper and its failure contracts + +**Files:** +- Create: `tests/build_native.py`, `tests/unit/test_build_native.py`. + +**Interfaces:** +- Consumes: `native_support.check_versions`, `require_origin` from Task 1. +- Produces: `expected_versions(environ) -> dict[str, str]`; `new_run_dir(prefix: Path, repo: Path) -> Path`; `build_command(python: str, source: Path, run_dir: Path, pins: dict[str, str], pybind_dir: Path, eigen_dir: Path) -> list[str]`; `install_command(python: str, wheel: Path) -> list[str]`; `run_logged(command: list[str], *, cwd: Path, log: Path, env: dict[str, str], timeout: float = 600) -> None`; `audit_wheel(wheel: Path, package_source: Path) -> None`; `main() -> int`. +- CLI: `{envpython} tests/build_native.py` builds the adjacent fixture using that interpreter's installed tools. It never installs dependency packages itself. +- Required version environment variables: `STUBGEN_` + uppercased distribution name with `-` replaced by `_` + `_VERSION`, for `numpy`, `scipy`, `pybind11`, `cmake`, `ninja`, `scikit-build-core`, `cmeel`, `cmeel-eigen`. +- Persistent per-invocation evidence: `/tmp/native/run-*/evidence.json`, `build.log`, `install.log`, `wheel/*.whl`, `cmake/`. There is no cleanup operation and no shared source-tree build directory. + +- [ ] **Step 1: Add real failure/path/payload tests before the helper exists.** + +Create `tests/unit/test_build_native.py` with these tests; imports come from `build_native` and `importlib.machinery.EXTENSION_SUFFIXES`: + +```python +import json +import os +import sys +import zipfile +from importlib.machinery import EXTENSION_SUFFIXES + +import pytest + +from build_native import ( + audit_wheel, build_command, expected_versions, install_command, + new_run_dir, run_logged, +) + +PINS = {"numpy": "2.2.6", "scipy": "1.15.3", "pybind11": "2.9.2", + "cmake": "3.31.10", "ninja": "1.13.2", "scikit-build-core": "1.0.3", + "cmeel": "0.59.0", "cmeel-eigen": "3.4.0.2"} + + +def test_explicit_versions_are_required(): + env = {"STUBGEN_" + k.upper().replace("-", "_") + "_VERSION": v for k, v in PINS.items()} + assert expected_versions(env) == PINS + del env["STUBGEN_NUMPY_VERSION"] + with pytest.raises(RuntimeError, match="STUBGEN_NUMPY_VERSION"): + expected_versions(env) + + +def test_run_directories_are_owned_unique_and_not_source(tmp_path): + repo = tmp_path / "repo" + prefix = tmp_path / "env" + first = new_run_dir(prefix, repo) + second = new_run_dir(prefix, repo) + other = new_run_dir(tmp_path / "env-other", repo) + assert first != second != other + assert first.is_relative_to(prefix / "tmp/native") + assert other.is_relative_to(tmp_path / "env-other/tmp/native") + with pytest.raises(RuntimeError): + new_run_dir(repo, repo) + with pytest.raises(RuntimeError): + new_run_dir(repo / "tests/py-demo", repo) + linked = tmp_path / "linked-env" + linked.mkdir() + outside = tmp_path / "outside" + outside.mkdir() + (linked / "tmp").symlink_to(outside, target_is_directory=True) + with pytest.raises(RuntimeError): + new_run_dir(linked, repo) + assert list(outside.iterdir()) == [] + + +def test_commands_target_interpreter_tools_and_wheel_without_dependency_resolution(tmp_path): + source, run = tmp_path / "fixture", tmp_path / "run" + pb, eigen = tmp_path / "pb", tmp_path / "eigen" + command = build_command("/env/bin/python", source, run, PINS, pb, eigen) + assert command[:8] == ["uv", "build", "--wheel", "--no-build-isolation", "--no-cache", "--python", "/env/bin/python", "--out-dir"] + assert f"cmake.define.pybind11_DIR={pb}" in command + assert f"cmake.define.Eigen3_DIR={eigen}" in command + assert "cmake.define.STUBGEN_PYBIND11_VERSION=2.9.2" in command + assert "cmake.version===3.31.10" in command + assert "ninja.version===1.13.2" in command + assert f"build-dir={run / 'cmake'}" in command + assert command[-1] == str(source) + wheel = run / "wheel/demo.whl" + assert install_command("/env/bin/python", wheel) == [ + "uv", "pip", "install", "--python", "/env/bin/python", "--no-deps", + "--reinstall-package", "py-demo", "--no-cache", str(wheel), + ] + + +@pytest.mark.parametrize("status", [0, 7]) +def test_real_process_status_and_output_are_retained(tmp_path, status): + command = [sys.executable, "-c", f"print('process-output'); raise SystemExit({status})"] + log = tmp_path / "process.log" + if status: + with pytest.raises(RuntimeError, match="exit 7"): + run_logged(command, cwd=tmp_path, log=log, env=dict(os.environ)) + else: + run_logged(command, cwd=tmp_path, log=log, env=dict(os.environ)) + lines = log.read_text().splitlines() + assert json.loads(lines[0]) == command + assert lines[-1] == "process-output" + + +def test_timeout_and_log_creation_failure_are_errors(tmp_path): + with pytest.raises(RuntimeError, match="timed out"): + run_logged([sys.executable, "-c", "import time; time.sleep(2)"], + cwd=tmp_path, log=tmp_path / "timeout.log", env=dict(os.environ), timeout=0.05) + assert (tmp_path / "timeout.log").is_file() + blocked = tmp_path / "blocked.log" + blocked.mkdir() + sentinel = tmp_path / "ran" + with pytest.raises(OSError): + run_logged([sys.executable, "-c", f"from pathlib import Path; Path({str(sentinel)!r}).touch()"], + cwd=tmp_path, log=blocked, env=dict(os.environ)) + assert not sentinel.exists() + + +@pytest.mark.parametrize("extra", [None, "lib/libdemo.a", "include/demo/Foo.h", "build/lib/stale.py", "demo/unrelated.txt"]) +def test_wheel_payload_is_exactly_package_extension_and_metadata(tmp_path, extra): + package = tmp_path / "demo" + package.mkdir() + (package / "__init__.py").write_bytes(b"VALUE = 1\n") + wheel = tmp_path / "fixture.whl" + with zipfile.ZipFile(wheel, "w") as archive: + archive.writestr("demo/__init__.py", b"VALUE = 1\n") + archive.writestr("demo/_bindings" + EXTENSION_SUFFIXES[0], b"test payload, not imported") + archive.writestr("py_demo-0.0.0.dist-info/METADATA", b"Name: py-demo\n") + if extra: + archive.writestr(extra, b"unwanted") + if extra: + with pytest.raises(RuntimeError, match="payload"): + audit_wheel(wheel, package) + else: + audit_wheel(wheel, package) + (package / "__init__.py").write_bytes(b"VALUE = 2\n") + with pytest.raises(RuntimeError): + audit_wheel(wheel, package) +``` + +- [ ] **Step 2: Observe missing-helper failure, then implement the helper's core.** + +Use the common isolated pytest command on this focused file. Create `tests/build_native.py` with stdlib imports only at import time (`hashlib`, `importlib.metadata`, `importlib.machinery`, `json`, `os`, `pathlib.Path`, `subprocess`, `sys`, `tempfile`, `zipfile`, and `native_support`). Its core is: + +```python +import hashlib +import importlib.machinery +import importlib.metadata +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import zipfile + +from native_support import check_versions, require_origin + +PACKAGES = ("numpy", "scipy", "pybind11", "cmake", "ninja", "scikit-build-core", "cmeel", "cmeel-eigen") + + +def expected_versions(environ): + pins = {} + for name in PACKAGES: + key = "STUBGEN_" + name.upper().replace("-", "_") + "_VERSION" + if not environ.get(key): + raise RuntimeError(f"{key} must be set by tox") + pins[name] = environ[key] + return pins + + +def new_run_dir(prefix: Path, repo: Path) -> Path: + prefix, repo = prefix.resolve(), repo.resolve() + if repo.is_relative_to(prefix) or prefix.is_relative_to(repo / "tests"): + raise RuntimeError("Native build prefix overlaps source roots") + parent = prefix / "tmp/native" + require_origin("native build directory", str(parent), prefix) + parent.mkdir(parents=True, exist_ok=True) + return Path(tempfile.mkdtemp(prefix="run-", dir=parent)) + + +def build_command(python, source, run_dir, pins, pybind_dir, eigen_dir): + command = ["uv", "build", "--wheel", "--no-build-isolation", "--no-cache", + "--python", python, "--out-dir", str(run_dir / "wheel")] + settings = { + "build-dir": str(run_dir / "cmake"), + "cmake.version": "==" + pins["cmake"], + "ninja.version": "==" + pins["ninja"], + "ninja.make-fallback": "false", + "cmake.define.Python_EXECUTABLE": python, + "cmake.define.pybind11_DIR": str(pybind_dir), + "cmake.define.Eigen3_DIR": str(eigen_dir), + "cmake.define.STUBGEN_PYBIND11_VERSION": pins["pybind11"], + } + for key, value in settings.items(): + command += ["--config-setting", f"{key}={value}"] + return command + [str(source)] + + +def install_command(python, wheel): + return ["uv", "pip", "install", "--python", python, "--no-deps", + "--reinstall-package", "py-demo", "--no-cache", str(wheel)] + + +def run_logged(command, *, cwd, log, env, timeout=600): + print(f"Running {command!r}; log: {log}", flush=True) + try: + with log.open("xb") as stream: + stream.write((json.dumps(command) + "\n").encode()) + stream.flush() + result = subprocess.run(command, cwd=cwd, env=env, stdout=stream, + stderr=subprocess.STDOUT, timeout=timeout) + except subprocess.TimeoutExpired as exc: + raise RuntimeError(f"Command timed out after {timeout}s; log: {log}; {command!r}") from exc + if result.returncode: + tail = "\n".join(log.read_text(errors="replace").splitlines()[-30:]) + raise RuntimeError(f"Command exit {result.returncode}; log: {log}; {command!r}\n{tail}") + + +def audit_wheel(wheel, package_source): + expected = {"demo/" + p.relative_to(package_source).as_posix(): p.read_bytes() + for p in package_source.rglob("*.py")} + with zipfile.ZipFile(wheel) as archive: + names = archive.namelist() + if len(names) != len(set(names)) or any( + not n.startswith(("demo/", "py_demo-0.0.0.dist-info/")) + or n.endswith((".a", ".lib", ".h", ".hpp", ".pyc")) for n in names + ): + raise RuntimeError("Unexpected fixture wheel payload") + actual = {n: archive.read(n) for n in names if n.endswith(".py")} + extension_names = {"demo/_bindings" + suffix for suffix in importlib.machinery.EXTENSION_SUFFIXES} + extensions = [n for n in names if n in extension_names] + payload = {n for n in names if not n.startswith("py_demo-0.0.0.dist-info/")} + if actual != expected or len(extensions) != 1 or payload != set(expected) | set(extensions): + raise RuntimeError("Fixture wheel payload differs from source package/extension contract") +``` + +Use the following `main` flow, with `if __name__ == "__main__": raise SystemExit(main())`. It must not execute at import time: + +```python +def main(): + repo = Path(__file__).resolve().parents[1] + source = repo / "tests/py-demo" + run_dir = None + evidence = {"python": sys.executable, "prefix": sys.prefix, "phase": "prepare"} + try: + run_dir = new_run_dir(Path(sys.prefix), repo) + print(f"Native fixture evidence: {run_dir}", flush=True) + pins = expected_versions(os.environ) + before = {name: importlib.metadata.version(name) for name in PACKAGES} + evidence.update(pins=pins, before=before) + check_versions(pins, before) + for name in PACKAGES: + location = importlib.metadata.distribution(name).locate_file("") + require_origin(name, str(location), Path(sys.prefix)) + pybind_dir = Path(importlib.metadata.distribution("pybind11").locate_file("pybind11/share/cmake/pybind11")) + eigen_dir = Path(importlib.metadata.distribution("cmeel-eigen").locate_file("cmeel.prefix/share/eigen3/cmake")) + for name, path, config in (("pybind11", pybind_dir, "pybind11Config.cmake"), + ("Eigen", eigen_dir, "Eigen3Config.cmake")): + require_origin(name, str(path), Path(sys.prefix)) + if not (path / config).is_file(): + raise RuntimeError(f"Missing {name} CMake configuration: {path}") + evidence.update(pins=pins, before=before, pybind_dir=str(pybind_dir), eigen_dir=str(eigen_dir)) + env = dict(os.environ) + for key in list(env): + if key.startswith("SKBUILD_") or key in ("CMAKE_ARGS", "CMAKE_PREFIX_PATH", "CMAKE_TOOLCHAIN_FILE"): + env.pop(key) + env["CMAKE_BUILD_PARALLEL_LEVEL"] = "2" + evidence["phase"] = "build" + (run_dir / "evidence.json").write_text(json.dumps(evidence, indent=2) + "\n") + run_logged(build_command(sys.executable, source, run_dir, pins, pybind_dir, eigen_dir), + cwd=repo, log=run_dir / "build.log", env=env) + wheels = list((run_dir / "wheel").glob("*.whl")) + if len(wheels) != 1: + raise RuntimeError(f"Expected one new fixture wheel, found {wheels}") + wheel = wheels[0] + evidence["phase"] = "wheel payload" + audit_wheel(wheel, source / "demo") + evidence.update(wheel=str(wheel), wheel_sha256=hashlib.sha256(wheel.read_bytes()).hexdigest()) + evidence["phase"] = "install" + run_logged(install_command(sys.executable, wheel), cwd=repo, + log=run_dir / "install.log", env=env) + after = {name: importlib.metadata.version(name) for name in PACKAGES} + check_versions(pins, after) + evidence.update(after=after, phase="complete") + (run_dir / "evidence.json").write_text(json.dumps(evidence, indent=2) + "\n") + return 0 + except (OSError, RuntimeError, ValueError, importlib.metadata.PackageNotFoundError, zipfile.BadZipFile) as exc: + print(f"Native fixture {evidence['phase']} failed: {exc}", file=sys.stderr) + if run_dir is not None: + evidence["error"] = str(exc) + try: + (run_dir / "evidence.json").write_text(json.dumps(evidence, indent=2) + "\n") + except OSError as report_error: + print(f"Could not save evidence: {report_error}", file=sys.stderr) + return 1 +``` + +This fresh-per-invocation design deliberately trades incremental reuse across tox runs for simpler freshness guarantees. It still performs exactly one fixture build per profile invocation and shares it across modes. Six-hundred-second native build/install limits are separate from, and do not alter, the existing snapshot subprocess limits. + +- [ ] **Step 3: Verify behavioral failures, then commit the independently testable helper.** + +Run focused/cumulative tests on 3.10/3.13. Confirm the real nonzero process, timeout, blocked log, unsafe path, drift, and unwanted payload checks fail for their intended reasons, not missing tools. Run this control with isolated uv supplying pytest, then rerun unpatched green. Do not invoke the helper against the old setuptools fixture yet: backend integration belongs to Task 3. + +```python +import sys +from pathlib import Path +from unittest.mock import patch + +sys.path.insert(0, str(Path("tests").resolve())) +import build_native +import pytest + +original = build_native.install_command +with patch.object(build_native, "install_command", side_effect=lambda p, w: [a for a in original(p, w) if a != "--no-deps"]): + code = pytest.main(["tests/unit/test_build_native.py::test_commands_target_interpreter_tools_and_wheel_without_dependency_resolution", "-q"]) +assert code == pytest.ExitCode.TESTS_FAILED, code +``` + +```sh +git add tests/build_native.py tests/unit/test_build_native.py +git commit -m "test: add isolated fixture wheel builder with retained diagnostics" +``` + +--- + +### Task 3: Standard fixture backend, pinned dependencies, and 11 native tox profiles + +**Files:** +- Modify: `tests/py-demo/pyproject.toml`, `tests/py-demo/bindings/CMakeLists.txt`, `tox.ini`. +- Delete: `tests/py-demo/setup.py`, `tests/install-demo-module.sh`. + +**Interfaces:** +- Consumes Task 1 mode/provenance/runtime environment contracts and Task 2 build CLI. +- Produces the 11 native profile names from the spec, managed local generator wheels, `--installpkg` support, per-profile JUnit at `{envtmpdir}/native.xml`, and fixture evidence beneath `{envtmpdir}/native/`. +- Keeps the explicit compiler-free unit environment section's commands/dependencies/setenv overrides unchanged. + +- [ ] **Step 1: Capture the missing-new-profile failure before changing configuration.** + +```sh +env -u VIRTUAL_ENV uv run --no-project --isolated --with tox --with tox-uv \ + tox list -d --no-desc > "$E/task-3-before.txt" +``` + +Run this assertion separately and retain the expected `AssertionError`: + +```python +from pathlib import Path +p = Path(".superpowers/sdd/2026-09-25-test-harness-phase-three/task-3-before.txt") +expected = ["py310-pb30", "py311-pb30", "py312-pb30", "py313-pb30", + "py310-pb213", "py311-pb213", "py312-pb213", "py313-pb213", + "py313-pb29", "py313-pb211", "py313-pb212"] +assert p.read_text().splitlines() == expected +``` + +The old list has 13 names with mode suffixes; this is the intended mismatch. Do not use an unknown CLI option or a failed native build as configuration red evidence. + +- [ ] **Step 2: Replace fixture metadata and add component-scoped installation.** + +Replace `tests/py-demo/pyproject.toml` with: + +```toml +[build-system] +requires = ["scikit-build-core>=1.0,<2"] +build-backend = "scikit_build_core.build" + +[project] +name = "py-demo" +version = "0.0.0" +description = "Demo package to test stub generation" +readme = "README.rst" +requires-python = ">=3.10" +authors = [{name = "Sergei Izmailov", email = "sergei.a.izmailov@gmail.com"}] +dependencies = ["numpy"] + +[project.urls] +Repository = "https://github.com/sizmailov/pybind11-project-example" + +[tool.scikit-build] +cmake.source-dir = "bindings" +wheel.packages = ["demo"] +install.components = ["python"] +``` + +In `tests/py-demo/bindings/CMakeLists.txt`, retain C++17, source glob, includes, library linkage, and the existing `add_subdirectory` relationship. Add explicit Eigen discovery before that subdirectory so its resolved version is available in the parent scope: + +```cmake +find_package(Eigen3 3.4.0 EXACT CONFIG REQUIRED) +``` + +Replace its pybind11 discovery with: + +```cmake +if(NOT DEFINED STUBGEN_PYBIND11_VERSION) + message(FATAL_ERROR "Build the fixture through tox to select pybind11 explicitly") +endif() +find_package(Python COMPONENTS Interpreter Development.Module REQUIRED) +find_package(pybind11 ${STUBGEN_PYBIND11_VERSION} EXACT CONFIG REQUIRED) +message(STATUS "Fixture Python: ${Python_EXECUTABLE}") +message(STATUS "Fixture pybind11: ${pybind11_VERSION} at ${pybind11_DIR}") +message(STATUS "Fixture Eigen3: ${Eigen3_VERSION} at ${Eigen3_DIR}") +``` + +Append: + +```cmake +install(TARGETS _bindings + LIBRARY DESTINATION demo COMPONENT python + RUNTIME DESTINATION demo COMPONENT python +) +``` + +Both artifact clauses need `COMPONENT python`. The backend installs only this component, excluding the demo library's existing default-component exports/headers without changing its C++ source or removing its standalone CMake capabilities. + +Delete the fixture `setup.py` and old shell installer with the replacement local build route. The remaining CI caller is migrated in Task 4: this is an explicit transitional boundary, not a claim that the intermediate commit is CI-ready. Do not publish this partially integrated branch. + +- [ ] **Step 3: Replace the native tox configuration, preserving unit overrides.** + +Use this native configuration before the existing `[testenv:py{310,311,312,313}-unit]` section: + +```ini +[tox] +requires = tox>=4 +uv_mode = true +skip_missing_interpreters = false +env_list = + py{310,311,312,313}-pb30 + py{310,311,312,313}-pb213 + py313-pb29 + py313-pb211 + py313-pb212 + +[testenv] +description = Check installed demo stubs and errors across configured NumPy modes +package = wheel +wheel_build_env = .pkg +setenv = + PYTHONUNBUFFERED = 1 + STUBGEN_TEST_INSTALLED = 1 + STUBGEN_NUMPY_FORMATS = numpy-array-wrap-with-annotated + py313-pb{213,30}: STUBGEN_NUMPY_FORMATS = numpy-array-wrap-with-annotated numpy-array-use-type-var + pb29: PYBIND11_BRANCH = v2.9 + pb211: PYBIND11_BRANCH = v2.11 + pb212: PYBIND11_BRANCH = v2.12 + pb213: PYBIND11_BRANCH = v2.13 + pb30: PYBIND11_BRANCH = v3.0 + pb29: STUBGEN_PYBIND11_VERSION = 2.9.2 + pb211: STUBGEN_PYBIND11_VERSION = 2.11.2 + pb212: STUBGEN_PYBIND11_VERSION = 2.12.1 + pb213: STUBGEN_PYBIND11_VERSION = 2.13.6 + pb30: STUBGEN_PYBIND11_VERSION = 3.0.4 + pb29: STUBGEN_CMAKE_VERSION = 3.31.10 + pb{211,212}: STUBGEN_CMAKE_VERSION = 3.28.3 + pb{213,30}: STUBGEN_CMAKE_VERSION = 4.2.3 + py310: STUBGEN_NUMPY_VERSION = 2.2.6 + py311: STUBGEN_NUMPY_VERSION = 2.4.6 + py{312,313}: STUBGEN_NUMPY_VERSION = 2.5.3 + py310: STUBGEN_SCIPY_VERSION = 1.15.3 + py{311,312,313}: STUBGEN_SCIPY_VERSION = 1.17.1 + STUBGEN_CMEEL_VERSION = 0.59.0 + STUBGEN_CMEEL_EIGEN_VERSION = 3.4.0.2 + STUBGEN_SCIKIT_BUILD_CORE_VERSION = 1.0.3 + STUBGEN_NINJA_VERSION = 1.13.2 +allowlist_externals = uv +deps = + pytest>=8,<9 + typing_extensions>=4,<5 + cmeel=={env:STUBGEN_CMEEL_VERSION} + cmeel-eigen=={env:STUBGEN_CMEEL_EIGEN_VERSION} + numpy=={env:STUBGEN_NUMPY_VERSION} + scipy=={env:STUBGEN_SCIPY_VERSION} + pybind11=={env:STUBGEN_PYBIND11_VERSION} + cmake=={env:STUBGEN_CMAKE_VERSION} + scikit-build-core=={env:STUBGEN_SCIKIT_BUILD_CORE_VERSION} + ninja=={env:STUBGEN_NINJA_VERSION} +commands_pre = + {envpython} {toxinidir}/tests/build_native.py +commands = + {envpython} -I -m pytest {toxinidir}/tests/test_snapshot_helpers.py {toxinidir}/tests/test_demo_stubs.py {toxinidir}/tests/test_demo_errors.py --pybind11-branch {env:PYBIND11_BRANCH} --artifacts-dir {envtmpdir}/pytest-artifacts --junitxml {envtmpdir}/native.xml --maxfail=0 {posargs} +``` + +Remove the old global `skipsdist` setting: native tox now uses its wheel-package mechanism. The unit section still explicitly uses `package = skip` and its original uv installation command. Do not accidentally introduce native build dependencies or precommands there. + +Native profile names deliberately encode the interpreter that tox infers. Do not override `base_python` independently: CI derives its interpreter from this naming contract. The resolved configuration check below enforces agreement. + +- [ ] **Step 4: Inspect resolution, then evaluate the entire real native matrix.** + +Use `tox config` to verify all 11 environments' resolved `deps`, `set_env`, `base_python`, package mode, and commands, plus one unit environment's complete override. Confirm NumPy modes and exact pin tables independently, not by comparing a function with itself. + +First build `py313-pb29` and `py313-pb30` serially to exercise the oldest and newest families. If either fails, investigate and stop rather than silently selecting another backend/tool version. Then run all profiles with fresh tox environments using the audit: + +```sh +python "$E/audit_native.py" --label local-fresh -- \ + uv run --no-project --isolated --with tox --with tox-uv tox -r +``` + +Expected: 11 profile successes; nine profiles have 99 tests and the two dual-mode profiles have 101 tests, since only the existing 97 harness self-tests are selected natively. Both native test functions appear for every configured mode. New support tests run through the separate compiler-free selection. Record fixture wheel paths and `evidence.json`, inspect their payloads, CMake dependency locations, and unchanged before/after runtime pins. + +Run the existing four unit tox environments and cumulative source checks. Exercise `tox -e py313-pb30 -- --numpy-format numpy-array-use-type-var -k demo_stubs` as an explicitly filtered debugging check, separately from unfiltered acceptance. + +- [ ] **Step 5: Review source/reference preservation and commit the integrated local runner.** + +Confirm no old installer callers remain in tox. CI still needs migration in Task 4 and is not claimed operational at this intermediate commit. No native C++/Python fixtures or references should differ. + +```sh +git add tox.ini tests/py-demo/pyproject.toml tests/py-demo/bindings/CMakeLists.txt tests/py-demo/setup.py tests/install-demo-module.sh +git commit -m "test: build pinned native fixtures once per tox profile" +``` + +--- + +### Task 4: Derived CI matrix and the supplied-wheel execution path + +**Files:** +- Create: `tests/native_matrix.py`, `tests/unit/test_native_matrix.py`. +- Modify: `.github/workflows/ci.yml`. + +**Interfaces:** +- Consumes the expanded default native names from `tox list -d --no-desc`; consumes Task 3's `--installpkg` route, naming contract, and artifact locations. +- Produces `matrix_from_names(names: list[str]) -> dict[str, list[dict[str, str]]]`, returning `{"include": [{"env": "py313-pb30", "python": "3.13"}]}` for one input. +- CLI: `python tests/native_matrix.py DEFAULT_NAMES_FILE` emits one compact JSON line to stdout; invalid input returns nonzero and reports the error on stderr. +- CI build-job output `native-matrix` feeds `tests.strategy.matrix`; publication continues to depend on the existing `tests` job ID. + +- [ ] **Step 1: Write the independent inventory and invalid-input tests.** + +```python +import json +from pathlib import Path +import subprocess +import sys + +import pytest + +from native_matrix import matrix_from_names + +EXPECTED_NAMES = [ + "py310-pb30", "py311-pb30", "py312-pb30", "py313-pb30", + "py310-pb213", "py311-pb213", "py312-pb213", "py313-pb213", + "py313-pb29", "py313-pb211", "py313-pb212", +] + + +def test_explicit_profile_inventory_and_interpreters(): + matrix = matrix_from_names(EXPECTED_NAMES) + assert matrix == {"include": [ + {"env": "py310-pb30", "python": "3.10"}, + {"env": "py311-pb30", "python": "3.11"}, + {"env": "py312-pb30", "python": "3.12"}, + {"env": "py313-pb30", "python": "3.13"}, + {"env": "py310-pb213", "python": "3.10"}, + {"env": "py311-pb213", "python": "3.11"}, + {"env": "py312-pb213", "python": "3.12"}, + {"env": "py313-pb213", "python": "3.13"}, + {"env": "py313-pb29", "python": "3.13"}, + {"env": "py313-pb211", "python": "3.13"}, + {"env": "py313-pb212", "python": "3.13"}, + ]} + + +@pytest.mark.parametrize("names", [[], ["py313-pb30"] * 2, ["py313-unit"], + ["py39-pb30"], ["py313-pb99"], ["py313-pb30-naa"], ["py313-pb300"], + ["py313-pb209"], [""], [" py313-pb30"]]) +def test_rejects_missing_duplicate_or_unsupported_profiles(names): + with pytest.raises(ValueError): + matrix_from_names(names) + + +def test_cli_json_and_nonzero_failure(tmp_path): + script = Path(__file__).resolve().parents[1] / "native_matrix.py" + names = tmp_path / "names.txt" + names.write_text("py313-pb30\n") + result = subprocess.run([sys.executable, str(script), str(names)], + capture_output=True, text=True, timeout=30) + assert result.returncode == 0, result.stderr + assert json.loads(result.stdout) == {"include": [{"env": "py313-pb30", "python": "3.13"}]} + names.write_text("py313-pb30\npy313-pb30\n") + result = subprocess.run([sys.executable, str(script), str(names)], + capture_output=True, text=True, timeout=30) + assert result.returncode != 0 + assert result.stdout == "" + assert "Duplicate" in result.stderr +``` + +- [ ] **Step 2: Observe missing-adapter failure, then implement the small translator.** + +```python +import argparse +import json +from pathlib import Path +import re +import sys + +from snapshot_helpers import BRANCHES + + +def matrix_from_names(names): + if not names: + raise ValueError("No default native environments") + if len(names) != len(set(names)): + raise ValueError("Duplicate native environments") + rows = [] + for name in names: + match = re.fullmatch(r"py(3)(1[0-3])-pb([23])(0|[1-9]\d?)", name) + if match is None or f"v{match[3]}.{int(match[4])}" not in BRANCHES: + raise ValueError(f"Unsupported native environment: {name!r}") + rows.append({"env": name, "python": f"{match[1]}.{match[2]}"}) + return {"include": rows} + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("names_file", type=Path) + args = parser.parse_args() + try: + matrix = matrix_from_names(args.names_file.read_text().splitlines()) + except (OSError, ValueError) as exc: + print(str(exc), file=sys.stderr) + return 1 + print(json.dumps(matrix, separators=(",", ":"))) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) +``` + +The regex validates the approved naming schema/interpreter range; `BRANCHES` is the existing reference-series vocabulary. Neither defines the execution list or duplicates dependency pins. Do not parse raw tox brace syntax or add a tox plugin dependency to the adapter/tests. + +- [ ] **Step 3: Validate the actual tox-derived inventory before wiring CI.** + +```sh +env -u VIRTUAL_ENV uv run --no-project --isolated --with tox --with tox-uv \ + tox list -d --no-desc > "$E/default-native.txt" +python tests/native_matrix.py "$E/default-native.txt" > "$E/native-matrix.json" +env -u VIRTUAL_ENV uv run --no-project --isolated --with 'tox==4.64.2' --with 'tox-uv==1.36.0' \ + tox config --format json -k env_name base_python set_env -o "$E/resolved-native.json" +``` + +Use the following independent check. Tox's resolved configuration can include its packaging environment; only the explicit expected native names are part of the matrix. + +```python +import json +from pathlib import Path +E = Path(".superpowers/sdd/2026-09-25-test-harness-phase-three") +expected = ["py310-pb30", "py311-pb30", "py312-pb30", "py313-pb30", + "py310-pb213", "py311-pb213", "py312-pb213", "py313-pb213", + "py313-pb29", "py313-pb211", "py313-pb212"] +rows = json.loads((E / "native-matrix.json").read_text())["include"] +resolved = json.loads((E / "resolved-native.json").read_text())["env"] +assert [r["env"] for r in rows] == expected +cases = [] +for row in rows: + item = resolved[row["env"]] + assert item["base_python"] == [row["env"].split("-")[0]] + env = item["set_env"] + modes = env["STUBGEN_NUMPY_FORMATS"].split() + wanted = ["numpy-array-wrap-with-annotated"] + if row["env"] in ("py313-pb30", "py313-pb213"): + wanted += ["numpy-array-use-type-var"] + assert modes == wanted + cases.extend((row["python"], env["PYBIND11_BRANCH"], mode) for mode in modes) +assert len(cases) == len(set(cases)) == 13 +``` + +Also run list/translation with a fresh `--workdir` and `UV_PYTHON_DOWNLOADS=never`. It must not create fixture environments, compile code, or require discovering every matrix interpreter. The list command, unlike full `tox config`, does not materialize packaging/run environments. + +- [ ] **Step 4: Replace only native CI orchestration and expose the derived matrix.** + +Add this output to the existing `build` job: + +```yaml + outputs: + native-matrix: ${{ steps.native-matrix.outputs.matrix }} +``` + +After its existing uv setup, add: + +```yaml + - name: Derive native test matrix from tox + id: native-matrix + shell: bash + run: | + set -euo pipefail + uv run --no-project --isolated --with tox --with tox-uv \ + tox list -d --no-desc > "$RUNNER_TEMP/native-envs.txt" + matrix=$(python tests/native_matrix.py "$RUNNER_TEMP/native-envs.txt") + printf 'matrix=%s\n' "$matrix" >> "$GITHUB_OUTPUT" +``` + +Replace the existing `tests` job, not the unit/gemmi/check/build-publication logic, with: + +```yaml + tests: + name: "Native tests • ${{ matrix.env }}" + runs-on: ubuntu-latest + needs: [build] + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.build.outputs.native-matrix) }} + steps: + - uses: actions/checkout@v5 + - uses: actions/download-artifact@v6 + with: + name: dist + path: dist + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + - uses: astral-sh/setup-uv@v7 + - name: Test supplied wheel through shared tox profile + shell: bash + env: + TOX_PROFILE: ${{ matrix.env }} + run: | + set -euo pipefail + shopt -s nullglob + wheels=(dist/*.whl) + test "${#wheels[@]}" -eq 1 + uv run --no-project --isolated --with tox --with tox-uv \ + tox -e "$TOX_PROFILE" --installpkg "${wheels[0]}" + - name: Archive native build and test diagnostics + uses: actions/upload-artifact@v6 + if: failure() + with: + name: "${{ matrix.env }}-diagnostics" + include-hidden-files: true + path: | + .tox/${{ matrix.env }}/log/ + .tox/${{ matrix.env }}/tmp/native/**/evidence.json + .tox/${{ matrix.env }}/tmp/native/**/*.log + .tox/${{ matrix.env }}/tmp/native/**/CMakeCache.txt + .tox/${{ matrix.env }}/tmp/native/**/CMakeConfigureLog.yaml + .tox/${{ matrix.env }}/tmp/pytest-artifacts/ + .tox/${{ matrix.env }}/tmp/native.xml + retention-days: 30 + if-no-files-found: ignore +``` + +`include-hidden-files` is needed for `.tox` artifacts. Restrict paths to logs/evidence, not whole environments or fixture wheels. The old native-only annotation installation step is removed with the duplicate setup route; pytest/JUnit diagnostics remain available. No changes to gemmi's annotation step, release credentials, or publication conditions. + +- [ ] **Step 5: Exercise the supplied-wheel route locally and commit.** + +Build the generator wheel from a clean tracked-source archive (including Task 3's committed tree). Use fresh directory names for later repetitions: + +```sh +E="$(pwd)/.superpowers/sdd/2026-09-25-test-harness-phase-three" +mkdir "$E/clean-source" "$E/dist" +git archive --format=tar HEAD -o "$E/clean-source.tar" +tar -xf "$E/clean-source.tar" -C "$E/clean-source" +(cd "$E/clean-source" && env -u VIRTUAL_ENV uv build --wheel --out-dir "$E/dist") +WHEEL=$(python - "$E/dist" <<'PY' +from pathlib import Path +import sys +wheel, = Path(sys.argv[1]).glob('*.whl') +print(wheel.resolve()) +PY +) +printf '%s\n' "$WHEEL" > "$E/generator-wheel.path" +``` + +Record its SHA-256 and verify its production files match the current tracked production tree. Do not change generator package discovery or clean unrelated worktree build output. + +Run the CI command locally across all 11 profiles, with the batch audit and an absolute wheel path: + +```sh +python "$E/audit_native.py" --label supplied-wheel -- \ + uv run --no-project --isolated --with tox --with tox-uv \ + tox --installpkg "$WHEEL" +``` + +Inspect tox package-install records: they must name the supplied wheel and must not build/reinstall the generator from `.`. Distinguish expected fixture-wheel builds from a forbidden replacement generator build. Confirm installed origin within pytest. Save this artifact-identity probe as `$E/check_supplied_wheel.py`, then run it with each of the 11 native interpreters using `-I`, passing the absolute `$WHEEL` path: + +```python +import hashlib +from importlib.metadata import distribution +import json +from pathlib import Path +import sys +import zipfile + +import pybind11_stubgen + +wheel = Path(sys.argv[1]) +digest = hashlib.sha256(wheel.read_bytes()).hexdigest() +origin = Path(pybind11_stubgen.__file__).resolve() +assert origin.is_relative_to(Path(sys.prefix).resolve()), origin +installed = origin.parent.parent +with zipfile.ZipFile(wheel) as archive: + assert not any(n.startswith('build/') for n in archive.namelist()) + names = {n for n in archive.namelist() if n.startswith('pybind11_stubgen/') and n.endswith('.py')} + actual = {p.relative_to(installed).as_posix() for p in origin.parent.rglob('*.py')} + assert names == actual + for name in names: + assert archive.read(name) == (installed / name).read_bytes(), name +info = json.loads(distribution('pybind11-stubgen').read_text('direct_url.json')) +archive_info = info['archive_info'] +assert (archive_info.get('hashes', {}).get('sha256') == digest + or archive_info.get('hash') == 'sha256=' + digest), info +print(json.dumps({'origin': str(origin), 'wheel_sha256': digest, 'direct_url': info})) +``` + +If the installer records an unexpected provenance schema, investigate rather than removing the artifact identity check; origin and equal source files alone are insufficient. Run focused and cumulative tests on 3.10/3.13. Run this mutation with isolated uv supplying pytest, then restore an unpatched green run. Run YAML/all applicable hooks. + +```python +import sys +from pathlib import Path +from unittest.mock import patch + +sys.path.insert(0, str(Path("tests").resolve())) +import native_matrix +import pytest + +original = native_matrix.matrix_from_names + +def wrong_interpreter(names): + result = original(names) + for row in result["include"]: + row["python"] = "3.13" + return result + +with patch.object(native_matrix, "matrix_from_names", side_effect=wrong_interpreter): + code = pytest.main(["tests/unit/test_native_matrix.py::test_explicit_profile_inventory_and_interpreters", "-q"]) +assert code == pytest.ExitCode.TESTS_FAILED, code +``` + +```sh +git add tests/native_matrix.py tests/unit/test_native_matrix.py .github/workflows/ci.yml +git commit -m "test: derive native CI jobs from tox and consume the built wheel" +``` + +--- + +### Task 5: User documentation and full acceptance audit + +**Files:** +- Modify: `tests/README.md`. +- Evidence only: this plan's ignored workspace, including disposable source copies and final report. + +**Interfaces:** +- Consumes all preceding CLI/environment interfaces, wheel evidence, JUnit case properties, and audit helper. +- Produces documented workflows and a final local acceptance report; no new runtime/helper API. + +- [ ] **Step 1: Update documentation alongside verified commands.** + +Replace the old native prerequisites/commands/suffix explanation with this content, retaining the existing snapshot update safeguards and compiler-free instructions: + +```markdown +Native checks require Python 3.10–3.13 for the full matrix, uv, tox with tox-uv, +and a C++17 compiler. Tox installs pinned pybind11 distributions, CMake, +scikit-build-core, Ninja, cmeel/Eigen, NumPy, and SciPy. It no longer clones +pybind11 or builds/installs demo-lib separately. Normalization still uses the +existing pinned Ruff tool. + +The default matrix has 11 native build profiles covering the same 13 cases. +For Python 3.13 with pybind11 2.13 or 3.0, both NumPy rendering modes share one +installed fixture. Other profiles use annotated arrays. The old `-naa` and +`-nutv` tox suffixes are replaced by a mode selection within the grouped profile. + + tox -e py313-pb30 + tox -e py313-pb30 -- -k demo_errors + tox -e py313-pb30 -- --numpy-format numpy-array-use-type-var + tox + tox -e py313-pb30 --installpkg dist/pybind11_stubgen-3.0.0-py3-none-any.whl + +Tox's selected pybind11/CMake and runtime dependency versions are authoritative +for native checks both locally and in CI. NumPy/SciPy are pinned to the observed +phase-two runtime versions; fixture installation cannot replace them. The root +development dependency group is not the native test environment. + +Local native tox installs a generator wheel. CI supplies its existing build +artifact through the same runner. Installed origins are checked inside pytest. +After a matching tox run, a single check can be repeated without rebuilding: + + STUBGEN_TEST_INSTALLED=1 .tox/py313-pb30/bin/python -I -m pytest \ + tests/test_demo_stubs.py --pybind11-branch v3.0 \ + --numpy-format numpy-array-use-type-var + +That direct rerun checks installed origins and snapshots but does not reconstruct +tox's dependency-pin environment. Rerun tox after generator/fixture changes or +to validate the configured pins and rebuild the fixture. + +Each profile owns its build directories and diagnostics. Checks can use bounded +parallel tox execution after the isolation acceptance test; they do not require +pytest workers. Reference updates remain explicit and serial: + + tox -e py313-pb30 -- --numpy-format numpy-array-wrap-with-annotated --update-snapshots + git diff -- tests/stubs tests/errors + +Do not update shared references concurrently. Existing aliases and per-test, +nontransactional update semantics are unchanged. A broken build or generator +command is not eligible to become a new expectation. + +Build/install logs and fixture provenance are under +`.tox//tmp/native/run-*/`; tox command logs are under +`.tox//log/`. Pytest diagnostics remain in the environment's +`tmp/pytest-artifacts/`, with distinct case/check/run directories. The native +JUnit report is `tmp/native.xml`. CI's failure upload includes these scoped +logs and reports, not entire virtual environments. +``` + +Explain that the supplied-wheel example assumes the current generator wheel has already been built in `dist/`; use the actual filename when the project version changes. Update the earlier README sentence that says the default list still contains 13 environments. Keep the ordinary-Python positional-only bug and dirty-tree generator packaging issue outside this migration; replace the phase-two-only closing policy with the continuing test-infrastructure rule against hiding production bugs through expectation changes. + +- [ ] **Step 2: Run the four-version compiler-free acceptance independently of native environments.** + +Run all four explicit unit tox environments and fresh source/wheel checks using pytest-only environments. A concrete fresh-environment loop is: + +```sh +unset VIRTUAL_ENV STUBGEN_TEST_INSTALLED STUBGEN_NUMPY_VERSION STUBGEN_SCIPY_VERSION STUBGEN_NUMPY_FORMATS +for version in 3.10 3.11 3.12 3.13; do + uv venv --python "$version" "$E/source-$version" + uv pip install --python "$E/source-$version/bin/python" 'pytest>=8,<9' + "$E/source-$version/bin/python" -c 'from importlib.util import find_spec; assert all(find_spec(n) is None for n in ("demo", "numpy", "scipy", "cmake", "scikit_build_core"))' + "$E/source-$version/bin/python" -c 'from pathlib import Path; import pybind11_stubgen; assert Path(pybind11_stubgen.__file__).resolve() == Path("pybind11_stubgen/__init__.py").resolve()' + "$E/source-$version/bin/python" -m pytest tests/unit tests/test_snapshot_helpers.py -q + uv venv --python "$version" "$E/wheel-$version" + uv pip install --python "$E/wheel-$version/bin/python" 'pytest>=8,<9' "$WHEEL" + "$E/wheel-$version/bin/python" -I -c 'from importlib.util import find_spec; assert all(find_spec(n) is None for n in ("demo", "numpy", "scipy", "cmake", "scikit_build_core"))' + STUBGEN_TEST_INSTALLED=1 "$E/wheel-$version/bin/python" -I -m pytest tests/unit tests/test_snapshot_helpers.py -q +done +uv run --no-project --isolated --with tox --with tox-uv tox \ + -e py310-unit,py311-unit,py312-unit,py313-unit +``` + +Build `$WHEEL` from the final tracked implementation first, not from a stale task snapshot. Reuse Task 4's archive/build procedure with new `final-source`/`final-dist` directories and update `generator-wheel.path`. Test the installed guard's red/green behavior on a narrow selection: from the checkout, run the fresh wheel environment's Python without `-I`, with `STUBGEN_TEST_INSTALLED=1`, on `tests/unit/test_pipeline.py::test_python_module_pipeline`; require a setup error naming the checkout origin. Restore `-I` and require a passing test, then run the full unpatched selection. + +- [ ] **Step 3: Re-run native acceptance and inspect exact cases, reuse, and pins.** + +Use unique audit labels for an unfiltered local-wheel run and a supplied-wheel run on the final tree. Read all 11 JUnit files and compare to the independently expected native inventory. A checker core is: + +```python +from pathlib import Path +import xml.etree.ElementTree as ET + +profiles = ["py310-pb30", "py311-pb30", "py312-pb30", "py313-pb30", + "py310-pb213", "py311-pb213", "py312-pb213", "py313-pb213", + "py313-pb29", "py313-pb211", "py313-pb212"] +A, T = "numpy-array-wrap-with-annotated", "numpy-array-use-type-var" +seen = [] +for profile in profiles: + tree = ET.parse(Path(".tox") / profile / "tmp/native.xml") + cases = tree.findall(".//testcase") + assert not any(c.find(tag) is not None for c in cases for tag in ("failure", "error", "skipped")) + native = [c.attrib["name"] for c in cases if c.attrib["name"].startswith("test_demo_")] + modes = [A, T] if profile in ("py313-pb30", "py313-pb213") else [A] + expected = [f"{test}[{mode}]" for test in ("test_demo_stubs", "test_demo_errors") for mode in modes] + assert sorted(native) == sorted(expected), (profile, native) + properties = {p.attrib["name"]: p.attrib["value"] for p in tree.findall(".//property")} + assert Path(properties["native_extension"]).resolve().is_relative_to((Path(".tox") / profile).resolve()) + assert len(properties["native_extension_sha256"]) == 64 + seen.extend((profile, mode) for mode in modes) +assert len(seen) == len(set(seen)) == 13 +``` + +Sorting here compares case inventories only; never sort diagnostic stderr or expected output. Check the JUnit runtime properties against the explicit version table. For each dual-mode profile, correlate its single fixture build command/evidence record with both pytest mode IDs and the unchanged extension digest. Check `evidence.json` before/after versions, CMake's selected version/paths, and the audited fixture payload. + +Use `--result-json` on tox verification runs when useful to retain the package installation command list. Verify installed generator files against `$WHEEL` with `zipfile`/hashes inside each environment; do not infer artifact identity solely from a site-packages path. + +- [ ] **Step 4: Demonstrate bounded parallel isolation and sibling-source freshness.** + +Run two different profiles concurrently (two tox workers, two compiler jobs per build), using the supplied generator wheel and the audit: + +```sh +python "$E/audit_native.py" --label parallel -- \ + uv run --no-project --isolated --with tox --with tox-uv \ + tox -p 2 -e py310-pb213,py313-pb30 --installpkg "$WHEEL" +``` + +Verify both complete case inventories and disjoint build/evidence paths. Never add `--update-snapshots` to parallel verification. + +For freshness, extract another clean tracked archive into a new directory beneath `$E`. Run `py313-pb30` there using `$WHEEL`; save its build evidence. Append this line to that disposable copy's `tests/demo-lib/include/demo/Foo.h`: + +```cpp +#error PHASE_THREE_SIBLING_REBUILD_CONTROL +``` + +Run the same tox command again. Require nonzero build failure containing that marker, a new retained build log, and no subsequent pytest command using the previously installed extension. Restore the original header bytes in that disposable copy and require a green rerun. Never modify the tracked fixture in the main worktree for this probe. This is behavioral proof against stale frontend wheel caching and stale CMake inputs, not merely a command-argument assertion. + +- [ ] **Step 5: Verify failure and preservation boundaries, then commit documentation.** + +Run the Task 1 grouped-mode failure regression, Task 2 process/log/timeout/payload tests, and drift/origin negative controls on the final code. For an actual native pytest drift control, run these commands separately, recording the first as an expected failure rather than letting shell fail-fast conceal the restored run: + +```sh +STUBGEN_TEST_INSTALLED=1 STUBGEN_NUMPY_VERSION=0.0.0 STUBGEN_SCIPY_VERSION=1.17.1 \ + .tox/py313-pb30/bin/python -I -m pytest tests/test_demo_stubs.py \ + --pybind11-branch v3.0 --numpy-format numpy-array-wrap-with-annotated -q +STUBGEN_TEST_INSTALLED=1 STUBGEN_NUMPY_VERSION=2.5.3 STUBGEN_SCIPY_VERSION=1.17.1 \ + .tox/py313-pb30/bin/python -I -m pytest tests/test_demo_stubs.py \ + --pybind11-branch v3.0 --numpy-format numpy-array-wrap-with-annotated -q +``` + +Require a setup error naming NumPy for the first and a green test for the second. No native rebuild is needed for this isolated control. + +Audit protected paths against `807e7e4`: + +```sh +git diff --exit-code 807e7e4 -- pybind11_stubgen tests/py-demo/demo \ + tests/demo-lib tests/py-demo/bindings/src \ + tests/stubs tests/errors tests/snapshot_helpers.py tests/test_snapshot_helpers.py \ + tests/test_demo_stubs.py tests/test_demo_errors.py pyproject.toml uv.lock +uv lock --check +uv run pre-commit run --all-files +git diff --check +``` + +Review the remaining CMake/fixture metadata/tox/CI changes manually against the spec. Confirm gemmi, release triggers/permissions/conditions, root package discovery, and all snapshot aliases are unchanged. No old installer calls or old native environment examples should remain in active runner/docs files (historical design/plan documents may retain them). + +Write a final report containing exact commits, command exit statuses, case inventories, observed versions/build locations, wheel hashes/origins, reference/index audit results, negative-control failures and restored greens, plus the explicitly deferred remote acceptance. Retain logs for both early build failures and ordinary pytest failures. Do not claim actual GitHub upload behavior from YAML validation. + +```sh +git add tests/README.md +git commit -m "docs: document grouped native tests and verified dependency policy" +``` + +## Spec coverage and final review gate + +| Spec contract | Implementation/verification task | +| --- | --- | +| 11 profiles / 13 cases; single-mode override; continue after ordinary failure | Tasks 1, 3, 4, 5 | +| Same-process generator origin and installed demo/extension provenance | Tasks 1, 5 | +| Explicit pybind11/CMake/Eigen/backend/runtime selections, no dependency replacement | Tasks 2, 3, 5 | +| Standard fixture wheel, no redundant standalone demo build, correct payload | Tasks 2, 3, 5 | +| Per-environment build paths, actual shared fixture, source freshness, parallel safety | Tasks 1, 2, 3, 5 | +| Authoritative tox matrix; externally supplied generator wheel; retained early/late logs | Tasks 3, 4, 5 | +| Compiler-free independence and sensitive orchestration tests | Tasks 1, 2, 4, 5 | +| Immutable references/index during checks, production/demo source preservation | Baseline and Tasks 3–5 | +| Existing unit/gemmi/release/root packaging boundaries | Tasks 3–5 and final cumulative review | +| Local-only acceptance, separate bugs, documented limits | Every task report and Task 5 | + +Request a final cumulative review against the approved spec after all five task gates. Re-run affected acceptance after any fixes; do not assume earlier green runs cover later edits. Keep the existing branch/worktree and perform no integration or remote actions without a new user decision. diff --git a/docs/superpowers/specs/2026-09-25-test-harness-phase-three-design.md b/docs/superpowers/specs/2026-09-25-test-harness-phase-three-design.md index 450f9ea0..ecab0ef3 100644 --- a/docs/superpowers/specs/2026-09-25-test-harness-phase-three-design.md +++ b/docs/superpowers/specs/2026-09-25-test-harness-phase-three-design.md @@ -2,10 +2,10 @@ ## Status and starting point -The orchestration, fixture-build, and acceptance sections were approved in -conversation. This written specification awaits user review. The runtime -NumPy reconciliation described below was discovered while preparing the document -and is explicitly part of that review, not a silently approved dependency change. +The user approved the orchestration, fixture-build, and acceptance design and +this written specification, explicitly including the NumPy/SciPy pins. The +runtime dependency reconciliation below was discovered while preparing the +specification and approved during its review, not silently adopted. No phase-three implementation is included here. Continue on `test-harness-phase-one` in its existing isolated worktree. The @@ -169,7 +169,7 @@ installation logs show NumPy being reinstalled at this stage. Therefore the configured requirement alone is not a reliable description of native runtime coverage; the mismatch is present before phase three. -The proposed policy is to pin the actual observed runtime versions, preserving +Pin the actual observed runtime versions, preserving what the accepted native tests exercise rather than silently switching them to NumPy 1.x or upgrading them further: @@ -180,7 +180,7 @@ NumPy 1.x or upgrading them further: | 3.12–3.13 | `2.5.3` | `1.17.1` | All existing environments for a given interpreter agree on these versions. -This NumPy declaration change requires the user's explicit written-spec review. +This NumPy declaration change was explicitly approved during written-spec review. It changes declared native setup policy, not the observed runtime baseline. Keep the current pytest and typing-extensions requirements; unrelated package or root development-group changes are not included. From 26dddace227e2138bb938d7315b60abd3787c1cd Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 06:45:55 +0000 Subject: [PATCH 28/40] test: parameterize native modes and share installed-origin guards --- tests/conftest.py | 57 ++++++++++++++++- tests/native_support.py | 44 +++++++++++++ tests/unit/conftest.py | 14 ----- tests/unit/test_native_support.py | 101 ++++++++++++++++++++++++++++++ 4 files changed, 200 insertions(+), 16 deletions(-) create mode 100644 tests/native_support.py create mode 100644 tests/unit/test_native_support.py diff --git a/tests/conftest.py b/tests/conftest.py index b2ada0e0..225d07d6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,13 @@ +import hashlib +import importlib +import importlib.metadata +import os import sys from pathlib import Path import pytest +from native_support import check_versions, require_origin, runtime_pins, select_modes from snapshot_helpers import BRANCHES, NUMPY_FORMATS, make_case @@ -14,13 +19,61 @@ def pytest_addoption(parser): group.addoption("--artifacts-dir", type=Path) +def pytest_generate_tests(metafunc): + if "demo_case" not in metafunc.fixturenames: + return + try: + modes = select_modes( + metafunc.config.getoption("numpy_format"), + os.environ.get("STUBGEN_NUMPY_FORMATS"), + ) + except ValueError as exc: + raise pytest.UsageError(str(exc)) from exc + metafunc.parametrize("demo_case", modes, indirect=True, ids=modes) + + +@pytest.fixture(scope="session", autouse=True) +def enforce_installed_origin(): + if os.environ.get("STUBGEN_TEST_INSTALLED") != "1": + return + importlib.import_module("pybind11_stubgen") + for name, module in list(sys.modules.items()): + if name == "pybind11_stubgen" or name.startswith("pybind11_stubgen."): + require_origin(name, getattr(module, "__file__", None), Path(sys.prefix)) + + +@pytest.fixture(scope="session") +def installed_native(record_testsuite_property): + prefix = Path(sys.prefix) + demo = importlib.import_module("demo") + extension = importlib.import_module("demo._bindings") + require_origin("demo", demo.__file__, prefix) + path = require_origin("demo._bindings", extension.__file__, prefix) + expected = runtime_pins(os.environ) + before = {name: importlib.metadata.version(name) for name in ("numpy", "scipy")} + check_versions(expected, before) + digest = hashlib.sha256(path.read_bytes()).hexdigest() + record_testsuite_property("native_extension", str(path)) + record_testsuite_property("native_extension_sha256", digest) + record_testsuite_property( + "native_python", f"{sys.version_info.major}.{sys.version_info.minor}" + ) + for name, version in before.items(): + record_testsuite_property(name, version) + yield + after = {name: importlib.metadata.version(name) for name in before} + check_versions(before, after) + check_versions(expected, after) + assert hashlib.sha256(path.read_bytes()).hexdigest() == digest, path + + @pytest.fixture -def demo_case(pytestconfig): +def demo_case(request, pytestconfig, installed_native): return make_case( Path(__file__).resolve().parents[1], sys.version_info[:2], pytestconfig.getoption("pybind11_branch"), - pytestconfig.getoption("numpy_format"), + request.param, ) diff --git a/tests/native_support.py b/tests/native_support.py new file mode 100644 index 00000000..55e99842 --- /dev/null +++ b/tests/native_support.py @@ -0,0 +1,44 @@ +from collections.abc import Mapping +from pathlib import Path + +from snapshot_helpers import NUMPY_FORMATS + + +def select_modes(explicit: str | None, defaults: str | None) -> tuple[str, ...]: + modes = (explicit,) if explicit is not None else tuple((defaults or "").split()) + if not modes or any(mode not in NUMPY_FORMATS for mode in modes): + raise ValueError( + "Native tests require --numpy-format or valid STUBGEN_NUMPY_FORMATS" + ) + if len(set(modes)) != len(modes): + raise ValueError("Duplicate NumPy modes") + return modes + + +def require_origin(name: str, origin: str | None, prefix: Path) -> Path: + prefix = prefix.resolve() + if origin is None or not Path(origin).resolve().is_relative_to(prefix): + raise RuntimeError(f"{name} must be installed under {prefix}; found {origin}") + return Path(origin).resolve() + + +def check_versions(expected: Mapping[str, str], actual: Mapping[str, str]) -> None: + for name, version in expected.items(): + if actual.get(name) != version: + raise RuntimeError( + f"Dependency drift: {name}: expected {version}, found {actual.get(name)}" + ) + + +def runtime_pins(environ: Mapping[str, str]) -> dict[str, str]: + result = { + name: environ[key] + for name, key in ( + ("numpy", "STUBGEN_NUMPY_VERSION"), + ("scipy", "STUBGEN_SCIPY_VERSION"), + ) + if key in environ + } + if len(result) == 1 or any(not version for version in result.values()): + raise ValueError("Set both NumPy and SciPy pins, or neither for a direct rerun") + return result diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 538ca3f1..f8785e87 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -1,5 +1,4 @@ import importlib -import os import sys from pathlib import Path @@ -9,19 +8,6 @@ from unit_support import make_parser, q -@pytest.fixture(scope="session", autouse=True) -def enforce_installed_origin(): - if os.environ.get("STUBGEN_TEST_INSTALLED") != "1": - return - prefix = Path(sys.prefix).resolve() - for name, module in list(sys.modules.items()): - if name == "pybind11_stubgen" or name.startswith("pybind11_stubgen."): - origin = getattr(module, "__file__", None) - assert origin is not None, (name, origin) - path = Path(origin).resolve() - assert path.is_relative_to(prefix), (name, path, prefix) - - @pytest.fixture def parser(): result = make_parser() diff --git a/tests/unit/test_native_support.py b/tests/unit/test_native_support.py new file mode 100644 index 00000000..111ede80 --- /dev/null +++ b/tests/unit/test_native_support.py @@ -0,0 +1,101 @@ +import os +from pathlib import Path +import shutil +import subprocess +import sys +import xml.etree.ElementTree as ET + +import pytest + +from native_support import check_versions, require_origin, runtime_pins, select_modes + +A = "numpy-array-wrap-with-annotated" +T = "numpy-array-use-type-var" + + +def test_modes_preserve_order_and_cli_overrides(): + assert select_modes(None, f"{A} {T}") == (A, T) + assert select_modes(T, f"{A} {T}") == (T,) + assert select_modes(A, None) == (A,) + + +@pytest.mark.parametrize("value", [None, "", "unknown", f"{A} {A}"]) +def test_invalid_defaults_are_errors_not_empty_parameters(value): + with pytest.raises(ValueError): + select_modes(None, value) + + +def test_invalid_explicit_mode(): + with pytest.raises(ValueError): + select_modes("unknown", A) + + +def test_origin_checks_path_components_and_missing_origins(tmp_path): + prefix = tmp_path / "env" + origin = prefix / "lib" / "module.py" + assert require_origin("module", str(origin), prefix) == origin + for outside in (None, str(tmp_path / "env-other" / "module.py")): + with pytest.raises(RuntimeError, match="module"): + require_origin("module", outside, prefix) + + +def test_runtime_pins_and_drift(): + env = {"STUBGEN_NUMPY_VERSION": "2.2.6", "STUBGEN_SCIPY_VERSION": "1.15.3"} + expected = {"numpy": "2.2.6", "scipy": "1.15.3"} + assert runtime_pins(env) == expected + assert runtime_pins({}) == {} + check_versions(expected, expected) + with pytest.raises(ValueError): + runtime_pins({"STUBGEN_NUMPY_VERSION": "2.2.6"}) + for actual in ({}, {**expected, "numpy": "1.26.4"}): + with pytest.raises(RuntimeError, match="numpy"): + check_versions(expected, actual) + + +@pytest.mark.parametrize("explicit, expected_count", [(None, 2), (T, 1)]) +def test_real_pytest_runs_remaining_mode_after_failure( + tmp_path, explicit, expected_count +): + tests_root = Path(__file__).resolve().parents[1] + for name in ("conftest.py", "snapshot_helpers.py", "native_support.py"): + shutil.copyfile(tests_root / name, tmp_path / name) + (tmp_path / "test_modes.py").write_text( + "import pytest\n" + "@pytest.fixture\n" + "def demo_case(request): return request.param\n" + "def test_mode(demo_case):\n" + f" assert demo_case != {A!r}\n" + ) + env = {k: v for k, v in os.environ.items() if not k.startswith("STUBGEN_")} + env.pop("PYTEST_ADDOPTS", None) + env["STUBGEN_NUMPY_FORMATS"] = f"{A} {T}" + env["PYTEST_DISABLE_PLUGIN_AUTOLOAD"] = "1" + command = [ + sys.executable, + "-I", + "-m", + "pytest", + "test_modes.py", + "-q", + "--confcutdir", + str(tmp_path), + "--junitxml", + "result.xml", + ] + if explicit: + command += ["--numpy-format", explicit] + result = subprocess.run( + command, cwd=tmp_path, env=env, capture_output=True, text=True, timeout=30 + ) + assert result.returncode == (1 if explicit is None else 0), ( + result.stdout + result.stderr + ) + cases = ET.parse(tmp_path / "result.xml").findall(".//testcase") + assert len(cases) == expected_count + assert [c.attrib["name"] for c in cases] == ( + [f"test_mode[{A}]", f"test_mode[{T}]"] + if explicit is None + else [f"test_mode[{T}]"] + ) + assert sum(c.find("failure") is not None for c in cases) == (explicit is None) + assert not any(c.find("skipped") is not None for c in cases) From c1d0ba9ae560c8c3579167d7375b9c883c70f3ce Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 06:55:11 +0000 Subject: [PATCH 29/40] test: add isolated fixture wheel builder with retained diagnostics --- tests/build_native.py | 257 ++++++++++++++++++++++++++++++++ tests/unit/test_build_native.py | 200 +++++++++++++++++++++++++ 2 files changed, 457 insertions(+) create mode 100644 tests/build_native.py create mode 100644 tests/unit/test_build_native.py diff --git a/tests/build_native.py b/tests/build_native.py new file mode 100644 index 00000000..7f7fa5a1 --- /dev/null +++ b/tests/build_native.py @@ -0,0 +1,257 @@ +import hashlib +import importlib.machinery +import importlib.metadata +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import zipfile + +from native_support import check_versions, require_origin + +PACKAGES = ( + "numpy", + "scipy", + "pybind11", + "cmake", + "ninja", + "scikit-build-core", + "cmeel", + "cmeel-eigen", +) + + +def expected_versions(environ) -> dict[str, str]: + pins = {} + for name in PACKAGES: + key = "STUBGEN_" + name.upper().replace("-", "_") + "_VERSION" + if not environ.get(key): + raise RuntimeError(f"{key} must be set by tox") + pins[name] = environ[key] + return pins + + +def new_run_dir(prefix: Path, repo: Path) -> Path: + prefix, repo = prefix.resolve(), repo.resolve() + if repo.is_relative_to(prefix) or prefix.is_relative_to(repo / "tests"): + raise RuntimeError("Native build prefix overlaps source roots") + parent = prefix / "tmp/native" + require_origin("native build directory", str(parent), prefix) + parent.mkdir(parents=True, exist_ok=True) + return Path(tempfile.mkdtemp(prefix="run-", dir=parent)) + + +def build_command( + python: str, + source: Path, + run_dir: Path, + pins: dict[str, str], + pybind_dir: Path, + eigen_dir: Path, +) -> list[str]: + command = [ + "uv", + "build", + "--wheel", + "--no-build-isolation", + "--no-cache", + "--python", + python, + "--out-dir", + str(run_dir / "wheel"), + ] + settings = { + "build-dir": str(run_dir / "cmake"), + "cmake.version": "==" + pins["cmake"], + "ninja.version": "==" + pins["ninja"], + "ninja.make-fallback": "false", + "cmake.define.Python_EXECUTABLE": python, + "cmake.define.pybind11_DIR": str(pybind_dir), + "cmake.define.Eigen3_DIR": str(eigen_dir), + "cmake.define.STUBGEN_PYBIND11_VERSION": pins["pybind11"], + } + for key, value in settings.items(): + command += ["--config-setting", f"{key}={value}"] + return command + [str(source)] + + +def install_command(python: str, wheel: Path) -> list[str]: + return [ + "uv", + "pip", + "install", + "--python", + python, + "--no-deps", + "--reinstall-package", + "py-demo", + "--no-cache", + str(wheel), + ] + + +def run_logged( + command: list[str], + *, + cwd: Path, + log: Path, + env: dict[str, str], + timeout: float = 600, +) -> None: + print(f"Running {command!r}; log: {log}", flush=True) + try: + with log.open("xb") as stream: + stream.write((json.dumps(command) + "\n").encode()) + stream.flush() + result = subprocess.run( + command, + cwd=cwd, + env=env, + stdout=stream, + stderr=subprocess.STDOUT, + timeout=timeout, + ) + except subprocess.TimeoutExpired as exc: + raise RuntimeError( + f"Command timed out after {timeout}s; log: {log}; {command!r}" + ) from exc + if result.returncode: + try: + tail = "\n".join(log.read_text(errors="replace").splitlines()[-30:]) + except OSError as exc: + tail = f"Could not read log tail: {exc}" + raise RuntimeError( + f"Command exit {result.returncode}; log: {log}; {command!r}\n{tail}" + ) + + +def audit_wheel(wheel: Path, package_source: Path) -> None: + expected = { + "demo/" + p.relative_to(package_source).as_posix(): p.read_bytes() + for p in package_source.rglob("*.py") + } + with zipfile.ZipFile(wheel) as archive: + names = archive.namelist() + if len(names) != len(set(names)) or any( + not n.startswith(("demo/", "py_demo-0.0.0.dist-info/")) + or n.endswith((".a", ".lib", ".h", ".hpp", ".pyc")) + for n in names + ): + raise RuntimeError("Unexpected fixture wheel payload") + actual = {n: archive.read(n) for n in names if n.endswith(".py")} + extension_names = { + "demo/_bindings" + suffix + for suffix in importlib.machinery.EXTENSION_SUFFIXES + } + extensions = [n for n in names if n in extension_names] + payload = {n for n in names if not n.startswith("py_demo-0.0.0.dist-info/")} + if ( + actual != expected + or len(extensions) != 1 + or payload != set(expected) | set(extensions) + ): + raise RuntimeError( + "Fixture wheel payload differs from source package/extension contract" + ) + + +def main() -> int: + repo = Path(__file__).resolve().parents[1] + source = repo / "tests/py-demo" + run_dir = None + evidence = {"python": sys.executable, "prefix": sys.prefix, "phase": "prepare"} + try: + run_dir = new_run_dir(Path(sys.prefix), repo) + print(f"Native fixture evidence: {run_dir}", flush=True) + pins = expected_versions(os.environ) + before = {name: importlib.metadata.version(name) for name in PACKAGES} + evidence.update(pins=pins, before=before) + check_versions(pins, before) + for name in PACKAGES: + location = importlib.metadata.distribution(name).locate_file("") + require_origin(name, str(location), Path(sys.prefix)) + pybind_dir = Path( + importlib.metadata.distribution("pybind11").locate_file( + "pybind11/share/cmake/pybind11" + ) + ) + eigen_dir = Path( + importlib.metadata.distribution("cmeel-eigen").locate_file( + "cmeel.prefix/share/eigen3/cmake" + ) + ) + for name, path, config in ( + ("pybind11", pybind_dir, "pybind11Config.cmake"), + ("Eigen", eigen_dir, "Eigen3Config.cmake"), + ): + require_origin(name, str(path), Path(sys.prefix)) + if not (path / config).is_file(): + raise RuntimeError(f"Missing {name} CMake configuration: {path}") + evidence.update( + pins=pins, + before=before, + pybind_dir=str(pybind_dir), + eigen_dir=str(eigen_dir), + ) + env = dict(os.environ) + for key in list(env): + if key.startswith("SKBUILD_") or key in ( + "CMAKE_ARGS", + "CMAKE_PREFIX_PATH", + "CMAKE_TOOLCHAIN_FILE", + ): + env.pop(key) + env["CMAKE_BUILD_PARALLEL_LEVEL"] = "2" + evidence["phase"] = "build" + (run_dir / "evidence.json").write_text(json.dumps(evidence, indent=2) + "\n") + run_logged( + build_command(sys.executable, source, run_dir, pins, pybind_dir, eigen_dir), + cwd=repo, + log=run_dir / "build.log", + env=env, + ) + wheels = list((run_dir / "wheel").glob("*.whl")) + if len(wheels) != 1: + raise RuntimeError(f"Expected one new fixture wheel, found {wheels}") + wheel = wheels[0] + evidence["phase"] = "wheel payload" + audit_wheel(wheel, source / "demo") + evidence.update( + wheel=str(wheel), + wheel_sha256=hashlib.sha256(wheel.read_bytes()).hexdigest(), + ) + evidence["phase"] = "install" + run_logged( + install_command(sys.executable, wheel), + cwd=repo, + log=run_dir / "install.log", + env=env, + ) + after = {name: importlib.metadata.version(name) for name in PACKAGES} + check_versions(pins, after) + evidence.update(after=after, phase="complete") + (run_dir / "evidence.json").write_text(json.dumps(evidence, indent=2) + "\n") + return 0 + except ( + OSError, + RuntimeError, + ValueError, + importlib.metadata.PackageNotFoundError, + zipfile.BadZipFile, + ) as exc: + print(f"Native fixture {evidence['phase']} failed: {exc}", file=sys.stderr) + if run_dir is not None: + evidence["error"] = str(exc) + try: + (run_dir / "evidence.json").write_text( + json.dumps(evidence, indent=2) + "\n" + ) + except OSError as report_error: + print(f"Could not save evidence: {report_error}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/unit/test_build_native.py b/tests/unit/test_build_native.py new file mode 100644 index 00000000..d8f45f57 --- /dev/null +++ b/tests/unit/test_build_native.py @@ -0,0 +1,200 @@ +import json +import os +import sys +import zipfile +from importlib.machinery import EXTENSION_SUFFIXES +from pathlib import Path + +import pytest + +from build_native import ( + audit_wheel, + build_command, + expected_versions, + install_command, + new_run_dir, + run_logged, +) + +PINS = { + "numpy": "2.2.6", + "scipy": "1.15.3", + "pybind11": "2.9.2", + "cmake": "3.31.10", + "ninja": "1.13.2", + "scikit-build-core": "1.0.3", + "cmeel": "0.59.0", + "cmeel-eigen": "3.4.0.2", +} + + +def test_explicit_versions_are_required(): + env = { + "STUBGEN_" + k.upper().replace("-", "_") + "_VERSION": v + for k, v in PINS.items() + } + assert expected_versions(env) == PINS + del env["STUBGEN_NUMPY_VERSION"] + with pytest.raises(RuntimeError, match="STUBGEN_NUMPY_VERSION"): + expected_versions(env) + + +def test_run_directories_are_owned_unique_and_not_source(tmp_path): + repo = tmp_path / "repo" + prefix = tmp_path / "env" + first = new_run_dir(prefix, repo) + second = new_run_dir(prefix, repo) + other = new_run_dir(tmp_path / "env-other", repo) + assert first != second != other + assert first.is_relative_to(prefix / "tmp/native") + assert other.is_relative_to(tmp_path / "env-other/tmp/native") + with pytest.raises(RuntimeError): + new_run_dir(repo, repo) + with pytest.raises(RuntimeError): + new_run_dir(repo / "tests/py-demo", repo) + linked = tmp_path / "linked-env" + linked.mkdir() + outside = tmp_path / "outside" + outside.mkdir() + (linked / "tmp").symlink_to(outside, target_is_directory=True) + with pytest.raises(RuntimeError): + new_run_dir(linked, repo) + assert list(outside.iterdir()) == [] + + +def test_commands_target_interpreter_tools_and_wheel_without_dependency_resolution( + tmp_path, +): + source, run = tmp_path / "fixture", tmp_path / "run" + pb, eigen = tmp_path / "pb", tmp_path / "eigen" + command = build_command("/env/bin/python", source, run, PINS, pb, eigen) + assert command[:8] == [ + "uv", + "build", + "--wheel", + "--no-build-isolation", + "--no-cache", + "--python", + "/env/bin/python", + "--out-dir", + ] + assert f"cmake.define.pybind11_DIR={pb}" in command + assert f"cmake.define.Eigen3_DIR={eigen}" in command + assert "cmake.define.STUBGEN_PYBIND11_VERSION=2.9.2" in command + assert "cmake.version===3.31.10" in command + assert "ninja.version===1.13.2" in command + assert f"build-dir={run / 'cmake'}" in command + assert command[-1] == str(source) + wheel = run / "wheel/demo.whl" + assert install_command("/env/bin/python", wheel) == [ + "uv", + "pip", + "install", + "--python", + "/env/bin/python", + "--no-deps", + "--reinstall-package", + "py-demo", + "--no-cache", + str(wheel), + ] + + +@pytest.mark.parametrize("status", [0, 7]) +def test_real_process_status_and_output_are_retained(tmp_path, status): + command = [ + sys.executable, + "-c", + f"print('process-output'); raise SystemExit({status})", + ] + log = tmp_path / "process.log" + if status: + with pytest.raises(RuntimeError, match="exit 7"): + run_logged(command, cwd=tmp_path, log=log, env=dict(os.environ)) + else: + run_logged(command, cwd=tmp_path, log=log, env=dict(os.environ)) + lines = log.read_text().splitlines() + assert json.loads(lines[0]) == command + assert lines[-1] == "process-output" + + +def test_failed_process_diagnostics_survive_log_reread_error(tmp_path, monkeypatch): + command = [sys.executable, "-c", "print('process-output'); raise SystemExit(7)"] + log = tmp_path / "process.log" + read_text = Path.read_text + + def fail_log_read(path, *args, **kwargs): + if path == log: + raise OSError("injected log reread failure") + return read_text(path, *args, **kwargs) + + with monkeypatch.context() as scoped: + scoped.setattr(Path, "read_text", fail_log_read) + with pytest.raises(RuntimeError, match="exit 7") as caught: + run_logged(command, cwd=tmp_path, log=log, env=dict(os.environ)) + message = str(caught.value) + assert repr(command) in message + assert str(log) in message + lines = log.read_text().splitlines() + assert json.loads(lines[0]) == command + assert lines[-1] == "process-output" + + +def test_timeout_and_log_creation_failure_are_errors(tmp_path): + with pytest.raises(RuntimeError, match="timed out"): + run_logged( + [sys.executable, "-c", "import time; time.sleep(2)"], + cwd=tmp_path, + log=tmp_path / "timeout.log", + env=dict(os.environ), + timeout=0.05, + ) + assert (tmp_path / "timeout.log").is_file() + blocked = tmp_path / "blocked.log" + blocked.mkdir() + sentinel = tmp_path / "ran" + with pytest.raises(OSError): + run_logged( + [ + sys.executable, + "-c", + f"from pathlib import Path; Path({str(sentinel)!r}).touch()", + ], + cwd=tmp_path, + log=blocked, + env=dict(os.environ), + ) + assert not sentinel.exists() + + +@pytest.mark.parametrize( + "extra", + [ + None, + "lib/libdemo.a", + "include/demo/Foo.h", + "build/lib/stale.py", + "demo/unrelated.txt", + ], +) +def test_wheel_payload_is_exactly_package_extension_and_metadata(tmp_path, extra): + package = tmp_path / "demo" + package.mkdir() + (package / "__init__.py").write_bytes(b"VALUE = 1\n") + wheel = tmp_path / "fixture.whl" + with zipfile.ZipFile(wheel, "w") as archive: + archive.writestr("demo/__init__.py", b"VALUE = 1\n") + archive.writestr( + "demo/_bindings" + EXTENSION_SUFFIXES[0], b"test payload, not imported" + ) + archive.writestr("py_demo-0.0.0.dist-info/METADATA", b"Name: py-demo\n") + if extra: + archive.writestr(extra, b"unwanted") + if extra: + with pytest.raises(RuntimeError, match="payload"): + audit_wheel(wheel, package) + else: + audit_wheel(wheel, package) + (package / "__init__.py").write_bytes(b"VALUE = 2\n") + with pytest.raises(RuntimeError): + audit_wheel(wheel, package) From ee11e4ed2dbc18fb42bd194e0ccb74677ec2b4f6 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 07:17:12 +0000 Subject: [PATCH 30/40] test: build pinned native fixtures once per tox profile --- tests/install-demo-module.sh | 100 ------------------ tests/py-demo/bindings/CMakeLists.txt | 15 ++- tests/py-demo/pyproject.toml | 26 +++-- tests/py-demo/setup.py | 142 -------------------------- tox.ini | 82 +++++++-------- 5 files changed, 74 insertions(+), 291 deletions(-) delete mode 100755 tests/install-demo-module.sh delete mode 100644 tests/py-demo/setup.py diff --git a/tests/install-demo-module.sh b/tests/install-demo-module.sh deleted file mode 100755 index 4dae6ef0..00000000 --- a/tests/install-demo-module.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/bin/bash - -set -e - -PYTHON_EXECUTABLE=$(python -c 'import sys; print(sys.executable)') - -resolve_path() { - local path="$1" - - while [ -L "$path" ]; do - path="$(readlink "$path")" - done - - if [ -d "$path" ]; then - (cd "$path" && pwd -P) - else - (cd "$(dirname "$path")" && echo "$(pwd -P)/$(basename "$path")") - fi -} - - -function parse_args() { - - CLEAR='\033[0m' - RED='\033[0;31m' - - function usage() { - if [ -n "$1" ]; then - echo -e "${RED}👉 $1${CLEAR}\n"; - fi - echo "Usage: $0 --pybind11-branch PYBIND11_BRANCH" - echo " --pybind11-branch name of pybind11 branch" - exit 1 - } - - # parse params - while [[ "$#" > 0 ]]; do case $1 in - --pybind11-branch) PYBIND11_BRANCH="$2"; shift;shift;; - *) usage "Unknown parameter passed: $1"; shift; shift;; - esac; done - - # verify params - if [ -z "$PYBIND11_BRANCH" ]; then usage "PYBIND11_BRANCH is not set"; fi; - - TESTS_ROOT="$(resolve_path "$(dirname "$0")")" - PROJECT_ROOT="${TESTS_ROOT}/.." - TEMP_DIR="${PROJECT_ROOT}/tmp/pybind11-${PYBIND11_BRANCH}" - INSTALL_PREFIX="${TEMP_DIR}/install" - BUILD_ROOT="${TEMP_DIR}/build" - EXTERNAL_DIR="${TEMP_DIR}/external" -} - - -clone_pybind11() { - mkdir -p "${EXTERNAL_DIR}" - if [ ! -d "${EXTERNAL_DIR}/pybind11/${PYBIND11_BRANCH}" ]; then - git clone \ - --depth 1 \ - --branch "${PYBIND11_BRANCH}" \ - --single-branch \ - https://github.com/pybind/pybind11.git \ - "${EXTERNAL_DIR}/pybind11/${PYBIND11_BRANCH}" - fi -} - -install_pybind11() { - export CMAKE_PREFIX_PATH="$(cmeel cmake)" - cmake \ - -S "${EXTERNAL_DIR}/pybind11/${PYBIND11_BRANCH}" \ - -B "${BUILD_ROOT}/pybind11"\ - -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE} \ - --fresh - cmake --install "${BUILD_ROOT}/pybind11" \ - --prefix "${INSTALL_PREFIX}" -} - -install_demo() { - cmake -S "${TESTS_ROOT}/demo-lib" -B "${BUILD_ROOT}/demo" - cmake --build "${BUILD_ROOT}/demo" - cmake --install "${BUILD_ROOT}/demo" \ - --prefix "${INSTALL_PREFIX}" -} - -install_pydemo() { - ( - export CMAKE_PREFIX_PATH="$(resolve_path "${INSTALL_PREFIX}"):$(cmeel cmake)"; - rm -rf ${TESTS_ROOT}/py-demo/build - uv pip install --python "${PYTHON_EXECUTABLE}" --force-reinstall "${TESTS_ROOT}/py-demo" - ) -} - -main () { - parse_args "$@" - clone_pybind11 - install_pybind11 - install_demo - install_pydemo -} - -main "$@" diff --git a/tests/py-demo/bindings/CMakeLists.txt b/tests/py-demo/bindings/CMakeLists.txt index fe8d3880..0a529b51 100644 --- a/tests/py-demo/bindings/CMakeLists.txt +++ b/tests/py-demo/bindings/CMakeLists.txt @@ -5,8 +5,16 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +find_package(Eigen3 3.4.0 EXACT CONFIG REQUIRED) add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../../demo-lib ${CMAKE_BINARY_DIR}/demo-lib-build) -find_package(pybind11 CONFIG REQUIRED) +if(NOT DEFINED STUBGEN_PYBIND11_VERSION) + message(FATAL_ERROR "Build the fixture through tox to select pybind11 explicitly") +endif() +find_package(Python COMPONENTS Interpreter Development.Module REQUIRED) +find_package(pybind11 ${STUBGEN_PYBIND11_VERSION} EXACT CONFIG REQUIRED) +message(STATUS "Fixture Python: ${Python_EXECUTABLE}") +message(STATUS "Fixture pybind11: ${pybind11_VERSION} at ${pybind11_DIR}") +message(STATUS "Fixture Eigen3: ${Eigen3_VERSION} at ${Eigen3_DIR}") file(GLOB_RECURSE BINDINGS_SOURCES @@ -18,3 +26,8 @@ pybind11_add_module(_bindings ${BINDINGS_SOURCES}) target_include_directories(_bindings PRIVATE ./src) target_link_libraries(_bindings PRIVATE Demo::demo) + +install(TARGETS _bindings + LIBRARY DESTINATION demo COMPONENT python + RUNTIME DESTINATION demo COMPONENT python +) diff --git a/tests/py-demo/pyproject.toml b/tests/py-demo/pyproject.toml index 54a2bed2..e4e8997a 100644 --- a/tests/py-demo/pyproject.toml +++ b/tests/py-demo/pyproject.toml @@ -1,8 +1,20 @@ [build-system] -requires = [ - "setuptools>=42", - "wheel", - "ninja", - "cmake>=3.12", -] -build-backend = "setuptools.build_meta" +requires = ["scikit-build-core>=1.0,<2"] +build-backend = "scikit_build_core.build" + +[project] +name = "py-demo" +version = "0.0.0" +description = "Demo package to test stub generation" +readme = "README.rst" +requires-python = ">=3.10" +authors = [{name = "Sergei Izmailov", email = "sergei.a.izmailov@gmail.com"}] +dependencies = ["numpy"] + +[project.urls] +Repository = "https://github.com/sizmailov/pybind11-project-example" + +[tool.scikit-build] +cmake.source-dir = "bindings" +wheel.packages = ["demo"] +install.components = ["python"] diff --git a/tests/py-demo/setup.py b/tests/py-demo/setup.py deleted file mode 100644 index 7141bc94..00000000 --- a/tests/py-demo/setup.py +++ /dev/null @@ -1,142 +0,0 @@ -import os -import re -import subprocess -import sys -from pathlib import Path - -from setuptools import Extension, find_packages, setup -from setuptools.command.build_ext import build_ext - -# Convert distutils Windows platform specifiers to CMake -A arguments -PLAT_TO_CMAKE = { - "win32": "Win32", - "win-amd64": "x64", - "win-arm32": "ARM", - "win-arm64": "ARM64", -} - - -# A CMakeExtension needs a sourcedir instead of a file list. -# The name must be the _single_ output extension from the CMake build. -# If you need multiple extensions, see scikit-build. -class CMakeExtension(Extension): - def __init__(self, name: str, sourcedir: str = "") -> None: - super().__init__(name, sources=[]) - self.sourcedir = os.fspath(Path(sourcedir).resolve()) - - -class CMakeBuild(build_ext): - def build_extension(self, ext: CMakeExtension) -> None: - # Must be in this form due to bug in .resolve() only fixed in Python 3.10+ - ext_fullpath = Path.cwd() / self.get_ext_fullpath(ext.name) - extdir = ext_fullpath.parent.resolve() - - # Using this requires trailing slash for auto-detection & inclusion of - # auxiliary "native" libs - - debug = int(os.environ.get("DEBUG", 0)) if self.debug is None else self.debug - cfg = "Debug" if debug else "Release" - - # CMake lets you override the generator - we need to check this. - # Can be set with Conda-Build, for example. - cmake_generator = os.environ.get("CMAKE_GENERATOR", "") - - # Set Python_EXECUTABLE instead if you use PYBIND11_FINDPYTHON - # EXAMPLE_VERSION_INFO shows you how to pass a value into the C++ code - # from Python. - cmake_args = [ - f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}{os.sep}", - f"-DPYTHON_EXECUTABLE={sys.executable}", - f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm - ] - build_args = [] - # Adding CMake arguments set as environment variable - # (needed e.g. to build for ARM OSx on conda-forge) - if "CMAKE_ARGS" in os.environ: - cmake_args += [item for item in os.environ["CMAKE_ARGS"].split(" ") if item] - - if "CMAKE_PREFIX_PATH" in os.environ: - cmake_args += [f"-DCMAKE_PREFIX_PATH={os.environ['CMAKE_PREFIX_PATH']}"] - - # In this example, we pass in the version to C++. You might not need to. - cmake_args += [f"-DEXAMPLE_VERSION_INFO={self.distribution.get_version()}"] - - if self.compiler.compiler_type != "msvc": - # Using Ninja-build since it a) is available as a wheel and b) - # multithreads automatically. MSVC would require all variables be - # exported for Ninja to pick it up, which is a little tricky to do. - # Users can override the generator with CMAKE_GENERATOR in CMake - # 3.15+. - if not cmake_generator or cmake_generator == "Ninja": - try: - import ninja - - ninja_executable_path = Path(ninja.BIN_DIR) / "ninja" - cmake_args += [ - "-GNinja", - f"-DCMAKE_MAKE_PROGRAM:FILEPATH={ninja_executable_path}", - ] - except ImportError: - pass - - else: - # Single config generators are handled "normally" - single_config = any(x in cmake_generator for x in {"NMake", "Ninja"}) - - # CMake allows an arch-in-generator style for backward compatibility - contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"}) - - # Specify the arch if using MSVC generator, but only if it doesn't - # contain a backward-compatibility arch spec already in the - # generator name. - if not single_config and not contains_arch: - cmake_args += ["-A", PLAT_TO_CMAKE[self.plat_name]] - - # Multi-config generators have a different way to specify configs - if not single_config: - cmake_args += [ - f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{cfg.upper()}={extdir}" - ] - build_args += ["--config", cfg] - - if sys.platform.startswith("darwin"): - # Cross-compile support for macOS - respect ARCHFLAGS if set - archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", "")) - if archs: - cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))] - - # Set CMAKE_BUILD_PARALLEL_LEVEL to control the parallel build level - # across all generators. - if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ: - # self.parallel is a Python 3 only way to set parallel jobs by hand - # using -j in the build_ext call, not supported by pip or PyPA-build. - if hasattr(self, "parallel") and self.parallel: - # CMake 3.12+ only. - build_args += [f"-j{self.parallel}"] - - build_temp = Path(self.build_temp) / ext.name - if not build_temp.exists(): - build_temp.mkdir(parents=True) - - subprocess.run( - ["cmake", ext.sourcedir, *cmake_args], cwd=build_temp, check=True - ) - subprocess.run( - ["cmake", "--build", ".", *build_args], cwd=build_temp, check=True - ) - - -setup( - name="py-demo", - version="0.0.0", - author="Sergei Izmailov", - author_email="sergei.a.izmailov@gmail.com", - description="Demo package to test stub generation", - long_description=open("README.rst").read(), - ext_modules=[CMakeExtension("demo._bindings", "bindings")], - packages=find_packages(), - cmdclass=dict(build_ext=CMakeBuild), - url="https://github.com/sizmailov/pybind11-project-example", - zip_safe=False, - install_requires=["numpy"], -) diff --git a/tox.ini b/tox.ini index 8481434b..67a8aad0 100644 --- a/tox.ini +++ b/tox.ini @@ -1,61 +1,61 @@ [tox] requires = tox>=4 uv_mode = true +skip_missing_interpreters = false env_list = - py313-pb30-nutv - py{310,311,312,313}-pb30-naa - py{310,311,312,313}-pb213-naa - py313-pb29-naa - py313-pb211-naa - py313-pb212-naa - py313-pb213-nutv -skipsdist = true -isolated_build = true + py{310,311,312,313}-pb30 + py{310,311,312,313}-pb213 + py313-pb29 + py313-pb211 + py313-pb212 [testenv] -description = Check generated demo stubs and errors -package = skip - +description = Check installed demo stubs and errors across configured NumPy modes +package = wheel +wheel_build_env = .pkg setenv = PYTHONUNBUFFERED = 1 - - pb29: PYBIND11_BRANCH = v2.9 + STUBGEN_TEST_INSTALLED = 1 + STUBGEN_NUMPY_FORMATS = numpy-array-wrap-with-annotated + py313-pb{213,30}: STUBGEN_NUMPY_FORMATS = numpy-array-wrap-with-annotated numpy-array-use-type-var + pb29: PYBIND11_BRANCH = v2.9 pb211: PYBIND11_BRANCH = v2.11 pb212: PYBIND11_BRANCH = v2.12 pb213: PYBIND11_BRANCH = v2.13 pb30: PYBIND11_BRANCH = v3.0 - - naa: NUMPY_FORMAT = numpy-array-wrap-with-annotated - nutv: NUMPY_FORMAT = numpy-array-use-type-var - - PROJECT_ROOT = {toxinidir} - TESTS_ROOT = {env:PROJECT_ROOT}/tests - CMAKE = {envbindir}/cmake - -allowlist_externals = - bash - git - uv - + pb29: STUBGEN_PYBIND11_VERSION = 2.9.2 + pb211: STUBGEN_PYBIND11_VERSION = 2.11.2 + pb212: STUBGEN_PYBIND11_VERSION = 2.12.1 + pb213: STUBGEN_PYBIND11_VERSION = 2.13.6 + pb30: STUBGEN_PYBIND11_VERSION = 3.0.4 + pb29: STUBGEN_CMAKE_VERSION = 3.31.10 + pb{211,212}: STUBGEN_CMAKE_VERSION = 3.28.3 + pb{213,30}: STUBGEN_CMAKE_VERSION = 4.2.3 + py310: STUBGEN_NUMPY_VERSION = 2.2.6 + py311: STUBGEN_NUMPY_VERSION = 2.4.6 + py{312,313}: STUBGEN_NUMPY_VERSION = 2.5.3 + py310: STUBGEN_SCIPY_VERSION = 1.15.3 + py{311,312,313}: STUBGEN_SCIPY_VERSION = 1.17.1 + STUBGEN_CMEEL_VERSION = 0.59.0 + STUBGEN_CMEEL_EIGEN_VERSION = 3.4.0.2 + STUBGEN_SCIKIT_BUILD_CORE_VERSION = 1.0.3 + STUBGEN_NINJA_VERSION = 1.13.2 +allowlist_externals = uv deps = pytest>=8,<9 - cmeel==0.59.0 - cmeel-eigen==3.4.0.2 - numpy~=1.20 - scipy~=1.0 typing_extensions>=4,<5 - pb29: cmake==3.31.* - pb211: cmake==3.28.3 - pb212: cmake==3.28.3 - pb213: cmake==4.2.* - pb30: cmake==4.2.* - + cmeel=={env:STUBGEN_CMEEL_VERSION} + cmeel-eigen=={env:STUBGEN_CMEEL_EIGEN_VERSION} + numpy=={env:STUBGEN_NUMPY_VERSION} + scipy=={env:STUBGEN_SCIPY_VERSION} + pybind11=={env:STUBGEN_PYBIND11_VERSION} + cmake=={env:STUBGEN_CMAKE_VERSION} + scikit-build-core=={env:STUBGEN_SCIKIT_BUILD_CORE_VERSION} + ninja=={env:STUBGEN_NINJA_VERSION} commands_pre = - bash -c 'cmake="${CMAKE}" {env:TESTS_ROOT}/install-demo-module.sh' - + {envpython} {toxinidir}/tests/build_native.py commands = - uv pip install --python "{envpython}" . - {envpython} -m pytest {toxinidir}/tests/test_snapshot_helpers.py {toxinidir}/tests/test_demo_stubs.py {toxinidir}/tests/test_demo_errors.py --pybind11-branch {env:PYBIND11_BRANCH} --numpy-format {env:NUMPY_FORMAT} --artifacts-dir {envtmpdir}/pytest-artifacts {posargs} + {envpython} -I -m pytest {toxinidir}/tests/test_snapshot_helpers.py {toxinidir}/tests/test_demo_stubs.py {toxinidir}/tests/test_demo_errors.py --pybind11-branch {env:PYBIND11_BRANCH} --artifacts-dir {envtmpdir}/pytest-artifacts --junitxml {envtmpdir}/native.xml --maxfail=0 {posargs} [testenv:py{310,311,312,313}-unit] description = Check compiler-free production contracts and snapshot helpers From 2344b79897da79c0f4c03988e371134c88b422ea Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 07:52:50 +0000 Subject: [PATCH 31/40] test: derive native CI jobs from tox and consume the built wheel --- .github/workflows/ci.yml | 167 ++++-------------- .../2026-09-25-test-harness-phase-three.md | 76 ++++++-- tests/native_matrix.py | 38 ++++ tests/test_snapshot_helpers.py | 15 +- tests/unit/test_native_matrix.py | 88 +++++++++ 5 files changed, 237 insertions(+), 147 deletions(-) create mode 100644 tests/native_matrix.py create mode 100644 tests/unit/test_native_matrix.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f19f323..2980b416 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,8 @@ jobs: name: Build wheel runs-on: ubuntu-latest needs: [check] + outputs: + native-matrix: ${{ steps.native-matrix.outputs.matrix }} steps: - uses: actions/checkout@v5 - uses: actions/setup-python@v5 @@ -40,6 +42,16 @@ jobs: - uses: astral-sh/setup-uv@v7 + - name: Derive native test matrix from tox + id: native-matrix + shell: bash + run: | + set -euo pipefail + uv run --no-project --isolated --with tox --with tox-uv \ + tox list -d --no-desc > "$RUNNER_TEMP/native-envs.txt" + matrix=$(python tests/native_matrix.py "$RUNNER_TEMP/native-envs.txt") + printf 'matrix=%s\n' "$matrix" >> "$GITHUB_OUTPUT" + - name: Build sdist + wheel run: | uv lock --check @@ -80,152 +92,47 @@ jobs: run: .venv/bin/python -I -m pytest tests/unit tests/test_snapshot_helpers.py tests: - name: "Test 🐍 ${{ matrix.config.python }} • pybind-${{ matrix.config.pybind11-branch }} • ${{ matrix.config.numpy-format }}" + name: "Native tests • ${{ matrix.env }}" runs-on: ubuntu-latest needs: [build] strategy: fail-fast: false - matrix: - config: - [ - { - pybind11-branch: "v3.0", - python: "3.13", - numpy-format: "numpy-array-use-type-var", - cmake-version: "4.2.x", - }, - { - pybind11-branch: "v3.0", - python: "3.13", - numpy-format: "numpy-array-wrap-with-annotated", - cmake-version: "4.2.x", - }, - { - pybind11-branch: "v3.0", - python: "3.12", - numpy-format: "numpy-array-wrap-with-annotated", - cmake-version: "4.2.x", - }, - { - pybind11-branch: "v3.0", - python: "3.11", - numpy-format: "numpy-array-wrap-with-annotated", - cmake-version: "4.2.x", - }, - { - pybind11-branch: "v3.0", - python: "3.10", - numpy-format: "numpy-array-wrap-with-annotated", - cmake-version: "4.2.x", - }, - { - pybind11-branch: "v2.13", - python: "3.13", - numpy-format: "numpy-array-use-type-var", - cmake-version: "4.2.x", - }, - { - pybind11-branch: "v2.13", - python: "3.13", - numpy-format: "numpy-array-wrap-with-annotated", - cmake-version: "4.2.x", - }, - { - pybind11-branch: "v2.13", - python: "3.12", - numpy-format: "numpy-array-wrap-with-annotated", - cmake-version: "4.2.x", - }, - { - pybind11-branch: "v2.13", - python: "3.11", - numpy-format: "numpy-array-wrap-with-annotated", - cmake-version: "4.2.x", - }, - { - pybind11-branch: "v2.13", - python: "3.10", - numpy-format: "numpy-array-wrap-with-annotated", - cmake-version: "4.2.x", - }, - { - pybind11-branch: "v2.12", - python: "3.13", - numpy-format: "numpy-array-wrap-with-annotated", - cmake-version: "3.28.3", - }, - { - pybind11-branch: "v2.11", - python: "3.13", - numpy-format: "numpy-array-wrap-with-annotated", - cmake-version: "3.28.3", - }, - { - pybind11-branch: "v2.9", - python: "3.13", - numpy-format: "numpy-array-wrap-with-annotated", - cmake-version: "3.28.3", - }, - ] + matrix: ${{ fromJSON(needs.build.outputs.native-matrix) }} steps: - uses: actions/checkout@v5 - - - name: Download build artifacts - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v6 with: name: dist path: dist - - - name: Setup Python ${{ matrix.config.python }} - uses: actions/setup-python@v5 + - uses: actions/setup-python@v5 with: - python-version: ${{ matrix.config.python }} - + python-version: ${{ matrix.python }} - uses: astral-sh/setup-uv@v7 - - - name: Update CMake - uses: jwlawson/actions-setup-cmake@v2 - with: - cmake-version: ${{ matrix.config.cmake-version }} - - - name: Create UV venv - run: | - uv venv .venv - - - name: Setup annotations on Linux - if: runner.os == 'Linux' - run: uv pip install pytest-github-actions-annotate-failures - - - name: Install requirements - run: uv sync --group dev --no-install-project - - - name: Install - shell: bash - working-directory: dist - run: uv pip install --disable-pip-version-check *.whl - - - name: Install demo module - shell: bash - run: | - source .venv/bin/activate - ./tests/install-demo-module.sh --pybind11-branch "${{ matrix.config.pybind11-branch }}" - - - name: Check installed wheel and demo snapshots + - name: Test supplied wheel through shared tox profile shell: bash + env: + TOX_PROFILE: ${{ matrix.env }} run: | - source .venv/bin/activate - python -I -c 'import sys; from pathlib import Path; import pybind11_stubgen; p = Path(pybind11_stubgen.__file__).resolve(); assert p.is_relative_to(Path(sys.prefix).resolve()), p; print(p)' - python -m pytest tests/test_snapshot_helpers.py tests/test_demo_stubs.py tests/test_demo_errors.py \ - --pybind11-branch "${{ matrix.config.pybind11-branch }}" \ - --numpy-format "${{ matrix.config.numpy-format }}" \ - --artifacts-dir tmp/pytest-artifacts/ --maxfail=0 - - - name: Archive test diagnostics + set -euo pipefail + shopt -s nullglob + wheels=(dist/*.whl) + test "${#wheels[@]}" -eq 1 + uv run --no-project --isolated --with tox --with tox-uv \ + tox -e "$TOX_PROFILE" --installpkg "${wheels[0]}" + - name: Archive native build and test diagnostics uses: actions/upload-artifact@v6 if: failure() with: - name: "python-${{ matrix.config.python }}-pybind-${{ matrix.config.pybind11-branch }}-${{ matrix.config.numpy-format }}-diagnostics" - path: tmp/pytest-artifacts/ + name: "${{ matrix.env }}-diagnostics" + include-hidden-files: true + path: | + .tox/${{ matrix.env }}/log/ + .tox/${{ matrix.env }}/tmp/native/**/evidence.json + .tox/${{ matrix.env }}/tmp/native/**/*.log + .tox/${{ matrix.env }}/tmp/native/**/CMakeCache.txt + .tox/${{ matrix.env }}/tmp/native/**/CMakeConfigureLog.yaml + .tox/${{ matrix.env }}/tmp/pytest-artifacts/ + .tox/${{ matrix.env }}/tmp/native.xml retention-days: 30 if-no-files-found: ignore diff --git a/docs/superpowers/plans/2026-09-25-test-harness-phase-three.md b/docs/superpowers/plans/2026-09-25-test-harness-phase-three.md index e3a1da0e..6eeb89f9 100644 --- a/docs/superpowers/plans/2026-09-25-test-harness-phase-three.md +++ b/docs/superpowers/plans/2026-09-25-test-harness-phase-three.md @@ -45,6 +45,7 @@ | `tox.ini` | Single native profile/dependency/mode authority, local wheel packaging, installed pytest execution. | | `tests/native_matrix.py` (new) | Validate expanded tox names and emit compact CI matrix JSON. | | `tests/unit/test_native_matrix.py` (new) | Independent case inventory, adapter validation, real CLI failures. | +| `tests/test_snapshot_helpers.py` | Modernize only `test_entry_points_use_pytest_without_legacy_mutating_checks` for CI-to-tox-to-installed-pytest delegation; preserve every other harness self-test and the test count. | | `.github/workflows/ci.yml` | Derive native matrix, invoke tox with the downloaded wheel, retain early and late failure diagnostics. | | `tests/README.md` | Commands, renamed profiles, dependency policy, serial updates, and verification boundaries. | @@ -60,7 +61,7 @@ git check-ignore -q "$E" mkdir -p "$E" ``` -Set `E` in each shell invocation that uses it; tool calls do not share shell variables. After building a generator wheel, save its absolute path in `$E/generator-wheel.path` and reload `WHEEL` from that file in later commands. Keep logs, disposable source copies, command/status records, and per-task reports there. Do not read or reuse another plan's scratch helpers. Capture before/after reference and index states for every native verification batch. Intentional commits change the index between batches; comparisons are within each batch, not against the pre-implementation index forever. +Set `E` in each shell invocation that uses it; tool calls do not share shell variables. After building a generator wheel, save its absolute path in `$E/generator-wheel.path` and independently record its SHA-256 in `$E/generator-wheel.sha256` before invoking tox. Reload `WHEEL` and `WHEEL_SHA256` from those files in later commands; verify the digest is unchanged afterward. Keep logs, disposable source copies, command/status records, and per-task reports there. Do not read or reuse another plan's scratch helpers. Capture before/after reference and index states for every native verification batch. Intentional commits change the index between batches; comparisons are within each batch, not against the pre-implementation index forever. Fast checks, repeated on both endpoint interpreters during each task: @@ -927,6 +928,7 @@ git commit -m "test: build pinned native fixtures once per tox profile" **Files:** - Create: `tests/native_matrix.py`, `tests/unit/test_native_matrix.py`. - Modify: `.github/workflows/ci.yml`. +- Narrow correction: only `test_entry_points_use_pytest_without_legacy_mutating_checks` in `tests/test_snapshot_helpers.py` (plus a local stdlib import), and this plan's affected scope/probe/preservation passages. **Interfaces:** - Consumes the expanded default native names from `tox list -d --no-desc`; consumes Task 3's `--installpkg` route, naming contract, and artifact locations. @@ -1151,6 +1153,8 @@ Replace the existing `tests` job, not the unit/gemmi/check/build-publication log `include-hidden-files` is needed for `.tox` artifacts. Restrict paths to logs/evidence, not whole environments or fixture wheels. The old native-only annotation installation step is removed with the duplicate setup route; pytest/JUnit diagnostics remain available. No changes to gemmi's annotation step, release credentials, or publication conditions. +Modernize only the existing entry-point regression named in the file scope: the retained failures establish RED because the obsolete direct-CI `-m pytest` assertion conflicts with approved tox delegation. Require CI's shared profile/`--installpkg` invocation and native `[testenv]` installed pytest execution specifically (stdlib `ConfigParser`, not a match accidentally satisfied by unit commands). Preserve legacy-script, automatic-update, dependency, and diagnostic guards and all other self-tests. Run process-local negative controls for broken CI delegation and removed native pytest, then restored focused and cumulative greens; no misleading workflow comments or new test dependencies. + - [ ] **Step 5: Exercise the supplied-wheel route locally and commit.** Build the generator wheel from a clean tracked-source archive (including Task 3's committed tree). Use fresh directory names for later repetitions: @@ -1169,9 +1173,16 @@ print(wheel.resolve()) PY ) printf '%s\n' "$WHEEL" > "$E/generator-wheel.path" +python - "$WHEEL" > "$E/generator-wheel.sha256" <<'PY' +import hashlib +from pathlib import Path +import sys +print(hashlib.sha256(Path(sys.argv[1]).read_bytes()).hexdigest()) +PY +WHEEL_SHA256=$(< "$E/generator-wheel.sha256") ``` -Record its SHA-256 and verify its production files match the current tracked production tree. Do not change generator package discovery or clean unrelated worktree build output. +Record this digest before invoking tox and verify its production files match the current tracked production tree. Do not change generator package discovery or clean unrelated worktree build output. Run the CI command locally across all 11 profiles, with the batch audit and an absolute wheel path: @@ -1181,7 +1192,9 @@ python "$E/audit_native.py" --label supplied-wheel -- \ tox --installpkg "$WHEEL" ``` -Inspect tox package-install records: they must name the supplied wheel and must not build/reinstall the generator from `.`. Distinguish expected fixture-wheel builds from a forbidden replacement generator build. Confirm installed origin within pytest. Save this artifact-identity probe as `$E/check_supplied_wheel.py`, then run it with each of the 11 native interpreters using `-I`, passing the absolute `$WHEEL` path: +Inspect and retain tox package-install records independently: each must name exactly the supplied wheel, exit zero, and not build/reinstall the generator from `.`. Distinguish expected fixture-wheel builds from a forbidden replacement generator build. Confirm installed origin within pytest. Investigation with uv 0.12.19 established that local wheel installs record the exact file URL with `archive_info: {}`; a mandatory PEP 610 hash was an unsupported assumption, not an installer defect to work around. Keep uv and `--installpkg` unchanged. Combine the independently saved pre-run hash, exact recorded file URL, post-run artifact digest, installed inventory/bytes/origin, and command records; none alone suffices. + +Save this scratch artifact-identity probe as `$E/check_supplied_wheel.py`, then run it with each of the 11 native interpreters as `".tox/$profile/bin/python" -I "$E/check_supplied_wheel.py" "$WHEEL" "$WHEEL_SHA256"`. The second argument must come from the saved pre-run digest, not a second computation of the current artifact: ```python import hashlib @@ -1189,12 +1202,45 @@ from importlib.metadata import distribution import json from pathlib import Path import sys +from urllib.parse import urlsplit +from urllib.request import url2pathname import zipfile +import re import pybind11_stubgen +assert len(sys.argv) == 3, 'expected absolute wheel path and saved pre-run SHA-256' wheel = Path(sys.argv[1]) -digest = hashlib.sha256(wheel.read_bytes()).hexdigest() +assert wheel.is_absolute(), wheel +wheel = wheel.resolve(strict=True) +expected_digest = sys.argv[2] +assert re.fullmatch(r'[0-9a-f]{64}', expected_digest), expected_digest +payload = wheel.read_bytes() +digest = hashlib.sha256(payload).hexdigest() +assert digest == expected_digest, ('expected digest mismatch', digest, expected_digest) +info = json.loads(distribution('pybind11-stubgen').read_text('direct_url.json')) +assert isinstance(info, dict) and set(info) == {'url', 'archive_info'}, info +assert isinstance(info['url'], str), info +url = urlsplit(info['url']) +assert url.scheme == 'file' and not url.netloc and not url.query and not url.fragment, info +recorded_path = Path(url2pathname(url.path)) +assert recorded_path.is_absolute(), info +recorded_path = recorded_path.resolve(strict=True) +assert recorded_path == wheel, ('supplied path mismatch', recorded_path, wheel) +assert hashlib.sha256(recorded_path.read_bytes()).hexdigest() == expected_digest == digest +archive_info = info['archive_info'] +assert isinstance(archive_info, dict) and set(archive_info) <= {'hash', 'hashes'}, info +hashes = archive_info.get('hashes', {}) +assert isinstance(hashes, dict), info +provided_hashes = list(hashes.items()) +if 'hash' in archive_info: + value = archive_info['hash'] + assert isinstance(value, str) and value.count('=') == 1, info + provided_hashes.append(tuple(value.split('='))) +for algorithm, value in provided_hashes: + assert isinstance(algorithm, str) and isinstance(value, str), info + # Unknown algorithms/schemas fail rather than silently bypassing evidence. + assert hashlib.new(algorithm, payload).hexdigest() == value, info origin = Path(pybind11_stubgen.__file__).resolve() assert origin.is_relative_to(Path(sys.prefix).resolve()), origin installed = origin.parent.parent @@ -1205,14 +1251,13 @@ with zipfile.ZipFile(wheel) as archive: assert names == actual for name in names: assert archive.read(name) == (installed / name).read_bytes(), name -info = json.loads(distribution('pybind11-stubgen').read_text('direct_url.json')) -archive_info = info['archive_info'] -assert (archive_info.get('hashes', {}).get('sha256') == digest - or archive_info.get('hash') == 'sha256=' + digest), info -print(json.dumps({'origin': str(origin), 'wheel_sha256': digest, 'direct_url': info})) +print(json.dumps({'python': sys.version, 'prefix': sys.prefix, 'origin': str(origin), + 'wheel': str(wheel), 'recorded_path': str(recorded_path), + 'expected_sha256': expected_digest, 'wheel_sha256': digest, + 'production_files': sorted(names), 'direct_url': info})) ``` -If the installer records an unexpected provenance schema, investigate rather than removing the artifact identity check; origin and equal source files alone are insufficient. Run focused and cumulative tests on 3.10/3.13. Run this mutation with isolated uv supplying pytest, then restore an unpatched green run. Run YAML/all applicable hooks. +Empty `archive_info` is explicitly supported; any supplied hashes must also match, and contradictory hashes, unexpected schemas, remote authority, query, and fragment are rejected. Add scratch negative controls with a wrong expected digest and a distinct same-production-payload wheel/path, requiring identity failure for both; restore the true expected artifact and rerun green. Retain earlier failures. Confirm the saved digest still matches after tox, run a fresh all-11 batch under a unique audit label, and retain strict probe results plus exact install records. Run focused and cumulative tests on 3.10/3.13. Run this mutation with isolated uv supplying pytest, then restore an unpatched green run. Run YAML/all applicable hooks. ```python import sys @@ -1237,7 +1282,8 @@ assert code == pytest.ExitCode.TESTS_FAILED, code ``` ```sh -git add tests/native_matrix.py tests/unit/test_native_matrix.py .github/workflows/ci.yml +git add tests/native_matrix.py tests/unit/test_native_matrix.py .github/workflows/ci.yml \ + tests/test_snapshot_helpers.py docs/superpowers/plans/2026-09-25-test-harness-phase-three.md git commit -m "test: derive native CI jobs from tox and consume the built wheel" ``` @@ -1334,7 +1380,7 @@ uv run --no-project --isolated --with tox --with tox-uv tox \ -e py310-unit,py311-unit,py312-unit,py313-unit ``` -Build `$WHEEL` from the final tracked implementation first, not from a stale task snapshot. Reuse Task 4's archive/build procedure with new `final-source`/`final-dist` directories and update `generator-wheel.path`. Test the installed guard's red/green behavior on a narrow selection: from the checkout, run the fresh wheel environment's Python without `-I`, with `STUBGEN_TEST_INSTALLED=1`, on `tests/unit/test_pipeline.py::test_python_module_pipeline`; require a setup error naming the checkout origin. Restore `-I` and require a passing test, then run the full unpatched selection. +Build `$WHEEL` from the final tracked implementation first, not from a stale task snapshot. Reuse Task 4's archive/build procedure with new `final-source`/`final-dist` directories and update `generator-wheel.path` and the independently captured pre-run `generator-wheel.sha256`; reload both `WHEEL` and `WHEEL_SHA256` before acceptance. Test the installed guard's red/green behavior on a narrow selection: from the checkout, run the fresh wheel environment's Python without `-I`, with `STUBGEN_TEST_INSTALLED=1`, on `tests/unit/test_pipeline.py::test_python_module_pipeline`; require a setup error naming the checkout origin. Restore `-I` and require a passing test, then run the full unpatched selection. - [ ] **Step 3: Re-run native acceptance and inspect exact cases, reuse, and pins.** @@ -1366,7 +1412,7 @@ assert len(seen) == len(set(seen)) == 13 Sorting here compares case inventories only; never sort diagnostic stderr or expected output. Check the JUnit runtime properties against the explicit version table. For each dual-mode profile, correlate its single fixture build command/evidence record with both pytest mode IDs and the unchanged extension digest. Check `evidence.json` before/after versions, CMake's selected version/paths, and the audited fixture payload. -Use `--result-json` on tox verification runs when useful to retain the package installation command list. Verify installed generator files against `$WHEEL` with `zipfile`/hashes inside each environment; do not infer artifact identity solely from a site-packages path. +Use `--result-json` on tox verification runs when useful to retain the package installation command list. Run Task 4's strict probe inside every supplied-wheel environment with `-I "$E/check_supplied_wheel.py" "$WHEEL" "$WHEEL_SHA256"`, consuming the saved pre-run hash. Require unchanged post-run digest, exact local recorded file URL, complete installed production inventory/bytes and origin, and independently retained successful exact-wheel install commands with no generator source replacement. Support the investigated empty `archive_info` only as part of this complete evidence scheme; verify any hashes present. Repeat wrong-digest and same-payload wrong-path negative controls and restored greens; do not infer artifact identity solely from a site-packages path. - [ ] **Step 4: Demonstrate bounded parallel isolation and sibling-source freshness.** @@ -1408,13 +1454,15 @@ Audit protected paths against `807e7e4`: ```sh git diff --exit-code 807e7e4 -- pybind11_stubgen tests/py-demo/demo \ tests/demo-lib tests/py-demo/bindings/src \ - tests/stubs tests/errors tests/snapshot_helpers.py tests/test_snapshot_helpers.py \ + tests/stubs tests/errors tests/snapshot_helpers.py \ tests/test_demo_stubs.py tests/test_demo_errors.py pyproject.toml uv.lock uv lock --check uv run pre-commit run --all-files git diff --check ``` +Separately compare `tests/test_snapshot_helpers.py` against `807e7e4`: only `test_entry_points_use_pytest_without_legacy_mutating_checks` may differ for Task 4's justified CI-to-tox-to-installed-pytest modernization (including its local stdlib import). Verify all other functions and test count unchanged, and legacy/update/dependency/diagnostic guards retained. This is not a general exemption for harness behavior. + Review the remaining CMake/fixture metadata/tox/CI changes manually against the spec. Confirm gemmi, release triggers/permissions/conditions, root package discovery, and all snapshot aliases are unchanged. No old installer calls or old native environment examples should remain in active runner/docs files (historical design/plan documents may retain them). Write a final report containing exact commits, command exit statuses, case inventories, observed versions/build locations, wheel hashes/origins, reference/index audit results, negative-control failures and restored greens, plus the explicitly deferred remote acceptance. Retain logs for both early build failures and ordinary pytest failures. Do not claim actual GitHub upload behavior from YAML validation. diff --git a/tests/native_matrix.py b/tests/native_matrix.py new file mode 100644 index 00000000..b41a00bf --- /dev/null +++ b/tests/native_matrix.py @@ -0,0 +1,38 @@ +import argparse +import json +import re +import sys +from pathlib import Path + +from snapshot_helpers import BRANCHES + + +def matrix_from_names(names: list[str]) -> dict[str, list[dict[str, str]]]: + if not names: + raise ValueError("No default native environments") + if len(names) != len(set(names)): + raise ValueError("Duplicate native environments") + rows = [] + for name in names: + match = re.fullmatch(r"py(3)(1[0-3])-pb([23])(0|[1-9]\d?)", name) + if match is None or f"v{match[3]}.{int(match[4])}" not in BRANCHES: + raise ValueError(f"Unsupported native environment: {name!r}") + rows.append({"env": name, "python": f"{match[1]}.{match[2]}"}) + return {"include": rows} + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("names_file", type=Path) + args = parser.parse_args() + try: + matrix = matrix_from_names(args.names_file.read_text().splitlines()) + except (OSError, ValueError) as exc: + print(str(exc), file=sys.stderr) + return 1 + print(json.dumps(matrix, separators=(",", ":"))) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_snapshot_helpers.py b/tests/test_snapshot_helpers.py index ca29d598..50f09f36 100644 --- a/tests/test_snapshot_helpers.py +++ b/tests/test_snapshot_helpers.py @@ -866,14 +866,23 @@ def erase_output(output, repo, workspace, python_version): def test_entry_points_use_pytest_without_legacy_mutating_checks(): + from configparser import ConfigParser + repo = Path(__file__).resolve().parents[1] tox = (repo / "tox.ini").read_text() ci = (repo / ".github/workflows/ci.yml").read_text() - assert "-m pytest" in tox and "{posargs}" in tox - assert "pytest>=8,<9" in tox + config = ConfigParser(interpolation=None) + config.read_string(tox) + native = config["testenv"] + assert "{envpython} -I -m pytest" in native["commands"] + assert "{posargs}" in native["commands"] + assert "STUBGEN_TEST_INSTALLED = 1" in native["setenv"] + assert "pytest>=8,<9" in native["deps"] + assert "--update-snapshots" not in native["commands"] assert "PYTHON_TAG_FILE" not in tox native_job = ci.split(" tests:\n", 1)[1].split(" test-cli-options:\n", 1)[0] - assert "-m pytest" in native_job + assert "TOX_PROFILE: ${{ matrix.env }}" in native_job + assert 'tox -e "$TOX_PROFILE" --installpkg "${wheels[0]}"' in native_job assert "tmp/pytest-artifacts/" in native_job assert "--update-snapshots" not in native_job for script in ("check-demo-stubs-generation.sh", "check-demo-errors-generation.sh"): diff --git a/tests/unit/test_native_matrix.py b/tests/unit/test_native_matrix.py new file mode 100644 index 00000000..e720a5ed --- /dev/null +++ b/tests/unit/test_native_matrix.py @@ -0,0 +1,88 @@ +import json +import subprocess +import sys +from pathlib import Path + +import pytest + +from native_matrix import matrix_from_names + +EXPECTED_NAMES = [ + "py310-pb30", + "py311-pb30", + "py312-pb30", + "py313-pb30", + "py310-pb213", + "py311-pb213", + "py312-pb213", + "py313-pb213", + "py313-pb29", + "py313-pb211", + "py313-pb212", +] + + +def test_explicit_profile_inventory_and_interpreters(): + matrix = matrix_from_names(EXPECTED_NAMES) + assert matrix == { + "include": [ + {"env": "py310-pb30", "python": "3.10"}, + {"env": "py311-pb30", "python": "3.11"}, + {"env": "py312-pb30", "python": "3.12"}, + {"env": "py313-pb30", "python": "3.13"}, + {"env": "py310-pb213", "python": "3.10"}, + {"env": "py311-pb213", "python": "3.11"}, + {"env": "py312-pb213", "python": "3.12"}, + {"env": "py313-pb213", "python": "3.13"}, + {"env": "py313-pb29", "python": "3.13"}, + {"env": "py313-pb211", "python": "3.13"}, + {"env": "py313-pb212", "python": "3.13"}, + ] + } + + +@pytest.mark.parametrize( + "names", + [ + [], + ["py313-pb30"] * 2, + ["py313-unit"], + ["py39-pb30"], + ["py313-pb99"], + ["py313-pb30-naa"], + ["py313-pb300"], + ["py313-pb209"], + [""], + [" py313-pb30"], + ], +) +def test_rejects_missing_duplicate_or_unsupported_profiles(names): + with pytest.raises(ValueError): + matrix_from_names(names) + + +def test_cli_json_and_nonzero_failure(tmp_path): + script = Path(__file__).resolve().parents[1] / "native_matrix.py" + names = tmp_path / "names.txt" + names.write_text("py313-pb30\n") + result = subprocess.run( + [sys.executable, str(script), str(names)], + capture_output=True, + text=True, + timeout=30, + ) + assert result.returncode == 0, result.stderr + assert result.stdout == '{"include":[{"env":"py313-pb30","python":"3.13"}]}\n' + assert json.loads(result.stdout) == { + "include": [{"env": "py313-pb30", "python": "3.13"}] + } + names.write_text("py313-pb30\npy313-pb30\n") + result = subprocess.run( + [sys.executable, str(script), str(names)], + capture_output=True, + text=True, + timeout=30, + ) + assert result.returncode != 0 + assert result.stdout == "" + assert "Duplicate" in result.stderr From 49be65f885660c262f07c7c54134ce4ba44c6945 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 08:24:12 +0000 Subject: [PATCH 32/40] docs: document grouped native tests and verified dependency policy --- tests/README.md | 75 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/tests/README.md b/tests/README.md index 615ef57b..9e408d73 100644 --- a/tests/README.md +++ b/tests/README.md @@ -9,9 +9,11 @@ is a separate, explicit operation. ## Prerequisites -Use Python 3.10 or newer. Native tests additionally need Git, a C++17 compiler, -uv, and tox with tox-uv. Tox installs the existing CMake/Eigen/Python dependencies. -Initial builds clone pybind11; normalization may download the pinned Ruff tool. +Use Python 3.10 or newer for compiler-free tests. Native checks require Python +3.10–3.13 for the full matrix, uv, tox with tox-uv, and a C++17 compiler. Tox +installs pinned pybind11 distributions, CMake, scikit-build-core, Ninja, +cmeel/Eigen, NumPy, and SciPy. It no longer clones pybind11 or builds/installs +demo-lib separately. Normalization still uses the existing pinned Ruff tool. ```sh uv python install 3.10 3.11 3.12 3.13 @@ -54,40 +56,61 @@ uv run --no-project --with tox --with tox-uv tox \ The unit environments install with uv, run isolated Python, and verify the actual pytest process imports the installed generator. The default tox list -still runs the existing 13 native configurations; unit environments are explicit. +runs 11 native build profiles covering 13 cases; unit environments are explicit. CI adds a separate compiler-free wheel matrix while retaining native/gemmi jobs. ## Native checks +The default matrix has 11 native build profiles covering the same 13 cases. +For Python 3.13 with pybind11 2.13 or 3.0, both NumPy rendering modes share one +installed fixture. Other profiles use annotated arrays. The old `-naa` and +`-nutv` tox suffixes are replaced by a mode selection within the grouped profile. + ```sh -tox -e py313-pb30-naa # One environment: Python 3.13, pybind11 3.0, annotated arrays. -tox -e py313-pb30-naa -- -k demo_errors # One check in that environment. -tox # Full configured matrix, serially. +tox -e py313-pb30 +tox -e py313-pb30 -- -k demo_errors +tox -e py313-pb30 -- --numpy-format numpy-array-use-type-var +tox +tox -e py313-pb30 --installpkg dist/pybind11_stubgen-3.0.0-py3-none-any.whl ``` -Environment suffix `naa` means `numpy-array-wrap-with-annotated`; `nutv` means -`numpy-array-use-type-var`. Keep runs serial: the existing native build and some -reference directories are shared. Do not use `tox -p` or pytest workers yet. +The supplied-wheel example assumes the current generator wheel has already +been built in `dist/`; use the actual filename when the project version changes. + +Tox's selected pybind11/CMake and runtime dependency versions are authoritative +for native checks both locally and in CI. NumPy/SciPy are pinned to the observed +phase-two runtime versions; fixture installation cannot replace them. The root +development dependency group is not the native test environment. -After tox has installed the project and matching demo, a check can be rerun -without rebuilding: +Local native tox installs a generator wheel. CI supplies its existing build +artifact through the same runner. Installed origins are checked inside pytest. +After a matching tox run, a single check can be repeated without rebuilding: ```sh -.tox/py313-pb30-naa/bin/python -m pytest tests/test_demo_stubs.py \ - --pybind11-branch v3.0 --numpy-format numpy-array-wrap-with-annotated +STUBGEN_TEST_INSTALLED=1 .tox/py313-pb30/bin/python -I -m pytest \ + tests/test_demo_stubs.py --pybind11-branch v3.0 \ + --numpy-format numpy-array-use-type-var ``` -That command tests the installed generator, not uninstalled source edits. Rerun -tox after changing generator or native fixture code. Missing configuration or a -missing demo is an error, not a skipped test. +That direct rerun checks installed origins and snapshots but does not reconstruct +tox's dependency-pin environment. Rerun tox after generator/fixture changes or +to validate the configured pins and rebuild the fixture. Missing configuration +or a missing demo is an error, not a skipped test. + +Each profile owns its build directories and diagnostics. Checks can use bounded +parallel tox execution after the isolation acceptance test; they do not require +pytest workers. Reference updates remain explicit and serial. ## Update references deliberately ```sh -tox -e py313-pb30-naa -- --update-snapshots +tox -e py313-pb30 -- --numpy-format numpy-array-wrap-with-annotated --update-snapshots git diff -- tests/stubs tests/errors ``` +Do not update shared references concurrently. Existing aliases and per-test, +nontransactional update semantics are unchanged. + Use `-k demo_stubs` or `-k demo_errors` with the update option to select one kind of expectation. Generation, exit-status, no-output, and formatting assertions still apply in update mode. A broken command cannot become a new expectation. @@ -107,8 +130,13 @@ review the diff; do not accept unexplained changes from a bulk regeneration. Failures show the configuration, resolved reference location, file-set changes, content diffs, and process diagnostics. Each check uses a fresh temporary output directory. Optional `--artifacts-dir PATH` retains failure output outside the -reference trees. Tox uses its environment temporary directory; CI uploads -`tmp/pytest-artifacts/` with separate configuration/check/run subdirectories. +reference trees. Pytest diagnostics remain in the environment's +`tmp/pytest-artifacts/`, with distinct case/check/run directories. + +Build/install logs and fixture provenance are under +`.tox//tmp/native/run-*/`; tox command logs are under +`.tox//log/`. The native JUnit report is `tmp/native.xml`. CI's failure +upload includes these scoped logs and reports, not entire virtual environments. ## Add a regression @@ -122,6 +150,7 @@ explicitly update that environment's expectations. Review only the intended changes and run affected shared profiles and the compatibility matrix before merging. -During test-only phase two, report newly discovered production bugs separately. -Do not hide them with changed expectations, unexplained skips, or snapshot -updates; production fixes require a separate scope. +Test-infrastructure changes must not hide production bugs with changed +expectations, unexplained skips, or snapshot updates. Report bugs separately; +production fixes require a separate scope. The ordinary-Python positional-only +bug and dirty-tree generator packaging issue remain outside this migration. From 4c84bad5dfb95b63980be8c629c3c6a2746829cb Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 08:54:21 +0000 Subject: [PATCH 33/40] test: cover fixture wheel cardinality and clarify reference labels --- tests/README.md | 4 ++++ tests/unit/test_build_native.py | 35 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/tests/README.md b/tests/README.md index 9e408d73..cd68288a 100644 --- a/tests/README.md +++ b/tests/README.md @@ -82,6 +82,10 @@ for native checks both locally and in CI. NumPy/SciPy are pinned to the observed phase-two runtime versions; fixture installation cannot replace them. The root development dependency group is not the native test environment. +The retained labels `v2.9`, `v2.11`, `v2.12`, `v2.13`, and `v3.0` select reference +compatibility series (including via `--pybind11-branch`). They no longer designate +floating Git inputs: tox installs exact pinned pybind11 distribution versions. + Local native tox installs a generator wheel. CI supplies its existing build artifact through the same runner. Installed origins are checked inside pytest. After a matching tox run, a single check can be repeated without rebuilding: diff --git a/tests/unit/test_build_native.py b/tests/unit/test_build_native.py index d8f45f57..bbb8cad7 100644 --- a/tests/unit/test_build_native.py +++ b/tests/unit/test_build_native.py @@ -198,3 +198,38 @@ def test_wheel_payload_is_exactly_package_extension_and_metadata(tmp_path, extra (package / "__init__.py").write_bytes(b"VALUE = 2\n") with pytest.raises(RuntimeError): audit_wheel(wheel, package) + + +def test_wheel_payload_rejects_duplicate_members(tmp_path): + package = tmp_path / "demo" + package.mkdir() + (package / "__init__.py").write_bytes(b"VALUE = 1\n") + wheel = tmp_path / "fixture.whl" + with zipfile.ZipFile(wheel, "w") as archive: + archive.writestr("demo/__init__.py", b"VALUE = 1\n") + archive.writestr( + "demo/_bindings" + EXTENSION_SUFFIXES[0], b"test payload, not imported" + ) + archive.writestr("py_demo-0.0.0.dist-info/METADATA", b"Name: py-demo\n") + with pytest.warns(UserWarning, match="Duplicate name"): + archive.writestr("demo/__init__.py", b"VALUE = 1\n") + with pytest.raises(RuntimeError, match="payload"): + audit_wheel(wheel, package) + + +@pytest.mark.parametrize("extension_count", [0, 2], ids=["missing", "multiple"]) +def test_wheel_payload_rejects_invalid_extension_count(tmp_path, extension_count): + package = tmp_path / "demo" + package.mkdir() + (package / "__init__.py").write_bytes(b"VALUE = 1\n") + wheel = tmp_path / "fixture.whl" + with zipfile.ZipFile(wheel, "w") as archive: + archive.writestr("demo/__init__.py", b"VALUE = 1\n") + for index in range(extension_count): + archive.writestr( + "demo/_bindings" + EXTENSION_SUFFIXES[index], + b"test payload, not imported", + ) + archive.writestr("py_demo-0.0.0.dist-info/METADATA", b"Name: py-demo\n") + with pytest.raises(RuntimeError, match="payload"): + audit_wheel(wheel, package) From c64af037267dc3b6bef284f8387223f64c8b1c3f Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 17:08:22 +0000 Subject: [PATCH 34/40] docs: define shared TOML snapshot catalog design --- ...26-09-25-test-harness-phase-four-design.md | 456 ++++++++++++++++++ 1 file changed, 456 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-25-test-harness-phase-four-design.md diff --git a/docs/superpowers/specs/2026-09-25-test-harness-phase-four-design.md b/docs/superpowers/specs/2026-09-25-test-harness-phase-four-design.md new file mode 100644 index 00000000..b6a2fa56 --- /dev/null +++ b/docs/superpowers/specs/2026-09-25-test-harness-phase-four-design.md @@ -0,0 +1,456 @@ +# Test harness phase four: shared snapshots and explicit variants + +## Status and baseline + +The user approved file-level sharing, an explicit TOML catalog, development/test +TOML dependencies, case-scoped copy-on-write updates, and the safety and +verification design below. This written specification awaits user review before +implementation planning. No phase-four implementation is included here. + +Continue on `test-harness-phase-one` in its existing worktree. At the user's +request it was rebased onto `prune-historical-snapshots` (`b651555`). The new +preservation anchor is **`4c84bad5dfb95b63980be8c629c3c6a2746829cb`**. All 32 +harness commits replayed without conflicts; the resulting tree differs from +pre-rebase `e8fbf84` only by the pruning branch's 155 deletions. Every surviving +tracked blob/mode and all 13 active cases' effective expectations are unchanged. +The backup ref is `backup/test-harness-phase-one-before-prune-e8fbf84`. + +Rebase evidence and old/new commit correspondence are retained under +`.superpowers/sdd/2026-09-25-test-harness-phase-four/`. Earlier specifications and +reports contain historical hashes; do not reinterpret them as this phase's +baseline. Do not restore pruned references or modify other worktrees. + +Fresh rebased verification passed four isolated source suites of 192 tests on +Python 3.10–3.13 and the entire supplied-wheel native route: 11 profiles, 1,093 +tests, 13 logical cases, and 26 native checks. Reference/index audits, hooks, +lock and whitespace checks passed. This is a baseline, not implementation +acceptance or evidence of remote CI execution. + +This implements the [phase-one roadmap's snapshot organization phase](2026-09-24-test-harness-phase-one-design.md#phase-4-snapshot-organization), +building on [phase-three orchestration](2026-09-25-test-harness-phase-three-design.md). + +## Goal and selected approach + +Store each distinct expectation once per logical output filename, while making +all case-to-reference choices explicit and reviewable. Preserve exact effective +expectations, compatibility coverage, read-only checking, and guarded updates. +Do not introduce fallback, overlay, inheritance, or version-range selection. + +The pruned baseline contains 317 tracked reference entries: 312 regular files +and five symlinks. There are 61 distinct contents, occupying 88,888 bytes rather +than 280,479 bytes of repeated payload. Each active case has 31 stub filenames; +18 filenames are identical across all cases and 13 have genuine variants. +There are nine distinct complete stub trees. The migration should result in +**59 stub payloads and two stderr payloads**, plus one catalog, not 13 copies of +a complete tree. These are migration targets, not permanent limits on future +regressions or variants. + +Alternatives rejected: + +- Whole-tree aliases leave most duplication within the distinct trees. +- File symlinks broaden the filesystem and update safety surface. +- JSON was rejected for the catalog in favor of TOML and comment-preserving + editing. + +The catalog describes expectations. Tox remains the execution and dependency +configuration authority; do not derive a second execution matrix from it. + +## Scope and preservation + +Allowed changes: + +- New `tests/snapshot_cases.toml` and reorganized payloads under `tests/stubs/` + and `tests/errors/`; remove obsolete physical copies and aliases. +- Focused catalog reading/comparison and editing helpers under `tests/`. +- The `DemoCase` reference-selection integration in `tests/snapshot_helpers.py` + and narrowly necessary native test/diagnostic glue. +- Adapt layout-dependent synthetic integration fixtures and assertions; add + compiler-free catalog/editor regression tests under `tests/unit/`. +- Development/test dependency declarations and their necessary lock entries; + tox/CI provisioning of those dependencies, without orchestration changes. +- Test documentation, this specification, and its implementation plan. + +Preserve: + +- All production files under `pybind11_stubgen/`, the C++ fixture and its build + configuration, and all existing demo Python source/module identities. +- Exact effective expected filenames and bytes for every active case, including + stderr order. Deduplication is not regeneration or expectation correction. +- All 13 cases and both native checks per case; installed-origin/runtime guards, + dual-mode reuse, build isolation, and both generator-wheel routes. +- Native dependency pins, root generator metadata/backend/package discovery, + Ruff configuration/pin, normalization, and subprocess invocation semantics. +- Existing 300-second snapshot process timeouts and 600-second native build/ + installation timeouts, failure propagation, and diagnostic retention. +- Compiler-free production/support coverage without demo, NumPy/SciPy, native + setup or formatter downloads. The approved TOML dependencies are the narrow + exception to the previous pytest/stdlib-only test dependency boundary. +- Workflow triggers, matrix membership, gemmi jobs, artifact retention policy, + publication conditions and permissions. + +No new test/support `__init__.py` files, broad refactoring or dependency upgrades, +production fixes, additional platforms/interpreters, pruning beyond the already +rebased branch, or permanent migration/overlay framework. Keep ordinary-Python +positional-only behavior, dirty-tree generator packaging, and the previously +deferred permanent real-tox inventory gate separate. No push, PR, merge, release +tag, remote workflow run or publication is part of this work. + +## Catalog and payload layout + +Use **`tests/snapshot_cases.toml`**, with TOML 1.0-compatible syntax, a top-level +integer `format = 1`, and a `cases` table. Each case key is the existing complete +`DemoCase.id`, including runtime Python, pybind11 series and NumPy mode. Keep +existing diagnostic case IDs and pytest parameter IDs unchanged. + +Every case contains exactly two tables, `stubs` and `errors`. Each table maps a +logical generated relative filename to a physical relative payload filename. +Stub values are relative to `tests/stubs/`; error values are relative to +`tests/errors/`. Keys and paths are literal, case-sensitive strings, not patterns. +Dots and slashes in logical filenames must be quoted in TOML. + +This excerpt illustrates two entries per stub table; the actual catalog must +contain each case's entire expected file set, not just these entries: + +```toml +format = 1 + +[cases."python-3.13-pybind11-v2.13-numpy-array-wrap-with-annotated".stubs] +"demo/pure_python/functions.pyi" = "demo/pure_python/functions/shared.pyi" +"demo/_bindings/numpy.pyi" = "demo/_bindings/numpy/py310-pb213-annotated.pyi" + +[cases."python-3.13-pybind11-v2.13-numpy-array-wrap-with-annotated".errors] +"demo.errors.stderr.txt" = "pybind11-v2.9/demo.errors.stderr.txt" + +[cases."python-3.13-pybind11-v2.13-numpy-array-use-type-var".stubs] +"demo/pure_python/functions.pyi" = "demo/pure_python/functions/shared.pyi" +"demo/_bindings/numpy.pyi" = "demo/_bindings/numpy/py313-pb213-type-var.pyi" + +[cases."python-3.13-pybind11-v2.13-numpy-array-use-type-var".errors] +"demo.errors.stderr.txt" = "pybind11-v2.9/demo.errors.stderr.txt" +``` + +Use human-readable payload labels, not hashes. For initial stub migration, group +by logical filename and exact bytes. A universally shared file gets `shared`; +other groups use the lexicographically smallest full case ID in the group as +the representative. Render its label as `py-pb` +plus `-annotated` or `-type-var`, for example `py310-pb213-annotated`. Place these +under the logical filename's parent/stem with its original suffix, as illustrated. +Record the resulting inventory before migration. Keep the two existing canonical +stderr files and map all relevant cases directly to them. + +Labels describe a payload's origin, not a selection rule. A reference can later +be reused by another case without renaming it. Sharing is within the same check +kind and logical output filename, not across unrelated files merely because +both happen to contain the same bytes. Reject a physical payload mapped to two +different logical filenames within its kind. + +There are no default tables, implicit common files, includes, aliases or inherited +values. A missing case is an error; do not select a nearby Python or pybind11 +version. A missing expected filename means the generated filename is unexpected, +not that lookup should search another directory. Empty kind tables are valid +explicit expectations, but native success still requires `demo/__init__.pyi` +and native error runs still require their established fatal-process contracts. +Unknown cases are never created implicitly by an update. + +All migrated payloads are regular files. Retaining `tests/stubs/` preserves its +existing Ruff exclusion; do not change formatting policy or reformat payloads. + +## Components and dependency direction + +Keep the implementation small and test-side: + +- `tests/snapshot_catalog.py`: read/validate TOML, select a case and check kind, + resolve payloads, construct the existing logical `dict[str, bytes]` snapshot, + and compare/report logical file-set and byte differences. +- `tests/snapshot_updates.py`: plan and perform a selected-case update, preserve + TOML presentation, publish mappings, and perform narrowly scoped cleanup. +- `tests/snapshot_helpers.py`: retain generic filesystem, diff, process, + normalization and diagnostic primitives; adapt the case model to identify the + catalog instead of treating version directories as the native reference API. +- Native tests: keep generation and validation bodies intact apart from + reference selection, catalog comparison/update calls, and diagnostic context. +- `tests/unit/test_snapshot_catalog.py` and + `tests/unit/test_snapshot_updates.py`: focused synthetic behavior contracts. + +Reuse the existing diff/diagnostic primitives rather than creating another +normalizer or subprocess runner. Generic direct-directory helpers may remain +for their existing self-tests; they must not become a legacy lookup fallback. +Adapt only layout-dependent synthetic integration tests to the new reference +model, preserving their failure/update intent. + +Keep catalog imports lazy where needed so importing `BRANCHES` for the stdlib +native-matrix adapter does not acquire a TOML dependency. Catalog reads must not +import the editor. Do not cache catalog contents across native tests: explicit +serial updates in one pytest session must see the previous test's committed +mapping changes. + +## Development and test dependencies + +Add these requirements to the root development dependency group: + +```toml +dev = [ + "tomli>=2,<3; python_version < '3.11'", + "tomlkit>=0.13,<1", +] +``` + +This is an additive excerpt, not a replacement for existing dev requirements. +Use stdlib `tomllib` on Python >=3.11 and `tomli` on 3.10. Import `tomlkit` only +when a real catalog edit is needed; read-only checks and no-op updates must not +require it to be importable. Missing required tooling must produce an actionable +error, never a different parser/editor fallback or skipped test. + +Declare the same test requirements in the native and explicit unit tox +environments: native self-tests exercise update behavior too. Update the CI +compiler-free wheel installation step and README isolated commands to supply +these requirements without syncing the native development group. Runtime +package requirements and the eight pinned native dependencies stay unchanged. + +Update `uv.lock` without an upgrade operation. The current lock already records +`tomli` 2.4.0 transitively; adding its direct conditional requirement must not +be used to upgrade unrelated packages. Review the resolved `tomlkit` addition +and every other lock diff. These are test dependencies, not a new runtime extra. + +Documentation probes using `tomlkit` 0.15.1 on Python 3.10, 3.11 and 3.13 +validated the examples and comment-preserving value replacement with both LF +and CRLF line endings. These probes check the library choice, not the future +catalog updater's correctness. + +## Validation and read-only comparison + +Validate the complete catalog's structure and referenced payloads, not only the +selected case. Fail clearly on unsupported format, wrong types, unknown fields, +malformed/duplicate TOML keys, missing kind tables, missing files, nonregular +entries, conflicting logical file/ancestor paths, or ambiguous payload ownership. +The format value must be an integer, not a boolean accepted through Python's +integer subclass relationship. + +Validate case identifiers as single safe path components. Validate logical and +physical filenames as nonempty canonical POSIX relative paths: reject absolute +paths, traversal, backslashes, NULs and noncanonical spellings. Physical paths +must stay within the correct payload root. Reject symlinks in the catalog path, +payload roots and anywhere beneath those roots, including unreferenced or +dangling links. Scan both payload trees for unsafe entry types, but use only +catalog-referenced files to construct expectations or find reusable variants. +Do not relax the existing rejection of symlinks in generated output. Resolve the +trusted repository location once; do not mistake an ordinary ancestor above that +location for a payload alias. + +Read referenced files as bytes. Compare the exact logical filename set and +contents, using existing diff semantics. An extra pool file does not become an +expectation merely because it exists. Unreferenced regular files are not searched +for alternatives and are not automatically garbage-collected by reads; this +also permits diagnosing leftovers from an interrupted update. The migration +and successful-update tests separately verify the intended payload inventory. + +Normal checks never alter the catalog, payloads or Git index. Invalid references +are errors even in update mode: do not silently recreate a missing mapped file +or bless a corrupted catalog from generated output. + +## Explicit case-scoped updates + +Retain `--update-snapshots` and existing mode/check selection. An update selects +one case and one kind; the other kind and every other case retain exactly their +previous effective expectations. Preserve scoped error-file updates: an `only` +selection changes only the selected logical filenames and rejects actual output +outside that scope. Full stub updates match the entire generated stub file set. + +All current generation, formatting, expected-exit, traceback, termination-marker, +no-output and required-`demo/__init__.pyi` checks run before reference mutation. +An invalid generator/formatter result can never become an expectation. + +For a valid changed snapshot: + +1. Load and validate the catalog and actual logical paths/bytes. Compute the + complete proposed selected-table change in memory before writing anything. +2. Keep unchanged mappings. For each new/changed logical filename, reuse an + already referenced variant with exactly the same bytes for that kind/name; + choose the lexicographically smallest relative payload path if several match. + Never rewrite an existing referenced payload, even if only the selected case + uses it. +3. Otherwise allocate one new directory for this case/kind update under + `variants///`, where `n` is the first unused positive integer, + starting at 1. Put all newly needed payloads beneath it using their unchanged + logical filenames. Skip occupied numbered entries, even empty directories; + reject unsafe or non-directory namespace parents. At this stage choose paths + without creating them; step 5 creates the directory/files exclusively. Never + overwrite occupied paths, follow links or use hashes as filenames. Allocate + nothing if every changed file can be reused. +4. Remove deleted logical filenames only from the selected table/scope. Preserve + other tables and the comments/order/formatting of untouched TOML entries. + Value replacements retain attached comments; deleting an entry may remove + that entry's own inline comment, not unrelated comments. Use `tomlkit`, not + regular-expression or whole-document regeneration. +5. Parse/validate the serialized TOML against the proposed mapping model before + filesystem mutation. Create and write all new payloads, then validate the + prospective catalog against the resulting physical files before publication. +6. Write a sibling temporary catalog. Immediately before atomic replacement, + recheck that the on-disk catalog still matches the loaded bytes; abort rather + than overwrite a detected intervening edit. Atomically replace the catalog + file. This is the mapping publication point, not a transaction over all files + or the entire test run. Do not claim crash/power-loss durability or support + for concurrent writers. +7. After publication, delete only old payload files displaced by this update + that are no longer referenced anywhere in the resulting catalog. Never sweep + unrelated unreferenced files, touch another case's payload, or recursively + delete a subtree. Empty-directory cleanup is limited to affected ancestors + beneath a payload root; unexpected filesystem errors propagate. + +No-op updates do not edit, reserialize, rename, deduplicate or clean anything; +all catalog and payload bytes remain identical. Do not automatically split +identical references just because update mode was requested. Repeated serial +updates producing the same bytes converge on existing referenced variants, +without requiring a separate deduplication command. + +If writing a payload, temporary catalog or catalog replacement fails before +publication, the old catalog and all previously referenced payloads remain +unchanged by this operation. A detected external catalog edit must be left +intact, not rolled back. New unreferenced files or a temporary catalog may remain; +report their exact locations and the primary error rather than undertaking a +broad rollback/cleanup framework. +If cleanup fails after publication, fail explicitly and report that the new +mapping is already published, with cleanup paths. Do not claim rollback or +suppress the original error behind secondary diagnostic failures. + +Updates are serial, including across tox processes; bounded parallel checking +remains supported. Detecting an intervening catalog edit is not a locking +protocol. Never stage or commit user expectations automatically. + +## Diagnostics and pytest integration + +Failure context identifies the unchanged case ID, check kind, catalog path and +TOML entry, logical filename, and canonical payload path where one exists. +Unexpected output is explicitly identified as having no mapping. Continue +showing logical missing/unexpected/changed-file diffs, not merely a diff of TOML +text. Retain generator/Ruff commands, cwd, statuses, stdout/stderr and artifacts. + +Extend reference-location protection to cover the catalog as well as both +payload roots. Diagnostic workspaces, retained artifacts and diagnostic symlinks +must not overwrite any of these. Preserve existing context when diagnostic +writing or retention itself fails. Catalog/preflight errors before generation +must name the offending catalog/case/path; they need not invent subprocess logs +for a process that was never started. + +Successful update reports list logical changes, old/new physical references, +created/reused/removed files, and catalog changes for the selected case/kind. +Replace the old advice that updating a shared directory changes all aliases: +copy-on-write deliberately prevents that propagation. No fake canonical +profile directory should be reported when selection is actually through TOML. + +Keep in-process installed provenance and runtime version/extension checks. +Continue ordinary failures across grouped modes. Do not require a native import +or a formatter download for catalog/editor or synthetic integration self-tests. + +## Mechanical migration and coverage + +The supported inventory remains: + +| Profiles | NumPy modes | +| --- | --- | +| `py310-pb30`, `py311-pb30`, `py312-pb30` | Annotated | +| `py313-pb30` | Annotated and type-var | +| `py310-pb213`, `py311-pb213`, `py312-pb213` | Annotated | +| `py313-pb213` | Annotated and type-var | +| `py313-pb29`, `py313-pb211`, `py313-pb212` | Annotated | + +Mode strings remain `numpy-array-wrap-with-annotated` and +`numpy-array-use-type-var`. Each logical case retains stub and fatal-error tests. + +Before moving payloads, record an independent baseline oracle from the rebased +legacy layout, including alias resolution, logical filenames and byte hashes. +Retain source bytes or access to the anchored Git objects for exact comparisons. +The migration uses those expectations, not fresh generator output. A one-off +migration/audit script belongs in this phase's ignored evidence workspace, not +as a permanent compatibility loader in the harness. + +Create the catalog and unique regular payloads, integrate lookup, and remove the +old copies/aliases as one coordinated migration boundary. Compare every new +case/kind snapshot to the independent pre-migration oracle; assert exact +filenames and bytes, all 13 cases, no pruned-only data, and the 61-payload target. +Also verify every committed payload is referenced. Keep a mapping inventory so +reviewers can trace relocated files despite Git's rename heuristics. + +Do not mistake a test using a literal case list for proof of actual tox execution. +Acceptance must inspect expanded tox profiles and real pytest/JUnit case IDs and +compare them with catalog coverage. This is not an additional production matrix +or a new permanent tox-config inspection subsystem. + +## Tests and acceptance + +Use failing behavioral tests before implementation, then focused and cumulative +runs. Where existing code already contains a guard, use a specific negative +control to prove the new test is sensitive rather than weakening the guard. + +Compiler-free regression coverage includes: + +- Literal mappings and complete logical file-set comparison; exact shared and + variant bytes, unknown cases, invalid schema/paths, duplicate keys, missing + files, directory/file conflicts, and symlink escapes. +- Read-only catalog/payload/index preservation; no editor import during reads + or no-op updates; native-matrix import remains stdlib-only. +- Copy-on-write with two or more consumers, stable unaffected case/kind data, + reuse of a matching referenced variant, deterministic collision handling, + additions/removals, scoped updates, and no unrelated garbage collection. +- TOML comments/order and untouched formatting, no-op byte identity, repeated + serial updates observing fresh mappings, and removal of only newly orphaned + old payloads. +- Failures during payload creation/write, temporary catalog write, replacement + and post-publication cleanup; rejection of an intervening catalog edit; + unchanged old references before publication and accurate post-publication + diagnostics, including secondary log/retention errors. +- Existing generator/formatter failure, empty-output, traceback and fatal-run + update guards using synthetic native-test invocations, not new expectations. + +Final local acceptance: + +1. Fresh source and clean installed-wheel suites plus explicit unit tox suites + on Python 3.10–3.13. Record exact collection/pass counts; preserve prior test + intent and add the catalog/editor contracts, with no skips or xfails masking + failures. Confirm compiler-free optional/native-module absence and actual + pytest installed-origin enforcement, including wrong-origin red/green. +2. All 11 native profiles / 13 cases through both local generator-wheel and + supplied-clean-wheel routes. Verify both native checks per case, no unexpected + skips/deselection, unchanged pins/installed provenance, and dual-mode reuse. + Build the supplied artifact from a clean archive, not the dirty checkout. +3. Preserve supplied artifact identity using the phase-three established checks: + independently saved pre-run hash, exact installed file URL, unchanged wheel, + exact installed production inventory/bytes and successful install commands; + validate metadata hashes when present, without requiring uv's absent field. +4. Two-profile bounded parallel check with no updates; catalog/payload/index + inventories unchanged. Test update behavior and injected failures only in + disposable fixtures/copies, not by regenerating real references. +5. Audit catalog/payload bytes and symlinks plus Git index within every native + batch. Separately prove migrated effective expectations against the baseline + oracle and protected production/fixture/config paths against `4c84bad`. +6. `uv lock --check`, all-files hooks and whitespace checks. Review scoped + dependency changes rather than requiring the intentionally edited lock and + dev group to remain byte-identical. Verify unrelated CI/config sections and + all eight native pins remain unchanged. + +Each native profile still runs its harness self-tests in addition to two checks +per selected mode. If test counts change, account for the precise collected IDs; +do not accept only a summary or hardcode a historical total after adding tests. + +## Documentation, evidence and handoff + +Update `tests/README.md` with the catalog/payload layout, literal variant +selection, test dependency installation, serial copy-on-write update workflow, +addition/removal behavior, no-op guarantees, new failure context and limits after +partial failure. Explain how to deliberately add a new case: declare it and its +kind tables explicitly, then populate via validated updates; do not silently +extend coverage from a missing-case lookup. Keep direct installed reruns and +the distinction between compatibility labels and pinned distributions. + +Use only this phase's ignored evidence workspace for new scripts/reports/logs. +Retain previous phase evidence and preserve tox tmp/log trees before commands +reset them. The rebase baseline currently leaves approximately 13 GiB free; +check space before disk-heavy acceptance and ask before deleting retained data. +Record per-task changes, review findings, exact commands, failures and restored +greens. Keep the branch/worktree in place after completion. + +Remote CI, gemmi execution on GitHub, actual artifact uploads and publication +remain unverified unless separately authorized and executed. A local green +matrix and YAML checks do not establish remote acceptance. Report newly exposed +bugs separately, without silent pin changes, stderr sorting, weakened assertions +or blanket snapshot updates. From 51ac28090f170ce3f24f722812770f84ffcdef96 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 18:23:10 +0000 Subject: [PATCH 35/40] docs: plan shared snapshot catalog implementation --- .../2026-09-25-test-harness-phase-four.md | 1152 +++++++++++++++++ ...26-09-25-test-harness-phase-four-design.md | 4 +- 2 files changed, 1154 insertions(+), 2 deletions(-) create mode 100644 docs/superpowers/plans/2026-09-25-test-harness-phase-four.md diff --git a/docs/superpowers/plans/2026-09-25-test-harness-phase-four.md b/docs/superpowers/plans/2026-09-25-test-harness-phase-four.md new file mode 100644 index 00000000..8ea2362f --- /dev/null +++ b/docs/superpowers/plans/2026-09-25-test-harness-phase-four.md @@ -0,0 +1,1152 @@ +# Test Harness Phase Four Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Deduplicate active native expectations using an explicit TOML catalog and safe case-scoped copy-on-write updates, without changing effective expectations. + +**Architecture:** Retain the two payload roots and existing generation/normalization pipeline. A small reader validates literal case mappings and compares logical snapshots; a separate editor publishes new payloads before atomically replacing the catalog. Migrate recorded baseline bytes, not generated output. + +**Tech Stack:** Python 3.10–3.13, pytest 8, stdlib tomllib / conditional tomli, tomlkit, existing uv/tox/scikit-build-core native runner. + +**Spec:** [Approved phase-four design](../specs/2026-09-25-test-harness-phase-four-design.md). Read it completely before this plan. + +## Global Constraints + +- Continue in `/home/virtuald/src/ext/pybind11-stubgen/.worktrees/test-harness-phase-one`, branch `test-harness-phase-one`; do not touch other worktrees. +- Preservation anchor: `4c84bad5dfb95b63980be8c629c3c6a2746829cb`, already rebased onto `prune-historical-snapshots` (`b651555`). Never restore the 155 pruned entries. +- All production files under `pybind11_stubgen/`, the C++ fixture and its build configuration, and all existing demo Python source/module identities remain unchanged. +- Preserve exact effective filenames/bytes/order for all 13 cases and both native checks per case. Deduplication is not regeneration or expectation correction. +- Preserve native pins, generator backend/package discovery/metadata, Ruff configuration/pin and normalization, installed-origin/runtime guards, and existing 300-second snapshot / 600-second build-install timeouts. +- Compiler-free tests require pytest and the approved TOML dependencies, not demo, NumPy/SciPy, native setup or formatter downloads. No new test/support `__init__.py` files. +- Add `tomli>=2,<3; python_version < '3.11'` and `tomlkit>=0.13,<1` only as development/test dependencies. Review scoped lock changes; no upgrade operation or unrelated upgrades. +- Keep workflow triggers, matrix membership, gemmi jobs, artifact retention policy, publication conditions and permissions unchanged. +- Catalog reads and no-op updates do not import tomlkit; ordinary checks never write references or the index. No fallback/overlay/version-range selection or new execution matrix. +- Updates are explicit and serial, including across tox processes. No all-files/all-run transaction, concurrent-writer support or power-loss durability claim. +- No push, PR, merge, release tag, remote workflow run or publication. Preserve this branch/worktree and all prior evidence. +- Keep ordinary-Python positional-only behavior, dirty-tree generator packaging, and the deferred permanent actual-tox inventory gate separate. Investigate failures rather than adjusting pins, sorting stderr, weakening tests, skipping or bulk-updating references. + +--- + +## Workspace, baseline and evidence + +All shell blocks run from the existing worktree root. Tool calls do not share shell variables; set `E` in every block that uses it: + +```sh +E="$PWD/.superpowers/sdd/2026-09-25-test-harness-phase-four" +git check-ignore -q "$E" +git status --short +git merge-base --is-ancestor prune-historical-snapshots HEAD +df -h . +``` + +The rebase baseline is already recorded in `rebase-baseline/summary.json`: four source suites ×192 and the supplied-wheel native route, 11 profiles /1,093 tests /26 comparisons. `rebase-after.json` records every old effective case's filename/hash inventory; `rebase-commit-map.json` maps historical hashes. Reuse these as historical evidence, not as proof of changed implementation. Before Task 1, verify no non-documentation changes since the anchor and repeat source endpoint suites. + +Use unique log names and `UV_LINK_MODE=copy`; unset `VIRTUAL_ENV`, `PYTHONPATH`, `PYTEST_ADDOPTS` and inherited `STUBGEN_*` in verification drivers. Source checks intentionally permit checkout imports; installed checks use `-I` and `STUBGEN_TEST_INSTALLED=1`. Preserve `.tox//{tmp,log}` into this phase's evidence directory before tox can clear them. Do not copy whole environments or delete retained data to regain space. + +Create an immutable oracle by extracting `git archive 4c84bad` into `$E/oracle-source` before migration. Use the old `snapshot_helpers.py` from that archive, not the new reader, to capture the following literal 13-case inventory into `$E/oracle.json`. Encode bytes as hexadecimal, retain all logical paths, and assert equality with `rebase-after.json` hashes. The literal list is an audit oracle, not execution configuration: + +```python +A = "numpy-array-wrap-with-annotated" +T = "numpy-array-use-type-var" +CASES = [ + ((3, 10), "v3.0", A), ((3, 11), "v3.0", A), + ((3, 12), "v3.0", A), ((3, 13), "v3.0", A), + ((3, 13), "v3.0", T), + ((3, 10), "v2.13", A), ((3, 11), "v2.13", A), + ((3, 12), "v2.13", A), ((3, 13), "v2.13", A), + ((3, 13), "v2.13", T), + ((3, 13), "v2.9", A), ((3, 13), "v2.11", A), + ((3, 13), "v2.12", A), +] +``` + +After importing the archived helper in a separate process, the capture loop is: + +```python +oracle = {} +for version, branch, mode in CASES: + case = old_helpers.make_case(archive_root, version, branch, mode) + oracle[case.id] = {} + for kind, root, profile in ( + ("stubs", case.stubs_root, case.stub_profile), + ("errors", case.errors_root, case.error_profile), + ): + tree = old_helpers.read_tree(old_helpers.resolve_profile(root, profile)) + oracle[case.id][kind] = {name: data.hex() for name, data in tree.items()} +assert len(oracle) == 13 +assert all(len(kinds["stubs"]) == 31 for kinds in oracle.values()) +``` + +Here `archive_root` is `$E/oracle-source`; `old_helpers` is imported after inserting `str(archive_root / "tests")` into `sys.path`; write `oracle` with `json.dumps(..., indent=2, sort_keys=True)`. JSON here is ignored audit evidence, not the shipped catalog. Never import old/new modules of the same name in one process to compute both sides of the proof. + +At each task: retain focused red/green logs, negative controls, cumulative endpoint results, hooks, diff review and a report in `task-N-report.md`. Scoped commits follow each task. Investigate before continuing if a previously passing contract fails. + +## File map and task boundaries + +| File | Responsibility | Task | +| --- | --- | --- | +| `tests/snapshot_catalog.py` | TOML schema/path validation, physical loading, logical comparison/context | 1 | +| `tests/snapshot_test_support.py` | Independent synthetic TOML/payload fixtures and filesystem inventory for tests | 1 | +| `tests/unit/test_snapshot_catalog.py` | Reader, confinement, missing/extra data, dependency contracts | 1 | +| `pyproject.toml`, `uv.lock`, `tox.ini`, `.github/workflows/ci.yml` | Approved test dependency provisioning only | 1 | +| `tests/snapshot_updates.py` | Copy-on-write planning, TOML edits, publication and scoped cleanup | 2 | +| `tests/unit/test_snapshot_updates.py` | Editor behavior, no-op/isolation/failure contracts | 2 | +| `tests/snapshot_cases.toml`, `tests/stubs/**`, `tests/errors/**` | Mechanical reference migration | 3 | +| `tests/snapshot_helpers.py`, `tests/test_demo_stubs.py`, `tests/test_demo_errors.py` | Catalog selection and diagnostic glue, not process rewrites | 3 | +| `tests/test_snapshot_helpers.py` | Adapt only layout-dependent synthetic integration fixtures/assertions | 3 | +| `tests/unit/test_snapshot_catalog.py` | Permanent independent catalog inventory/closure checks | 3 | +| `tests/README.md` | Dependency commands in Task 1; complete new workflow in Task 4 | 1, 4 | + +Tasks 1 and 2 leave the old native reference route active and test the new modules synthetically. Task 3 switches the native route and payload layout together. Do not publish a half-migrated state. Task 4 supplies full acceptance and final documentation. + +Every worker brief includes Global Constraints, workspace/baseline, this file map, the shared interface contract and its task text. Give the Task 3 worker/controller the Task 4 evidence-wrapper recipes for its first matrix run; these are reusable ignored audit tools, not a dependency on unimplemented shipped code. + +## Shared interface contract + +Types below are defined in Task 1 and consumed unchanged thereafter: + +```python +Snapshot = dict[str, bytes] # import the existing alias from snapshot_helpers +Cases = dict[str, dict[str, dict[str, str]]] +KINDS = ("stubs", "errors") +``` + +`Catalog` is a frozen dataclass with `repo: Path`, `path: Path`, `raw: bytes`, `cases: Cases`, and `pools: dict[str, Snapshot]`. Treat its nested dictionaries as immutable; editor planning uses `copy.deepcopy`. Methods: + +- `mapping(case_id: str, kind: str) -> dict[str, str]`: validate selection, return a copy. +- `snapshot(case_id: str, kind: str) -> Snapshot`: logical name → referenced bytes, not the whole pool. +- `context(case_id: str, kind: str, names: Iterable[str] | None = None) -> str`: catalog/case/kind plus logical entry → absolute path, or ``. + +Reader functions: + +- `validate_name(value: object, *, component: bool = False) -> Path`. +- `safe_path(repo: Path, relative: str) -> Path`: repo is the already resolved trusted root; inspect every existing component with `lstat`, reject symlinks/special files and non-directory intermediate components. Missing suffixes are allowed for planning; this function creates nothing. +- `parse_model(raw: bytes, path: Path) -> Cases`: pure schema/path/ownership validation, without requiring new planned files to exist. +- `validate_files(repo: Path, cases: Cases) -> dict[str, Snapshot]`: validate both roots, scan all entries with existing `read_tree`, and require every referenced file. Unreferenced regular files remain inert. +- `load_catalog(repo: Path) -> Catalog`: resolve trusted repo, validate/read `tests/snapshot_cases.toml`, parse and load files; wrap filesystem/parse failures with catalog context and original cause. +- `select_expected(catalog: Catalog, case_id: str, kind: str, actual: Snapshot, only: frozenset[str] | None) -> Snapshot`: validate every actual byte value/path and scope; return the selected expected snapshot. +- `changed_names(expected: Snapshot, actual: Snapshot) -> tuple[str, ...]`: sorted logical differences. +- Final `check_case(repo: Path, case_id: str, kind: str, actual: Snapshot, *, update: bool = False, only: frozenset[str] | None = None) -> str`: normal path loads/compares and returns context. Task 1 implements the read-only signature without `update`; Task 2 adds that keyword and lazy editing delegation. Mismatches use existing `SnapshotMismatch` so `diff.patch` retention continues. + +Task 2 exports `update_snapshot` with the same arguments except `update`, returning `UpdateReport`. `UpdateReport` fields: `catalog: Path`, `case_id: str`, `kind: str`, `changed: tuple[str, ...]`, `old_refs/new_refs: dict[str, str]`, and `created/reused/removed: tuple[str, ...]` (physical relative filenames). `describe() -> str` includes all fields with absolute reference locations. No-op reports have no created/reused/removed files. + +--- + +### Task 1: Strict read-only catalog and test dependencies + +**Files:** Create the reader, shared synthetic support and reader tests listed above. Edit only dev requirements in root pyproject, necessary lock metadata/package entries, tox dependency lists, the CI compiler-free installation step/name and README dependency commands. + +**Interfaces:** Consumes existing `HarnessError`, `SnapshotMismatch`, `Snapshot`, `relative_file`, `read_tree`, `diff_tree`. Produces all Task 1 interfaces above; editor delegation is added in Task 2, not a fake implementation now. + +- [ ] **Step 1: Add independent fixtures and first reader contracts.** + +The fixture helper must not call reader/editor code. Its API is `write_catalog(repo: Path, cases: Cases, payloads: dict[str, Snapshot]) -> Path`, returning `repo`; it creates both roots, writes supplied byte payloads, then renders a small TOML document with literal strings. `tree_state(root: Path) -> dict[str, tuple[str, bytes | str]]` inventories directories, regular bytes, raw link targets and special-entry tags using `os.walk(..., followlinks=False)`/`lstat`; never read a FIFO. Include an inert `.git/index` sentinel in preservation tests, and use real index audits in acceptance. + +Use JSON string quoting with `ensure_ascii=False` only as a fixture string-literal encoder (its escapes are TOML-compatible for these strings), not as a catalog format: + +```python +import json +import os +from pathlib import Path +import stat + + +def tree_state(root): + result = {} + def fail(error): + raise error + for directory, dirs, files in os.walk(root, followlinks=False, onerror=fail): + for name in dirs + files: + path = Path(directory) / name + mode = path.lstat().st_mode + key = path.relative_to(root).as_posix() + if stat.S_ISLNK(mode): + result[key] = ("link", os.readlink(path)) + elif stat.S_ISREG(mode): + result[key] = ("file", path.read_bytes()) + elif stat.S_ISDIR(mode): + result[key] = ("dir", "") + else: + result[key] = ("special", str(stat.S_IFMT(mode))) + return result + + +def write_catalog(repo, cases, payloads): + lines = ["format = 1", ""] + for kind in ("stubs", "errors"): + root = repo / "tests" / kind + root.mkdir(parents=True, exist_ok=True) + for name, data in payloads.get(kind, {}).items(): + target = root / name + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(data) + quote = lambda value: json.dumps(value, ensure_ascii=False) + for case, kinds in cases.items(): + for kind in ("stubs", "errors"): + lines.append(f"[cases.{quote(case)}.{kind}]") + for name, reference in kinds[kind].items(): + lines.append(f"{quote(name)} = {quote(reference)}") + lines.append("") + (repo / "tests/snapshot_cases.toml").write_bytes("\n".join(lines).encode()) + return repo +``` + +Start tests with literal mappings and a payload not referenced by the catalog: + +```python +from pathlib import Path +import pytest +from snapshot_catalog import load_catalog, check_case +from snapshot_helpers import HarnessError, SnapshotMismatch +from snapshot_test_support import write_catalog, tree_state + + +def sample(tmp_path): + cases = { + "a": {"stubs": {"demo/a.pyi": "shared/a.pyi"}, "errors": {}}, + "b": {"stubs": {"demo/a.pyi": "shared/a.pyi"}, "errors": {}}, + } + return write_catalog(tmp_path / "repo", cases, { + "stubs": {"shared/a.pyi": b"old\n", "unused.pyi": b"inert\n"}, + }) + + +def test_literal_selection_and_read_only_failure(tmp_path): + repo = sample(tmp_path) + before = tree_state(repo) + catalog = load_catalog(repo) + assert catalog.snapshot("a", "stubs") == {"demo/a.pyi": b"old\n"} + with pytest.raises(SnapshotMismatch) as exc: + check_case(repo, "a", "stubs", {"demo/extra.pyi": b"new\n"}) + message = str(exc.value) + assert "Missing: demo/a.pyi" in message + assert "Unexpected: demo/extra.pyi" in message + assert "" in message + assert str(repo / "tests/stubs/shared/a.pyi") in message + assert tree_state(repo) == before + + +@pytest.mark.parametrize("name", ["", "../x", "/x", "a\\b", "a//b", "a/./b", "x\0y"]) +def test_unsafe_reference_names_are_rejected(tmp_path, name): + repo = sample(tmp_path) + text = (repo / "tests/snapshot_cases.toml").read_text() + import json + text = text.replace('"shared/a.pyi"', json.dumps(name)) + (repo / "tests/snapshot_cases.toml").write_text(text) + before = tree_state(repo) + from snapshot_catalog import parse_model + with pytest.raises(HarnessError, match="[Uu]nsafe|[Ii]nvalid"): + parse_model(text.encode(), repo / "tests/snapshot_cases.toml") + with pytest.raises(HarnessError): + load_catalog(repo) + assert tree_state(repo) == before +``` + +Add parametrized literal malformed documents covering format `true`, format `2`, non-table `cases`, missing/extra kind, non-string reference, duplicate TOML keys, ancestor-conflicting logical names and one physical file owned by two logical names. Require the specific schema/path error, not just any exception. For example: + +```python +@pytest.mark.parametrize("raw, clue", [ + (b"format = true\n[cases.a]\nstubs = {}\nerrors = {}\n", "format"), + (b"format = 2\n[cases.a]\nstubs = {}\nerrors = {}\n", "format"), + (b"format = 1\ncases = 2\n", "cases"), + (b"format = 1\n[cases.a]\nstubs = 2\nerrors = {}\n", "stubs"), + (b"format = 1\n[cases.a.stubs]\n", "errors"), + (b"format = 1\nformat = 1\n[cases]\n", "TOML"), +]) +def test_schema_failures(tmp_path, raw, clue): + from snapshot_catalog import parse_model + with pytest.raises(HarnessError, match=clue): + parse_model(raw, tmp_path / "snapshot_cases.toml") +``` + +For filesystem negatives, replace the catalog, `tests`, a payload root, a nested directory, a mapped file and an unreferenced file with symlinks (including dangling/outside links), one scenario per fresh fixture. Assert no outside/sentinel writes. Test missing roots/files, nonregular entries and read/scan `OSError` propagation with retained cause. Unknown case/kind must fail rather than fall back. Explicit empty kind tables must load. + +- [ ] **Step 2: Record the initial RED, then declare approved dependencies.** + +Run the new reader file before implementing it; missing-module collection failure is the initial RED only. Later behavioral controls must also prove the actual guards are sensitive. + +```sh +env -u VIRTUAL_ENV UV_LINK_MODE=copy uv run --no-project --isolated --python 3.10 \ + --with 'pytest>=8,<9' --with 'tomli>=2,<3; python_version < "3.11"' \ + --with 'tomlkit>=0.13,<1' python -m pytest tests/unit/test_snapshot_catalog.py -q +``` + +Add the two requirements from Global Constraints to `[dependency-groups].dev` and to both tox `deps` lists. Do not sync the root dev group to run isolated tests. The CI compiler-free installation command becomes: + +```sh +uv pip install --python .venv/bin/python 'pytest>=8,<9' \ + 'tomli>=2,<3; python_version < "3.11"' 'tomlkit>=0.13,<1' dist/*.whl +``` + +Rename that step to describe test dependencies, not “pytest only.” Keep its wheel, interpreter and installed-pytest commands unchanged. Update both README compiler-free commands to supply the two requirements, adding `--isolated` to the harness-only uv command too. Keep focused production-only tests free of native packages. + +Run `uv lock` without `--upgrade`. Compare old/new package `(name, version, source)` inventories with stdlib tomllib on Python 3.13: every prior package record must remain; only tomlkit/new necessary metadata may be added. Inspect all lock diffs. If unrelated versions move, investigate before proceeding. + +- [ ] **Step 3: Implement the reader, without connecting native tests yet.** + +Use `from __future__ import annotations`. Implement pure model validation before filesystem loading. This path validator is the shared starting point: + +```python +import stat +from snapshot_helpers import HarnessError, relative_file + + +def validate_name(value, *, component=False): + if not isinstance(value, str) or "\0" in value: + raise HarnessError(f"Invalid snapshot path: {value!r}") + path = relative_file(value) + if component and len(path.parts) != 1: + raise HarnessError(f"Invalid case identifier: {value!r}") + return path + + +def safe_path(repo, relative): + parts = validate_name(relative).parts + path = repo + for index, part in enumerate(parts): + path = path / part + try: + mode = path.lstat().st_mode + except FileNotFoundError: + continue + if stat.S_ISLNK(mode): + raise HarnessError(f"Unexpected symlink: {path}") + if index < len(parts) - 1 and not stat.S_ISDIR(mode): + raise HarnessError(f"Non-directory snapshot parent: {path}") + if not (stat.S_ISREG(mode) or stat.S_ISDIR(mode)): + raise HarnessError(f"Unexpected filesystem entry: {path}") + return path +``` + +`parse_model` imports the version-selected parser inside the function, reports missing tomli with its installation requirement, decodes UTF-8 without newline translation, and wraps parser errors with `TOML` plus the catalog path. Validate exact root keys `format/cases`, `type(format) is int and format == 1`, string safe case IDs and exactly `stubs/errors` mappings. Require string safe values. Reject logical ancestor conflicts in each table and maintain `(kind, physical_name) -> logical_name` ownership across cases. Return fresh plain nested dictionaries. + +`validate_files` calls `safe_path(repo, "tests/stubs")` and `safe_path(repo, "tests/errors")`, requires actual directories, and then calls existing `read_tree` for each root. This scans even unreferenced entries and rejects nested symlinks/special files. Every mapped relative name must exist in the corresponding pool dictionary. Schema/path failures identify their offending TOML entry where known; missing mapped-file errors include case, kind, logical name and absolute target. Wrap `OSError` with catalog/root/path context and keep `__cause__`. A scan failure on an unreferenced entry must identify it as such, not invent a mapping. + +The dataclass methods are straightforward literal lookups: + +```python +from dataclasses import dataclass +from pathlib import Path + + +@dataclass(frozen=True) +class Catalog: + repo: Path + path: Path + raw: bytes + cases: dict[str, dict[str, dict[str, str]]] + pools: dict[str, dict[str, bytes]] + + def mapping(self, case_id, kind): + if case_id not in self.cases: + raise HarnessError(f"Catalog {self.path}: unknown case {case_id!r}") + if kind not in ("stubs", "errors"): + raise HarnessError(f"Catalog {self.path}: unknown kind {kind!r}") + return dict(self.cases[case_id][kind]) + + def snapshot(self, case_id, kind): + return {name: self.pools[kind][ref] + for name, ref in self.mapping(case_id, kind).items()} + + def context(self, case_id, kind, names=None): + mapping = self.mapping(case_id, kind) + lines = [f"Catalog: {self.path}", f"Case: {case_id}/{kind}"] + for name in sorted(mapping if names is None else names): + target = self.repo / "tests" / kind / mapping[name] if name in mapping else "" + lines.append(f"Entry cases[{case_id!r}][{kind!r}][{name!r}]: {target}") + return "\n".join(lines) + "\n" +``` + +`select_expected` validates all actual names/content before comparison, rejects logical ancestor conflicts and validates every `only` name. Reject actual keys outside `only`. Return either the whole logical expected snapshot or its exact scope. Use `bytes`, not decoded/string-normalized content. `changed_names` compares key presence and bytes in sorted union order. + +Implement the normal comparison route: + +```python +def check_case(repo, case_id, kind, actual, *, only=None): + try: + catalog = load_catalog(repo) + expected = select_expected(catalog, case_id, kind, actual, only) + except HarnessError as exc: + raise HarnessError(f"Case {case_id}/{kind}: {exc}") from exc + context = catalog.context(case_id, kind, expected.keys() | actual.keys()) + difference = diff_tree(expected, actual) + if difference: + raise SnapshotMismatch(context + difference) + return context + "changed: ()" +``` + +The old native route remains active in this commit. Task 2 extends this tested read-only function with the explicit update keyword; no editor stub or unsupported update mode is exposed in Task 1. + +- [ ] **Step 4: Prove confinement/read-only behavior and run cumulative tests.** + +Run focused and cumulative compiler-free suites on Python 3.10 and 3.13 with the dependencies above. In a fresh subprocess install an import blocker for `tomlkit` and `snapshot_updates`, then load/compare a valid synthetic catalog; require success. A separate import blocker for tomli/tomllib/tomlkit must still allow importing `native_matrix` and its existing helper-only matrix tests. On Python 3.10, a direct reader subprocess blocked from importing tomli must report the dependency clearly; do not remove pytest's own tomli dependency and misattribute a pytest startup error. + +Use process-local negative controls: bypass `validate_name` for a traversal test, omit unmapped-output comparison, or remove a schema guard, then require the corresponding new test to fail for the intended assertion. Restore unmodified green runs; never ship mutants. Inventory the complete reference trees/catalog (currently absent)/index before and after tests. + +Run all-files hooks and inspect dependencies/CI diff. Confirm no root runtime dependencies, native pins, production files, native setup, or existing expectations changed. + +- [ ] **Step 5: Self-review and commit Task 1.** + +```sh +git add tests/snapshot_catalog.py tests/snapshot_test_support.py \ + tests/unit/test_snapshot_catalog.py pyproject.toml uv.lock tox.ini \ + .github/workflows/ci.yml tests/README.md +git diff --cached --check +git commit -m "test: add strict TOML snapshot catalog reader" +``` + +Review gate: schema/path/data correctness, whole-catalog validation, read-only guarantees, lazy import boundaries, and narrowly scoped dependency wiring. + +### Task 2: Case-scoped editor and failure-sensitive publication + +**Files:** Create `tests/snapshot_updates.py` and `tests/unit/test_snapshot_updates.py`; add lazy update delegation to `tests/snapshot_catalog.py`. + +**Interfaces:** Consumes Task 1 reader/model/context utilities and shared independent fixtures. Produces `UpdateReport`/`update_snapshot` described above, plus internal helpers with explicit responsibilities: + +- `plan_update(catalog, case_id, kind, actual, only) -> UpdatePlan` performs no writes. +- `render_update(catalog, plan) -> bytes` lazily imports tomlkit and validates resulting model. +- `write_payloads(catalog, plan) -> None` exclusively creates the planned directory/files. +- `write_temporary_catalog(catalog, raw) -> Path` creates a sibling file, preserves original file mode, and reports its path if writing fails. +- `publish_catalog(catalog, temporary) -> None` revalidates safe path and unchanged raw bytes immediately before `os.replace`. +- `cleanup_payloads(catalog, plan) -> None` removes only planned orphan files/affected empty ancestors; unexpected errors propagate. + +`UpdatePlan` is a dataclass with `case_id`, `kind`, `changed`, `old_refs`, `new_refs`, `cases` (deep-copied proposed model), `snapshot` (intended full selected-kind bytes), `directory: str | None`, `new_files: Snapshot` (physical names), and `reused/removed: tuple[str, ...]`. + +Use the following concrete report type; annotations and field order are shared with the orchestrator below: + +```python +from dataclasses import dataclass +from pathlib import Path + + +@dataclass(frozen=True) +class UpdateReport: + catalog: Path + case_id: str + kind: str + changed: tuple[str, ...] + old_refs: dict[str, str] + new_refs: dict[str, str] + created: tuple[str, ...] + reused: tuple[str, ...] + removed: tuple[str, ...] + + def describe(self): + root = self.catalog.parent / self.kind + lines = [f"Case: {self.case_id}/{self.kind}", + f"Catalog: {self.catalog}; changed: {self.changed}"] + for name in self.changed: + old = str(root / self.old_refs[name]) if name in self.old_refs else "" + new = str(root / self.new_refs[name]) if name in self.new_refs else "" + lines.append(f"{name}: {old} -> {new}") + for action in ("created", "reused", "removed"): + lines.append(f"{action}: {tuple(str(root / p) for p in getattr(self, action))}") + return "\n".join(lines) +``` + +- [ ] **Step 1: Write sharing, reuse, no-op and scoped-update RED tests.** + +Use independently rendered fixtures, not the editor to establish preconditions: + +```python +import pytest +from snapshot_catalog import load_catalog +from snapshot_helpers import HarnessError +from snapshot_updates import update_snapshot +from snapshot_test_support import write_catalog, tree_state + + +def shared_repo(tmp_path): + cases = { + key: {"stubs": {"demo/a.pyi": "shared/a.pyi"}, + "errors": {"demo.errors.stderr.txt": "shared/error.txt"}} + for key in ("a", "b") + } + return write_catalog(tmp_path / "repo", cases, { + "stubs": {"shared/a.pyi": b"old\n", "unrelated.pyi": b"leave\n"}, + "errors": {"shared/error.txt": b"fatal\n"}, + }) + + +def test_copy_on_write_then_reuse_does_not_change_other_case(tmp_path): + repo = shared_repo(tmp_path) + first = update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"new\n"}) + after = load_catalog(repo) + assert after.snapshot("a", "stubs") == {"demo/a.pyi": b"new\n"} + assert after.snapshot("b", "stubs") == {"demo/a.pyi": b"old\n"} + assert after.snapshot("a", "errors") == {"demo.errors.stderr.txt": b"fatal\n"} + assert first.created == ("variants/a/1/demo/a.pyi",) + second = update_snapshot(repo, "b", "stubs", {"demo/a.pyi": b"new\n"}) + final = load_catalog(repo) + assert final.mapping("a", "stubs") == final.mapping("b", "stubs") + assert second.created == () + assert second.reused == first.created + assert second.removed == ("shared/a.pyi",) + assert (repo / "tests/stubs/unrelated.pyi").read_bytes() == b"leave\n" + + +def test_noop_is_byte_identical(tmp_path): + repo = shared_repo(tmp_path) + before = tree_state(repo) + report = update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"old\n"}) + assert report.changed == report.created == report.reused == report.removed == () + assert tree_state(repo) == before + + +def test_planning_preserves_model_and_files(tmp_path): + import copy + from snapshot_updates import plan_update + repo = shared_repo(tmp_path) + catalog = load_catalog(repo) + original_model = copy.deepcopy(catalog.cases) + original_files = tree_state(repo) + plan = plan_update(catalog, "a", "stubs", {"demo/a.pyi": b"new\n"}, None) + assert plan.cases != original_model + assert catalog.cases == original_model + assert tree_state(repo) == original_files +``` + +Add literal cases for full add/delete, `only` preserving other mappings, actual keys outside scope, a prospective logical ancestor conflict with an unselected scoped entry, file/directory shape changes, pre-existing numbered directories/files and bad namespace parents. Assert one numbered directory per update contains all newly needed files, and matching variants are chosen lexicographically only among referenced files of the same kind/name. An identical unreferenced file must stay inert. Existing referenced files must never be overwritten, even for a single consumer. + +Use hand-authored LF and CRLF TOML with top-level/table/inline comments. Replace one value and require an exact expected text replacement; delete only that entry's inline comment with the entry, preserve unrelated comments, and append new entries without reordering old tables. No-op subprocesses blocked from importing tomlkit must pass; changed updates in that process must fail before filesystem mutation and name the missing dependency. + +- [ ] **Step 2: Run focused RED tests and implement side-effect-free planning/rendering.** + +Use the Task 1 isolated command with `tests/unit/test_snapshot_updates.py`. Record initial RED separately from later guard mutations. + +Planning algorithm (implement directly, not as a rule framework): + +```python +import copy + + +def plan_update(catalog, case_id, kind, actual, only): + expected = select_expected(catalog, case_id, kind, actual, only) + changed = changed_names(expected, actual) + old_refs = catalog.mapping(case_id, kind) + new_refs = dict(old_refs) + intended = catalog.snapshot(case_id, kind) + pending = {} + reused = set() + for name in changed: + if name not in actual: + new_refs.pop(name, None) + intended.pop(name, None) + continue + intended[name] = actual[name] + matches = sorted({ + mapping[kind][name] + for mapping in catalog.cases.values() + if name in mapping[kind] + and catalog.pools[kind][mapping[kind][name]] == actual[name] + }) + if matches: + new_refs[name] = matches[0] + reused.add(matches[0]) + else: + pending[name] = actual[name] + directory = None + new_files = {} + if pending: + number = 1 + while True: + directory = f"variants/{case_id}/{number}" + candidate = safe_path(catalog.repo, f"tests/{kind}/{directory}") + if not candidate.exists(): + break + number += 1 + for name, content in sorted(pending.items()): + physical = f"{directory}/{name}" + new_refs[name] = physical + new_files[physical] = content + cases = copy.deepcopy(catalog.cases) + cases[case_id][kind] = new_refs + used = {ref for kinds in cases.values() for ref in kinds[kind].values()} + displaced = {old_refs[name] for name in changed if name in old_refs} + removed = tuple(sorted(displaced - used)) + return UpdatePlan(case_id, kind, changed, old_refs, new_refs, cases, + intended, directory, new_files, tuple(sorted(reused)), removed) +``` + +Validate the prospective model before writing. The physical directory number is a label, not a fallback or version selector. `safe_path` treats any existing numbered file/directory as occupied, and rejects symlink/special entries or non-directory parents. If there are no logical changes, return the empty report before importing tomlkit or choosing/creating directories. + +Rendering operates only on changed entries, preserving existing keys where values did not change: + +```python +def render_update(catalog, plan): + try: + import tomlkit + except ImportError as exc: + raise HarnessError("Catalog editing requires tomlkit>=0.13,<1") from exc + document = tomlkit.parse(catalog.raw.decode("utf-8")) + table = document["cases"][plan.case_id][plan.kind] + for name in plan.changed: + if name in plan.new_refs: + table[name] = plan.new_refs[name] + else: + del table[name] + raw = tomlkit.dumps(document).encode("utf-8") + if parse_model(raw, catalog.path) != plan.cases: + raise HarnessError("Serialized catalog differs from the planned mappings") + return raw +``` + +Add tests for invalid planned paths/ownership and parser/serializer failure before any allocation. The editor may not bypass strict model validation just because it constructed the data. + +- [ ] **Step 3: Implement safe publication and phase-specific failures.** + +`write_payloads` rechecks safe paths. Create `variants/` parents only after checking every existing component; create the numbered directory with `exist_ok=False`. Within it, create needed logical parent directories and open every new file with `xb`. Never call `write_bytes` on a pre-existing reference. A failure can leave only newly allocated, unreferenced files/directories. + +`write_temporary_catalog` uses `tempfile.NamedTemporaryFile(mode="wb", delete=False, dir=catalog.path.parent, prefix=".snapshot_cases-", suffix=".toml")`. Record its name immediately and preserve `stat.S_IMODE(catalog.path.stat().st_mode)` before closing. If its write fails, the raised `HarnessError` must include the temporary path and original cause. Validate the catalog's parent path before creating the temporary file. + +`publish_catalog` rechecks catalog/temporary paths against symlink/nonregular substitutions and compares on-disk catalog bytes with `catalog.raw`. If different, leave the external edit intact and fail. Then call `os.replace(temporary, catalog.path)`. + +The orchestrator has this fixed order: + +```python +def update_snapshot(repo, case_id, kind, actual, *, only=None): + try: + catalog = load_catalog(repo) + plan = plan_update(catalog, case_id, kind, actual, only) + except HarnessError as exc: + raise HarnessError(f"Case {case_id}/{kind}; catalog not published: {exc}") from exc + report = UpdateReport(catalog.path, case_id, kind, plan.changed, + plan.old_refs, plan.new_refs, tuple(sorted(plan.new_files)), + plan.reused, plan.removed) + if not plan.changed: + return report + published = False + temporary = None + try: + raw = render_update(catalog, plan) + write_payloads(catalog, plan) + pools = validate_files(catalog.repo, plan.cases) + prospective = Catalog(catalog.repo, catalog.path, raw, plan.cases, pools) + if prospective.snapshot(case_id, kind) != plan.snapshot: + raise HarnessError("Written payloads differ from the planned snapshot") + for other_case in catalog.cases: + for other_kind in ("stubs", "errors"): + if (other_case, other_kind) != (case_id, kind): + if prospective.snapshot(other_case, other_kind) != catalog.snapshot(other_case, other_kind): + raise HarnessError("An unselected expectation changed") + temporary = write_temporary_catalog(catalog, raw) + publish_catalog(catalog, temporary) + published = True + cleanup_payloads(catalog, plan) + except Exception as exc: + state = "published" if published else "not published" + raise HarnessError( + catalog.context(case_id, kind) + + f"Update failed; catalog {state}; temporary={temporary}; " + + f"planned payloads={tuple(str(catalog.repo / 'tests' / kind / p) for p in sorted(plan.new_files))}; " + + f"planned directory={plan.directory}; cleanup targets={plan.removed}: {exc}" + ) from exc + return report +``` + +The helper that creates a temporary file includes its path in its own failure, since assignment in the caller may not yet have completed. For pre-publication payload failures, report the allocated directory and every existing planned payload so a partially written file is discoverable. Do not imply a planned but never created file exists. No automatic rollback or unrelated orphan sweep. + +Cleanup unlinks only `plan.removed`, each resolved through `safe_path`. After all successful unlinks, collect affected parent directories strictly below the appropriate root, deduplicate them, and attempt `rmdir` deepest-first. Suppress only `ENOTEMPTY/EEXIST`; propagate other errors. Do not remove roots, recursively delete, or collect siblings. A post-publication error explicitly says mappings already changed. + +Add keyword-only `update: bool = False` to `check_case` and put this branch before the unchanged read-only body: + +```python +if update: + from snapshot_updates import update_snapshot + return update_snapshot(repo, case_id, kind, actual, only=only).describe() +``` + +- [ ] **Step 4: Inject failures at each boundary and prove restoration.** + +Parametrize the helpers above for pre-publication failure; compare only original referenced files/catalog as well as asserting any new leftovers are reported (full filesystem equality is not promised after writes start): + +```python +@pytest.mark.parametrize("stage", [ + "render_update", "write_payloads", "write_temporary_catalog", "publish_catalog", +]) +def test_prepublication_failure_preserves_live_references(tmp_path, monkeypatch, stage): + import snapshot_updates as editor + repo = shared_repo(tmp_path) + original = load_catalog(repo) + def fail(*args, **kwargs): + raise OSError(f"injected {stage}") + monkeypatch.setattr(editor, stage, fail) + with pytest.raises(HarnessError, match="not published"): + update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"changed\n"}) + assert original.path.read_bytes() == original.raw + after = load_catalog(repo) + for case in original.cases: + for kind in ("stubs", "errors"): + assert after.snapshot(case, kind) == original.snapshot(case, kind) +``` + +These outer-boundary cases are not sufficient alone. Also inject actual low-level partial `xb` writes, temporary-file writes, `os.replace`, orphan `unlink`, and directory `rmdir`. Require original cause and correct publication state. For post-publication cleanup failure, selected expectations are new, others unchanged, and leftovers are named. Mutate the catalog immediately before calling the real publication helper and prove the external bytes survive. Simulate corrupted new payload bytes before physical validation and require failure without publication. + +Add behavior controls disabling copy-on-write (overwrite the shared file), reusing an unreferenced candidate, removing raw-byte stale-edit detection, and swallowing cleanup `OSError`; each must break a named contract test. Restore and rerun focused plus cumulative source endpoint suites. No mutation reaches real references. + +- [ ] **Step 5: Self-review and commit Task 2.** + +```sh +git add tests/snapshot_updates.py tests/unit/test_snapshot_updates.py tests/snapshot_catalog.py +git diff --cached --check +git commit -m "test: add case-scoped TOML snapshot updates" +``` + +Review gate: no-op import/byte behavior, in-memory planning, isolation across case/kind/scope, deterministic reuse/allocation, comment preservation, publication ordering and cleanup/error semantics. Old native tests remain on their old reference route until Task 3. + +### Task 3: Mechanical migration and native integration + +**Files:** Create catalog and new payloads, remove old copies/aliases; adapt `DemoCase`/`make_case`, native reference glue, layout-dependent helper tests, and add shipped catalog inventory tests. + +**Interfaces:** Consumes `load_catalog`, `check_case`, `UpdateReport` and independent archive oracle. Produces a `DemoCase.catalog_path` property, retaining `id`, `stubs_root`, `errors_root`, runtime version and configuration fields. Remove obsolete `stub_profile/error_profile` properties from the native case model; keep generic directory helper APIs unchanged, never as fallback. + +- [ ] **Step 1: Write failing catalog inventory and native-glue tests.** + +Add a test of the actual repository catalog's exact 13 case IDs using the independent literal list from the baseline section, not a list generated by the reader. Require `demo/__init__.pyi` and `demo.errors.stderr.txt` in their respective kinds. Assert every payload file is referenced, no symlinks exist, and no identical same-kind/name variants have multiple physical copies. Keep the exact baseline 31-file sets and 61-payload count in migration evidence, not permanent assertions that would block future legitimate file/variant additions. The independent oracle checks exact initial filenames and bytes. + +Adapt the existing synthetic integration fixture setup with the shared literal TOML writer. For example: + +```python +case = h.DemoCase(repo, (3, 13), "v3.0", "numpy-array-wrap-with-annotated") +write_catalog(repo, { + case.id: { + "stubs": {"demo/__init__.pyi": "seed/init.pyi"}, + "errors": {"demo.errors.stderr.txt": "seed/stderr.txt"}, + }, +}, { + "stubs": {"seed/init.pyi": b"original"}, + "errors": {"seed/stderr.txt": b"original"}, +}) +``` + +After a synthetic successful update, assert through a fresh `load_catalog` that selected bytes changed and unrelated kind/case data did not. Failure tests must compare the catalog and both pools, not just a now-obsolete directory. Preserve the existing generator/formatter failure, traceback, no-output and empty-generation guards and their expected diagnostics. Make the selection test require an existing catalog/case and the runtime Python ID rather than directory profile properties. + +Only these existing test functions need layout adaptation: +`test_case_requires_existing_profiles_and_uses_runtime_python`, +`test_success_update_never_accepts_failed_generation_or_formatting`, +`test_error_update_rejects_traceback_even_with_exit_one`, +`test_integration_check_and_update_paths_with_synthetic_output`, and +`test_empty_output_cannot_erase_references`. Retain all generic filesystem, +process, formatting, diagnostics and entrypoint tests. New diagnostic/catalog +protection regressions belong in the new unit files unless they directly extend +an existing diagnostic parametrization; explain any collection-count change. + +Record RED before migration/wiring. The permanent inventory assertion should fail because the catalog is not yet present; synthetic integration tests should fail at the old selection route, not be skipped. + +- [ ] **Step 2: Wire literal selection and protect catalog diagnostics.** + +`DemoCase.catalog_path` returns `repo / "tests/snapshot_cases.toml"`. In `make_case`, preserve version/branch/mode validation and ID construction, then lazily import `load_catalog`, validate selection of both kinds and return the case. Prepend the constructed case ID to catalog/preflight `HarnessError` messages and retain their cause. Do not cache its catalog globally/session-wide or import the reader while importing `BRANCHES`. + +In both native tests, set `expected = case.catalog_path`, include it along with both roots in `reference_roots`, and replace only the final comparison/update call: + +```python +message = check_case( + case.repo, case.id, "stubs", actual, update=update_snapshots +) +if update_snapshots: + report_update(message) +``` + +For errors, keep `filename = "demo.errors.stderr.txt"`, the current validated `stderr`, kind `"errors"`, and `only=frozenset({filename})`. All generator argv, cwd, status checks, logging, formatting order, address normalization, output assertions and timeout helpers remain unchanged. + +Extend `diagnostics` with optional `reference_details: str = ""`, appended to context before writing it; preserve old callers. Compute the selected mapping context with the reader so generator/formatter failures as well as diffs identify the TOML entries and real payload paths. Preflight failures remain explicit setup failures with catalog/case/path context. Test a workspace equal to/above the catalog and an artifact path inside a catalog-path symlink; all must fail before reference writes. Existing diagnostic symlink and secondary-error contracts still apply with the catalog in the protected set. + +The subprocess grouped-mode test in `tests/unit/test_native_support.py` copies only three existing support files and overrides the native fixture. It must continue passing because reader imports are lazy; do not turn it into a native/catalog setup test. + +- [ ] **Step 3: Generate the catalog from the oracle, then remove old references.** + +Create `$E/migrate_snapshots.py`, an ignored one-shot script run with Python 3.13 + tomlkit. It must refuse to run if catalog/new destinations already exist or if current old reference bytes/links differ from the pre-migration inventory. First calculate all mappings/payloads in memory, verify no collisions, and retain the manifest preview as evidence. No generator invocation. + +The grouping/naming core is: + +```python +from collections import defaultdict +from pathlib import PurePosixPath +import re + +groups = defaultdict(list) +for case_id, kinds in oracle.items(): + for name, encoded in kinds["stubs"].items(): + groups[(name, bytes.fromhex(encoded))].append(case_id) +new_cases = {case: {"stubs": {}, "errors": {}} for case in sorted(oracle)} +new_payloads = {} +for (name, content), members in sorted(groups.items()): + chosen = min(members) + match = re.fullmatch( + r"python-(\d+)\.(\d+)-pybind11-v(\d+)\.(\d+)-numpy-array-(wrap-with-annotated|use-type-var)", + chosen, + ) + assert match, chosen + major, minor, pbmajor, pbminor, mode = match.groups() + label = "shared" if len(members) == len(oracle) else ( + f"py{major}{minor}-pb{pbmajor}{pbminor}-" + + ("annotated" if mode == "wrap-with-annotated" else "type-var") + ) + logical = PurePosixPath(name) + physical = (logical.parent / logical.stem / (label + logical.suffix)).as_posix() + assert physical not in new_payloads + new_payloads[physical] = content + for case in members: + new_cases[case]["stubs"][name] = physical +assert len(new_payloads) == 59 +``` + +For stderr, retain exact current `tests/errors/pybind11-v2.9/demo.errors.stderr.txt` and `tests/errors/pybind11-v3.0/demo.errors.stderr.txt`. Match each oracle stderr by exact bytes to those two files; require exactly one match and map directly to it. Verify no canonical stderr bytes change. + +Render one TOML document via tomlkit with integer format 1 and complete explicit case/kind tables, inserting sorted cases and sorted logical filenames. Validate `parse_model` equals the proposed model before writing. Write new stub payloads exclusively, then the catalog. Remove only the original tracked stub files/links and three old stderr aliases, using the saved list and explicit `git rm -- `; retain the two stderr files. Remove now-empty old directories without recursive deletion. Do not use a blanket `git add -A` or regenerate payloads. + +The temporary overlap with old symlinks cannot be loaded by the new strict reader; remove old entries before running `load_catalog`. This is one coordinated working-tree migration, committed only after all gates pass. + +Verify all 13 new effective case/kind maps against oracle **bytes**, then cross-check hashes against `rebase-after.json`. Require exactly 61 regular payloads, no links, every payload referenced and no pruned-only data. Retain old→new mapping and Git staged/unstaged inventory before committing. + +- [ ] **Step 4: Run glue negatives, preservation audits and the first full native matrix.** + +Run focused and cumulative source endpoint suites. Repeat synthetic generator/formatter/empty-output/traceback negatives with the new catalog: all failures leave both pools/catalog/index unchanged and preserve diagnostics. Test a successful synthetic update with two selected modes in one process so the second sees fresh mappings. + +Using the audit wrapper defined in Task 4, run all default native profiles serially through the local generator-wheel route. Preserve their old tmp/log trees before tox resets them. Inspect all JUnit case IDs, the actual 11 expanded tox names, two checks per mode and no skips/errors. Require the current canonical catalog/pool/index state to remain unchanged through the batch. Account for the number of harness self-tests rather than assuming an old total. + +For a real mismatch/retention control, use a disposable archived checkout and existing installed native interpreter; change one copied referenced payload, run exactly that case's stub check with `--artifacts-dir` outside copied references, and require failure naming catalog entry/logical file/canonical path plus retained process logs/diff. Both real and copied references remain unchanged by check mode. Restore copied bytes and pass. Never update the real catalog to make this control green. + +- [ ] **Step 5: Self-review and commit the coordinated migration.** + +```sh +git add tests/snapshot_cases.toml tests/stubs tests/errors tests/snapshot_helpers.py \ + tests/test_demo_stubs.py tests/test_demo_errors.py tests/test_snapshot_helpers.py \ + tests/unit/test_snapshot_catalog.py +# Include only any new diagnostic tests explicitly added to the new unit files. +git diff --cached --check +git commit -m "test: share snapshot payloads through explicit TOML cases" +``` + +Review gate: exact byte/file-set equivalence, no revived historical data, all consumers switched coherently, safety/diagnostics preserved, native flags/process contracts unchanged, no hidden fallback or count/coverage loss. + +### Task 4: Documentation and complete local acceptance + +**Files:** Finish `tests/README.md`; all verification scripts/reports below stay under this phase's ignored evidence directory. No implementation changes unless acceptance reveals a justified defect, which needs a focused regression and affected reruns. + +**Interfaces:** Consumes the complete catalog route, oracle, Task 1 test dependencies and unchanged phase-three tox/native provenance outputs. Produces retained acceptance evidence and the final docs commit. + +- [ ] **Step 1: Document the user workflow precisely.** + +Keep existing native profile commands/flags and direct installed reruns. Explain catalog literal maps, payload labels as origins rather than selectors, and the distinction between shared reading and case-scoped updating. Update examples to inspect all three reference locations: + +```sh +tox -e py313-pb30 -- --numpy-format numpy-array-wrap-with-annotated --update-snapshots +git diff -- tests/snapshot_cases.toml tests/stubs tests/errors +``` + +Document `-k demo_stubs`/`-k demo_errors`, serial updates, no-op byte guarantees, numbered new variants, reuse, scoped deletions, and explicit empty tables to bootstrap a deliberately added supported case. Adding a catalog case does not schedule it in tox. Explain pre-publication leftovers and post-publication cleanup failures; no rollback promise, bulk GC command or implicit unknown-case creation. Normal checks/artifacts never modify references. + +Document these compiler-free commands instead of syncing the root native dev group: + +```sh +env -u VIRTUAL_ENV uv run --no-project --isolated --python 3.10 \ + --with 'pytest>=8,<9' --with 'tomli>=2,<3; python_version < "3.11"' \ + --with 'tomlkit>=0.13,<1' python -m pytest tests/unit tests/test_snapshot_helpers.py +``` + +Keep production-only focused tests possible with pytest alone where the selected tests do not exercise catalog editing. Preserve diagnostics/build log locations, exact pin authority, installed guards, and separate production/packaging bug boundaries. + +- [ ] **Step 2: Create the reusable command/reference audit wrapper.** + +Save this as `$E/audit.py` before Task 3's first native run; it is an evidence tool, not shipped code. Invocation is `python "$E/audit.py" LABEL -- COMMAND ...`. It must write unique logs and record failures/timeouts before raising, with 3600-second overall timeout and no shared-shell-variable assumptions: + +```python +import hashlib +import json +import os +from pathlib import Path +import subprocess +import sys + +root = Path.cwd() +E = Path(__file__).resolve().parent +label = sys.argv[1] +assert len(Path(label).parts) == 1 and not Path(label).is_absolute() and label not in (".", "..") +assert sys.argv[2] == "--" +command = sys.argv[3:] +log = E / f"{label}.log" +record = E / f"{label}.json" +assert not (log.exists() or log.is_symlink() or record.exists() or record.is_symlink()) + +def inventory(): + result = {} + for relative in ("tests/stubs", "tests/errors", "tests/snapshot_cases.toml"): + start = root / relative + paths = [start] + if start.is_dir() and not start.is_symlink(): + for directory, dirs, files in os.walk(start, followlinks=False): + paths.extend(Path(directory) / name for name in dirs + files) + for path in paths: + key = path.relative_to(root).as_posix() + if path.is_symlink(): + result[key] = ["link", os.readlink(path)] + elif path.is_file(): + result[key] = ["file", hashlib.sha256(path.read_bytes()).hexdigest()] + elif path.is_dir(): + result[key] = ["dir"] + elif not path.exists(): + result[key] = ["missing"] + else: + result[key] = ["special"] + return {"references": result, "index": subprocess.check_output( + ["git", "ls-files", "--stage", "-z"], cwd=root).decode()} + +env = {k: v for k, v in os.environ.items() if not k.startswith("STUBGEN_")} +for key in ("VIRTUAL_ENV", "PYTHONPATH", "PYTEST_ADDOPTS"): + env.pop(key, None) +env["UV_LINK_MODE"] = "copy" +before = inventory() +status = None +error = None +try: + with log.open("xb") as stream: + status = subprocess.run(command, cwd=root, env=env, stdout=stream, + stderr=subprocess.STDOUT, timeout=3600).returncode +except Exception as exc: + error = repr(exc) +finally: + after = inventory() + record.write_text(json.dumps({"command": command, "returncode": status, + "error": error, "before": before, "after": after, + "unchanged": before == after}, indent=2) + "\n") +assert status == 0 and error is None and before == after, record +print(record) +``` + +Preserve previous tox outputs using a separate unique batch directory and record every move before each invocation. Save this second script as `$E/preserve_tox.py`, invoked with `LABEL PROFILE...`: + +```python +import json +from pathlib import Path +import shutil +import sys + +root = Path.cwd() +E = Path(__file__).resolve().parent +label, *profiles = sys.argv[1:] +assert len(Path(label).parts) == 1 and label not in (".", "..") +destination = E / f"{label}-previous-outputs" +destination.mkdir() +moved = [] +for profile in profiles: + assert len(Path(profile).parts) == 1 and profile not in (".", "..") + for kind in ("tmp", "log"): + source = root / ".tox" / profile / kind + if source.exists(): + target = destination / profile / kind + target.parent.mkdir(parents=True, exist_ok=True) + shutil.move(str(source), str(target)) + moved.append({"from": str(source), "to": str(target)}) + (destination / "moved.json").write_text(json.dumps(moved, indent=2) + "\n") +print(destination) +``` + +Move only these tmp/log trees; keep environment contents and prior reports. Before a later batch, verify and retain the current command logs, JUnit, builder evidence/logs/CMake caches, exact fixture wheel hash/payload, and installed-package probes. Also retain the local generator wheel and `.tox/.pkg/log/` for a local-wheel batch, correlated with the actual backend/install records; do not assume temporary install-copy paths remain available. Installed extensions and metadata will change on the next installation: capture their verification receipts now, not afterward. Relocated evidence contains original absolute paths; use the recorded prefix relocation when reading its files. Do not merely retain paths to subsequently deleted files. + +- [ ] **Step 3: Build a clean supplied artifact and test all compiler-free routes.** + +Build from a fresh clean tracked-source archive at the current implementation revision (not from the dirty root `build/` tree). Name each archive/source/dist uniquely and preserve earlier artifacts. Save absolute wheel path and SHA256 **before any installation** to `$E/generator-wheel.path` and `$E/generator-wheel.sha256`. + +```sh +E="$PWD/.superpowers/sdd/2026-09-25-test-harness-phase-four" +mkdir "$E/acceptance-source" "$E/acceptance-dist" +git archive HEAD -o "$E/acceptance-source.tar" +tar -xf "$E/acceptance-source.tar" -C "$E/acceptance-source" +(cd "$E/acceptance-source" && env -u VIRTUAL_ENV UV_LINK_MODE=copy \ + uv build --wheel --out-dir "$E/acceptance-dist") +``` + +Use Python to require exactly one wheel, save its absolute path/hash, inspect its exact 15-file `pybind11_stubgen/` production inventory including `py.typed` against tracked source and require no nested `build/` payload. Do not substitute a dirty-root wheel for this route. + +For each 3.10/3.11/3.12/3.13, create fresh `$E/source-VERSION` and `$E/wheel-VERSION` environments and install only pytest/TOML test dependencies, plus the saved wheel in the latter. Commands: + +```sh +E="$PWD/.superpowers/sdd/2026-09-25-test-harness-phase-four" +WHEEL=$(< "$E/generator-wheel.path") +for v in 3.10 3.11 3.12 3.13; do + uv venv --python "$v" "$E/source-$v" + uv venv --python "$v" "$E/wheel-$v" + for route in source wheel; do + UV_LINK_MODE=copy uv pip install --python "$E/$route-$v/bin/python" \ + 'pytest>=8,<9' 'tomli>=2,<3; python_version < "3.11"' 'tomlkit>=0.13,<1' + done + UV_LINK_MODE=copy uv pip install --python "$E/wheel-$v/bin/python" "$WHEEL" + env -u STUBGEN_TEST_INSTALLED "$E/source-$v/bin/python" -m pytest \ + tests/unit tests/test_snapshot_helpers.py -q + STUBGEN_TEST_INSTALLED=1 "$E/wheel-$v/bin/python" -I -m pytest \ + tests/unit tests/test_snapshot_helpers.py -q +done +``` + +Run these via an evidence driver to retain each command/status/log and before/after reference/index state; the shell illustrates exact argv, not permission to lose failure evidence. Collect test IDs separately and confirm the expected count and no skips/xfails. Run installed-origin wrong-origin RED without `-I`, then restore `-I` and pass; test the actual pytest guard, not just an import probe. + +In each fresh environment, use `importlib.util.find_spec` to require demo, numpy, scipy, cmake, pybind11, scikit_build_core, ninja and cmeel absent. Assert source generator origin is the checkout; wheel origin is inside the intended prefix. Verify no editor import with a blocker during read/no-op separately; full suites legitimately install tomlkit for edit tests. + +Run all four explicit unit tox profiles with the audit wrapper and preserved old outputs: + +```sh +E="$PWD/.superpowers/sdd/2026-09-25-test-harness-phase-four" +python "$E/audit.py" final-unit -- uv run --no-project --isolated \ + --with tox --with tox-uv tox -e py310-unit,py311-unit,py312-unit,py313-unit \ + --result-json "$E/final-unit-result.json" +``` + +Require 12 complete compiler-free selections in total, actual isolated installed guard behavior in the eight installed routes, and collection accounting for all prior test intent plus new tests. + +- [ ] **Step 4: Verify both full native routes and bounded parallel checking.** + +Before each command preserve selected profiles' tmp/log trees. Reload the saved artifact path every time: + +```sh +E="$PWD/.superpowers/sdd/2026-09-25-test-harness-phase-four" +WHEEL=$(< "$E/generator-wheel.path") +python "$E/audit.py" final-local -- uv run --no-project --isolated \ + --with tox --with tox-uv tox --result-json "$E/final-local-result.json" +# Preserve/copy this batch's logs, XML and native evidence before the next command. +python "$E/audit.py" final-supplied -- uv run --no-project --isolated \ + --with tox --with tox-uv tox --installpkg "$WHEEL" \ + --result-json "$E/final-supplied-result.json" +# Preserve/copy this batch's logs, XML and native evidence before the next command. +python "$E/audit.py" final-parallel -- uv run --no-project --isolated \ + --with tox --with tox-uv tox -p 2 -e py310-pb213,py313-pb30 \ + --installpkg "$WHEEL" --result-json "$E/final-parallel-result.json" +``` + +Run these as separate recorded batches with actual preservation between them, not by pasting the comment-only boundaries without implementing preservation. + +For each batch inspect real `tox list -d --no-desc` output and all corresponding JUnit files. Require the exact eleven names from the spec, both modes only for py313-pb30/py313-pb213, 26 native case/check IDs per full route, and no failures/errors/skips/unexpected deselection. Calculate the harness count from collected IDs and check per-profile totals. Compare executed logical case IDs with the actual catalog key set and oracle. + +Inspect the builder evidence and logs for all eight unchanged native versions, runtime before/after equality, extension origin/hash, exact fixture Python payload plus one extension, supplied generator path, real isolated pytest invocation and grouped-mode extension reuse. For parallel checking verify disjoint invocation-owned paths and both result inventories; references/catalog/index must remain unchanged. + +Write `$E/check_supplied_wheel.py` using only stdlib to check in every installed wheel environment and each of the 11 native supplied-route interpreters: + +- Independently saved pre-run SHA equals current saved artifact bytes. +- Exactly 15 installed production files including `py.typed` equal the wheel and tracked source, with no extra production files; installed origin lies under that interpreter's prefix. +- `importlib.metadata.distribution("pybind11-stubgen").read_text("direct_url.json")` has the exact absolute wheel `as_uri()` URL, no query/fragment/remote authority; installed wheel is not an editable directory. +- `archive_info` is a dictionary. Empty is accepted because uv was observed to omit hashes. If `hashes` or legacy `hash` is present, validate every provided algorithm/digest against the artifact rather than ignoring malformed data; the independently saved SHA256 remains mandatory. +- Correlate actual successful exact-wheel install argv in retained tox logs/result records; no subsequent source replacement is permitted on the supplied route. + +Implement the checker as follows; the command-log correlation remains an independent batch audit, since it cannot be inferred just from an installed package: + +```python +import hashlib +import importlib.metadata +import json +from pathlib import Path +import subprocess +import sys +from urllib.parse import urlsplit +import zipfile +import pybind11_stubgen + +wheel = Path(sys.argv[1]).absolute() +saved = sys.argv[2] +assert len(saved) == 64 and all(c in "0123456789abcdef" for c in saved) +assert hashlib.sha256(wheel.read_bytes()).hexdigest() == saved, "artifact hash" +repo = Path(__file__).resolve().parents[3] +tracked = subprocess.check_output( + ["git", "ls-files", "pybind11_stubgen"], cwd=repo, text=True +).splitlines() +expected = {name: (repo / name).read_bytes() for name in tracked} +assert len(expected) == 15 and "pybind11_stubgen/py.typed" in expected +with zipfile.ZipFile(wheel) as archive: + names = archive.namelist() + assert len(names) == len(set(names)), "duplicate wheel entries" + assert not any("build" in Path(name).parts for name in names), "dirty wheel" + payload = {name: archive.read(name) for name in names + if name.startswith("pybind11_stubgen/") and not name.endswith("/")} +assert payload == expected, "wheel production inventory/bytes" +prefix = Path(sys.prefix).resolve() +package = Path(pybind11_stubgen.__file__).resolve().parent +assert package.is_relative_to(prefix), "generator origin" +installed = {} +for path in package.rglob("*"): + if path.is_file() and "__pycache__" not in path.relative_to(package).parts: + assert path.resolve().is_relative_to(prefix), path + installed["pybind11_stubgen/" + path.relative_to(package).as_posix()] = path.read_bytes() +assert installed == expected, "installed production inventory/bytes" +dist = importlib.metadata.distribution("pybind11-stubgen") +metadata = json.loads(dist.read_text("direct_url.json")) +assert metadata["url"] == wheel.as_uri(), "installed artifact URL/path" +url = urlsplit(metadata["url"]) +assert url.scheme == "file" and not (url.netloc or url.query or url.fragment) +assert "dir_info" not in metadata, "not an archive installation" +info = metadata["archive_info"] +assert isinstance(info, dict) +if "hashes" in info: + assert isinstance(info["hashes"], dict) and info["hashes"], "hash schema" + for algorithm, digest in info["hashes"].items(): + assert isinstance(algorithm, str) and isinstance(digest, str) + assert hashlib.new(algorithm, wheel.read_bytes()).hexdigest() == digest, "metadata hash" +if "hash" in info: + assert isinstance(info["hash"], str) + algorithm, separator, digest = info["hash"].partition("=") + assert separator and algorithm and digest, "legacy hash schema" + assert hashlib.new(algorithm, wheel.read_bytes()).hexdigest() == digest, "legacy hash" +print(json.dumps({"python": sys.executable, "wheel": str(wheel), + "sha256": saved, "origin": str(package), "metadata": metadata, + "production_files": sorted(installed)}, indent=2)) +``` + +Unsupported/malformed hash algorithms fail rather than being ignored; known provided hashes are recomputed, while saved SHA256 is always required independently. Invoke this with each target environment's Python, using `-I`, and pass the original saved hash, not a freshly substituted value. Prove wrong saved digest and byte-identical alternate wheel path are rejected, then restore green. Set `STUBGEN_TEST_INSTALLED=1` when invoking pytest; the standalone checker also enforces origin directly. + +For example, save the actual expanded names once before the full native batches and audit supplied installations before their environments are changed again: + +```sh +E="$PWD/.superpowers/sdd/2026-09-25-test-harness-phase-four" +WHEEL=$(< "$E/generator-wheel.path") +SAVED_SHA=$(< "$E/generator-wheel.sha256") +uv run --no-project --isolated --with tox --with tox-uv \ + tox list -d --no-desc > "$E/final-native-envs.txt" +while IFS= read -r profile; do + ".tox/$profile/bin/python" -I "$E/check_supplied_wheel.py" "$WHEEL" "$SAVED_SHA" +done < "$E/final-native-envs.txt" +``` + +Record each probe's stdout/status separately in the evidence driver; also run it in all four fresh wheel environments. Do not regenerate the saved SHA to make a negative control pass. + +- [ ] **Step 5: Complete controls, preservation audit and final docs commit.** + +Run final focused reader/editor negatives and restored greens, plus the disposable real native mismatch/retention control from Task 3. Updates and mutation controls operate only in disposable repositories/fixtures; the real migrated catalog/payloads never receive `--update-snapshots` during acceptance. All 61 migrated payloads and their effective case bytes must still match the oracle after every native batch. + +Check protected paths against the preservation anchor: + +```sh +git diff --exit-code 4c84bad -- pybind11_stubgen tests/demo-lib \ + tests/py-demo tests/build_native.py tests/native_support.py tests/native_matrix.py \ + tests/conftest.py .pre-commit-config.yaml +uv lock --check +uv run --no-sync pre-commit run --all-files +git diff --check +``` + +For allowed-file changes, independently compare pyproject excluding exactly the two added dev requirements; compare tox except those two dependency additions; compare CI except the compiler-free install step's name/requirements. All other sections, native pins, formatter config, generator metadata and publication policy must match the anchor. Review generic helper/process/normalization bodies against the anchor and require unchanged semantics; account explicitly for the permitted case/diagnostic glue and synthetic fixture adaptation. + +Rerun affected acceptance after any fix. Request final cumulative review against the approved spec and preservation anchor; do not treat worker reports as independent controller verification. Record exact command/status/count/identity results, negative controls, preserved output locations, limits and any separate bugs in `$E/final-report.md`. No remote acceptance claim. + +```sh +git add tests/README.md +git diff --cached --check +git commit -m "docs: document shared snapshots and copy-on-write updates" +git status --short +``` + +Verify the final tree is clean, runtime/reference changes remain exactly scoped, and tests cover the final implementation revision (a README-only final commit does not change the tested implementation). Preserve the branch, worktree, backup ref and ignored evidence. + +## Spec coverage and completion boundary + +| Spec requirement | Deliverable/evidence | +| --- | --- | +| Literal TOML mappings, complete schema/path/payload validation | Task 1 reader and negative tests | +| tomllib/tomli selection, lazy tomlkit and native-matrix imports | Tasks 1–2 subprocess import controls | +| Approved dev/test dependencies without runtime/native drift | Task 1 config/lock diff; Task 4 independent preservation audit | +| Case/kind/scope copy-on-write, reuse, no-op and presentation | Task 2 editor and byte/consumer tests | +| Publication ordering, leftovers, cleanup and stale edits | Task 2 low-level failure injection and phase-specific diagnostics | +| Catalog-protected diagnostics; unchanged process guards | Task 3 synthetic integration and real copied mismatch control | +| Exact 13-case bytes, 61 payload migration, no historical resurrection | Independent archived oracle and Task 3 mapping audit | +| All source/wheel/unit versions and both native routes | Task 4 twelve suites, two full matrices and artifact identity checks | +| Parallel read-only isolation and reference/index safety | Task 4 bounded parallel batch and per-command inventories | +| Docs, review evidence, separate bugs and local-only scope | Task 4 handoff | + +Implementation ends here. Do not use this phase to fix production behavior, clean up unrelated generic helpers, redesign tox, change native pins, or run remote publication. The execution-method choice follows plan review; no implementation begins as part of writing this document. diff --git a/docs/superpowers/specs/2026-09-25-test-harness-phase-four-design.md b/docs/superpowers/specs/2026-09-25-test-harness-phase-four-design.md index b6a2fa56..ac93c6a8 100644 --- a/docs/superpowers/specs/2026-09-25-test-harness-phase-four-design.md +++ b/docs/superpowers/specs/2026-09-25-test-harness-phase-four-design.md @@ -4,8 +4,8 @@ The user approved file-level sharing, an explicit TOML catalog, development/test TOML dependencies, case-scoped copy-on-write updates, and the safety and -verification design below. This written specification awaits user review before -implementation planning. No phase-four implementation is included here. +verification design below. The user has also approved this written specification. +No phase-four implementation is included here. Continue on `test-harness-phase-one` in its existing worktree. At the user's request it was rebased onto `prune-historical-snapshots` (`b651555`). The new From d6df518be0a490f5663c20ae250d7a0fda899f69 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 18:43:24 +0000 Subject: [PATCH 36/40] test: add strict TOML snapshot catalog reader --- .github/workflows/ci.yml | 5 +- pyproject.toml | 2 + tests/README.md | 13 +- tests/snapshot_catalog.py | 253 +++++++++++++++++ tests/snapshot_test_support.py | 59 ++++ tests/unit/test_snapshot_catalog.py | 422 ++++++++++++++++++++++++++++ tox.ini | 4 + uv.lock | 13 + 8 files changed, 764 insertions(+), 7 deletions(-) create mode 100644 tests/snapshot_catalog.py create mode 100644 tests/snapshot_test_support.py create mode 100644 tests/unit/test_snapshot_catalog.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2980b416..427578d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,11 +81,12 @@ jobs: with: name: dist path: dist - - name: Install wheel and pytest only + - name: Install wheel and test dependencies shell: bash run: | uv venv .venv - uv pip install --python .venv/bin/python 'pytest>=8,<9' dist/*.whl + uv pip install --python .venv/bin/python 'pytest>=8,<9' \ + 'tomli>=2,<3; python_version < "3.11"' 'tomlkit>=0.13,<1' dist/*.whl - name: Test installed wheel without native fixtures env: STUBGEN_TEST_INSTALLED: "1" diff --git a/pyproject.toml b/pyproject.toml index 5aa1e5b4..7cffdee5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,8 @@ dev = [ "pip", "pre-commit>=4,<5", "pytest>=8,<9", + "tomli>=2,<3; python_version < '3.11'", + "tomlkit>=0.13,<1", "scipy~=1.0", "typing_extensions>=4,<5", "mypy>=1.19.1", diff --git a/tests/README.md b/tests/README.md index cd68288a..74ee7992 100644 --- a/tests/README.md +++ b/tests/README.md @@ -22,16 +22,18 @@ uv tool install tox --with tox-uv ## Harness self-tests, without a compiler -With pytest installed: +With pytest and the TOML test dependencies installed: ```sh python -m pytest tests/test_snapshot_helpers.py ``` -Or let uv supply only pytest, without syncing the project's native dependencies: +Or let uv supply test dependencies, without syncing the project's native dependencies: ```sh -uv run --no-project --with 'pytest>=8,<9' python -m pytest tests/test_snapshot_helpers.py +uv run --no-project --isolated --with 'pytest>=8,<9' \ + --with 'tomli>=2,<3; python_version < "3.11"' --with 'tomlkit>=0.13,<1' \ + python -m pytest tests/test_snapshot_helpers.py ``` ## Compiler-free production tests @@ -43,8 +45,9 @@ is needed. The following command tests source edits directly without syncing the development dependency group: ```sh -uv run --no-project --isolated --with 'pytest>=8,<9' python -m pytest \ - tests/unit tests/test_snapshot_helpers.py +uv run --no-project --isolated --with 'pytest>=8,<9' \ + --with 'tomli>=2,<3; python_version < "3.11"' --with 'tomlkit>=0.13,<1' \ + python -m pytest tests/unit tests/test_snapshot_helpers.py ``` To test a non-editable installation on each supported matrix interpreter: diff --git a/tests/snapshot_catalog.py b/tests/snapshot_catalog.py new file mode 100644 index 00000000..53efc0b2 --- /dev/null +++ b/tests/snapshot_catalog.py @@ -0,0 +1,253 @@ +"""Strict, read-only literal snapshot catalogs (no editor or native dependencies).""" + +from __future__ import annotations + +import stat +import sys +from collections.abc import Iterable +from dataclasses import dataclass +from pathlib import Path + +from snapshot_helpers import ( + HarnessError, + Snapshot, + SnapshotMismatch, + diff_tree, + read_tree, + relative_file, +) + +Cases = dict[str, dict[str, dict[str, str]]] +KINDS = ("stubs", "errors") + + +def validate_name(value: object, *, component: bool = False) -> Path: + if not isinstance(value, str) or "\0" in value: + raise HarnessError(f"Invalid snapshot path: {value!r}") + path = relative_file(value) + if component and len(path.parts) != 1: + raise HarnessError(f"Invalid case identifier: {value!r}") + return path + + +def safe_path(repo: Path, relative: str) -> Path: + parts = validate_name(relative).parts + path = repo + for index, part in enumerate(parts): + path = path / part + try: + mode = path.lstat().st_mode + except FileNotFoundError: + continue + except OSError as exc: + raise HarnessError( + f"Unable to inspect snapshot path: {path}: {exc}" + ) from exc + if stat.S_ISLNK(mode): + raise HarnessError(f"Unexpected symlink: {path}") + if index < len(parts) - 1 and not stat.S_ISDIR(mode): + raise HarnessError(f"Non-directory snapshot parent: {path}") + if not (stat.S_ISREG(mode) or stat.S_ISDIR(mode)): + raise HarnessError(f"Unexpected filesystem entry: {path}") + return path + + +def _validate_logical_names(names: Iterable[str]) -> None: + names = set(names) + for name in names: + path = validate_name(name) + if any(parent.as_posix() in names for parent in path.parents): + raise HarnessError(f"Conflicting snapshot paths: {name}") + + +def parse_model(raw: bytes, path: Path) -> Cases: + if sys.version_info >= (3, 11): + import tomllib + else: + try: + import tomli as tomllib + except ImportError as exc: + raise HarnessError( + f"Catalog {path}: install test dependency " + "tomli>=2,<3; python_version < '3.11'" + ) from exc + try: + model = tomllib.loads(raw.decode("utf-8")) + except (UnicodeDecodeError, tomllib.TOMLDecodeError) as exc: + raise HarnessError(f"Invalid TOML catalog {path}: {exc}") from exc + if set(model) != {"format", "cases"}: + raise HarnessError( + f"Catalog {path}: expected root keys format/cases; " + f"missing {sorted({'format', 'cases'} - model.keys())}, " + f"extra {sorted(model.keys() - {'format', 'cases'})}" + ) + if type(model["format"]) is not int or model["format"] != 1: + raise HarnessError(f"Catalog {path}: format must be integer 1") + if not isinstance(model["cases"], dict): + raise HarnessError(f"Catalog {path}: cases must be a table") + cases: Cases = {} + owners: dict[tuple[str, str], str] = {} + for case_id, kinds in model["cases"].items(): + entry = f"Catalog {path}: cases[{case_id!r}]" + try: + validate_name(case_id, component=True) + if not isinstance(kinds, dict): + raise HarnessError("must be a table") + if set(kinds) != set(KINDS): + raise HarnessError( + f"expected stubs/errors tables; missing {sorted(set(KINDS) - kinds.keys())}, " + f"extra {sorted(kinds.keys() - set(KINDS))}" + ) + except HarnessError as exc: + raise HarnessError(f"{entry}: {exc}") from exc + cases[case_id] = {} + for kind in KINDS: + table = kinds[kind] + kind_entry = f"{entry}[{kind!r}]" + if not isinstance(table, dict): + raise HarnessError(f"{kind_entry}: must be a table") + cases[case_id][kind] = {} + for name, reference in table.items(): + try: + validate_name(name) + validate_name(reference) + owner = owners.setdefault((kind, reference), name) + if owner != name: + raise HarnessError( + f"Ambiguous payload ownership: {reference!r} is used for " + f"both {owner!r} and {name!r}" + ) + except HarnessError as exc: + raise HarnessError(f"{kind_entry}[{name!r}]: {exc}") from exc + cases[case_id][kind][name] = reference + try: + _validate_logical_names(table) + except HarnessError as exc: + raise HarnessError(f"{kind_entry}: {exc}") from exc + return cases + + +def validate_files(repo: Path, cases: Cases) -> dict[str, Snapshot]: + pools = {} + for kind in KINDS: + root = repo / "tests" / kind + try: + safe_path(repo, f"tests/{kind}") + if not root.is_dir(): + raise HarnessError(f"Expected payload directory: {root}") + pools[kind] = read_tree(root) + except (OSError, HarnessError) as exc: + raise HarnessError( + f"Payload pool {root} (including unreferenced entries): {exc}" + ) from exc + for case_id, kinds in cases.items(): + for kind, mapping in kinds.items(): + for name, reference in mapping.items(): + if reference not in pools[kind]: + raise HarnessError( + f"Missing mapped file cases[{case_id!r}][{kind!r}][{name!r}]: " + f"{repo / 'tests' / kind / reference}" + ) + return pools + + +@dataclass(frozen=True) +class Catalog: + repo: Path + path: Path + raw: bytes + cases: Cases + pools: dict[str, Snapshot] + + def mapping(self, case_id: str, kind: str) -> dict[str, str]: + if case_id not in self.cases: + raise HarnessError(f"Catalog {self.path}: unknown case {case_id!r}") + if kind not in KINDS: + raise HarnessError(f"Catalog {self.path}: unknown kind {kind!r}") + return dict(self.cases[case_id][kind]) + + def snapshot(self, case_id: str, kind: str) -> Snapshot: + return { + name: self.pools[kind][ref] + for name, ref in self.mapping(case_id, kind).items() + } + + def context( + self, case_id: str, kind: str, names: Iterable[str] | None = None + ) -> str: + mapping = self.mapping(case_id, kind) + lines = [f"Catalog: {self.path}", f"Case: {case_id}/{kind}"] + for name in sorted(mapping if names is None else names): + target = ( + self.repo / "tests" / kind / mapping[name] + if name in mapping + else "" + ) + lines.append(f"Entry cases[{case_id!r}][{kind!r}][{name!r}]: {target}") + return "\n".join(lines) + "\n" + + +def load_catalog(repo: Path) -> Catalog: + path = repo / "tests/snapshot_cases.toml" + try: + repo = repo.resolve(strict=True) + path = repo / "tests/snapshot_cases.toml" + safe_path(repo, "tests/snapshot_cases.toml") + if not path.is_file(): + raise HarnessError(f"Expected catalog file: {path}") + raw = path.read_bytes() + cases = parse_model(raw, path) + pools = validate_files(repo, cases) + except (OSError, RuntimeError, HarnessError) as exc: + raise HarnessError(f"Catalog {path}: {exc}") from exc + return Catalog(repo, path, raw, cases, pools) + + +def select_expected( + catalog: Catalog, + case_id: str, + kind: str, + actual: Snapshot, + only: frozenset[str] | None, +) -> Snapshot: + _validate_logical_names(actual) + for name, content in actual.items(): + if not isinstance(content, bytes): + raise HarnessError(f"Snapshot content must be bytes: {name}") + if only is not None: + for name in only: + validate_name(name) + if actual.keys() - only: + raise HarnessError("Actual output exceeds the selected snapshot scope") + expected = catalog.snapshot(case_id, kind) + if only is not None: + expected = {name: content for name, content in expected.items() if name in only} + return expected + + +def changed_names(expected: Snapshot, actual: Snapshot) -> tuple[str, ...]: + return tuple( + name + for name in sorted(expected.keys() | actual.keys()) + if name not in expected or name not in actual or expected[name] != actual[name] + ) + + +def check_case( + repo: Path, + case_id: str, + kind: str, + actual: Snapshot, + *, + only: frozenset[str] | None = None, +) -> str: + try: + catalog = load_catalog(repo) + expected = select_expected(catalog, case_id, kind, actual, only) + except HarnessError as exc: + raise HarnessError(f"Case {case_id}/{kind}: {exc}") from exc + context = catalog.context(case_id, kind, expected.keys() | actual.keys()) + difference = diff_tree(expected, actual) + if difference: + raise SnapshotMismatch(context + difference) + return context + "changed: ()" diff --git a/tests/snapshot_test_support.py b/tests/snapshot_test_support.py new file mode 100644 index 00000000..c646622d --- /dev/null +++ b/tests/snapshot_test_support.py @@ -0,0 +1,59 @@ +"""Independent literal catalog fixtures and non-following filesystem inventories.""" + +from __future__ import annotations + +import json +import os +import stat +from pathlib import Path +from typing import TYPE_CHECKING + +from snapshot_helpers import Snapshot + +if TYPE_CHECKING: + from snapshot_catalog import Cases + + +def tree_state(root: Path) -> dict[str, tuple[str, bytes | str]]: + result = {} + + def fail(error): + raise error + + for directory, dirs, files in os.walk(root, followlinks=False, onerror=fail): + for name in dirs + files: + path = Path(directory) / name + mode = path.lstat().st_mode + key = path.relative_to(root).as_posix() + if stat.S_ISLNK(mode): + result[key] = ("link", os.readlink(path)) + elif stat.S_ISREG(mode): + result[key] = ("file", path.read_bytes()) + elif stat.S_ISDIR(mode): + result[key] = ("dir", "") + else: + result[key] = ("special", str(stat.S_IFMT(mode))) + return result + + +def write_catalog(repo: Path, cases: Cases, payloads: dict[str, Snapshot]) -> Path: + lines = ["format = 1", ""] + for kind in ("stubs", "errors"): + root = repo / "tests" / kind + root.mkdir(parents=True, exist_ok=True) + for name, data in payloads.get(kind, {}).items(): + target = root / name + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(data) + + def quote(value): + return json.dumps(value, ensure_ascii=False) + + for case, kinds in cases.items(): + for kind in ("stubs", "errors"): + lines.append(f"[cases.{quote(case)}.{kind}]") + for name, reference in kinds[kind].items(): + lines.append(f"{quote(name)} = {quote(reference)}") + lines.append("") + (repo / "tests/snapshot_cases.toml").write_bytes("\n".join(lines).encode()) + return repo diff --git a/tests/unit/test_snapshot_catalog.py b/tests/unit/test_snapshot_catalog.py new file mode 100644 index 00000000..439856f4 --- /dev/null +++ b/tests/unit/test_snapshot_catalog.py @@ -0,0 +1,422 @@ +from __future__ import annotations + +import json +import os +from pathlib import Path +import re +import shutil +import subprocess +import sys + +import pytest + +from snapshot_catalog import ( + changed_names, + check_case, + load_catalog, + parse_model, + safe_path, + select_expected, + validate_name, +) +from snapshot_helpers import HarnessError, SnapshotMismatch +from snapshot_test_support import tree_state, write_catalog + + +def sample(tmp_path): + cases = { + "a": {"stubs": {"demo/a.pyi": "shared/a.pyi"}, "errors": {}}, + "b": {"stubs": {"demo/a.pyi": "shared/a.pyi"}, "errors": {}}, + } + repo = write_catalog( + tmp_path / "repo", + cases, + {"stubs": {"shared/a.pyi": b"old\n", "unused.pyi": b"inert\n"}}, + ) + (repo / ".git").mkdir() + (repo / ".git/index").write_bytes(b"inert index sentinel\0") + return repo + + +def test_literal_selection_and_read_only_failure(tmp_path): + repo = sample(tmp_path) + before = tree_state(repo) + catalog = load_catalog(repo) + assert catalog.snapshot("a", "stubs") == {"demo/a.pyi": b"old\n"} + assert catalog.snapshot("b", "stubs") == {"demo/a.pyi": b"old\n"} + assert catalog.snapshot("a", "errors") == {} + assert catalog.raw == (repo / "tests/snapshot_cases.toml").read_bytes() + assert catalog.repo == repo.resolve() + mapping = catalog.mapping("a", "stubs") + mapping.clear() + assert catalog.mapping("a", "stubs") == {"demo/a.pyi": "shared/a.pyi"} + assert check_case(repo, "a", "stubs", {"demo/a.pyi": b"old\n"}).endswith( + "changed: ()" + ) + with pytest.raises(SnapshotMismatch) as exc: + check_case(repo, "a", "stubs", {"demo/extra.pyi": b"new\n"}) + message = str(exc.value) + assert "Missing: demo/a.pyi" in message + assert "Unexpected: demo/extra.pyi" in message + assert "" in message + assert str(repo / "tests/stubs/shared/a.pyi") in message + assert "Case: a/stubs" in message + assert tree_state(repo) == before + + +@pytest.mark.parametrize("name", ["", "../x", "/x", "a\\b", "a//b", "a/./b", "x\0y"]) +def test_unsafe_reference_names_are_rejected(tmp_path, name): + repo = sample(tmp_path) + path = repo / "tests/snapshot_cases.toml" + text = path.read_text().replace('"shared/a.pyi"', json.dumps(name)) + path.write_text(text) + before = tree_state(repo) + with pytest.raises(HarnessError, match="[Uu]nsafe|[Ii]nvalid") as exc: + parse_model(text.encode(), path) + assert "cases" in str(exc.value) + assert "stubs" in str(exc.value) + assert "demo/a.pyi" in str(exc.value) + with pytest.raises(HarnessError): + load_catalog(repo) + assert tree_state(repo) == before + + +@pytest.mark.parametrize( + "raw, clue", + [ + (b"format = true\n[cases.a]\nstubs = {}\nerrors = {}\n", "format"), + (b"format = 2\n[cases.a]\nstubs = {}\nerrors = {}\n", "format"), + (b"format = 1\ncases = 2\n", "cases"), + (b"format = 1\n[cases.a]\nstubs = 2\nerrors = {}\n", "stubs"), + (b"format = 1\n[cases.a.stubs]\n", "errors"), + (b"format = 1\nformat = 1\n[cases]\n", "TOML"), + (b"[cases]\n", "format"), + (b"format = 1\n", "cases"), + (b"format = 1\nextra = 0\n[cases]\n", "extra"), + (b"format = 1\n[cases]\na = 3\n", "cases.*a"), + (b"format = 1\n[cases.a]\nstubs = {}\nerrors = {}\nextra = {}\n", "extra"), + (b"format = 1\n[cases.a]\nstubs = {x = 2}\nerrors = {}\n", "stubs.*x"), + (b'format = 1\n[cases.a]\nstubs = {"../x" = "a"}\nerrors = {}\n', "Unsafe"), + (b'format = 1\n[cases."a/b"]\nstubs = {}\nerrors = {}\n', "case identifier"), + ( + b'format = 1\n[cases.a]\nstubs = {x = "a", "x/y" = "b"}\nerrors = {}\n', + "Conflicting.*x/y", + ), + ( + b'format = 1\n[cases.a]\nstubs = {x = "a", y = "a"}\nerrors = {}\n', + "ownership.*a", + ), + ( + b'format = 1\n[cases.a]\nstubs = {x = "a"}\nerrors = {}\n[cases.b]\nstubs = {y = "a"}\nerrors = {}\n', + "ownership.*a", + ), + (b"\xff", "TOML"), + ], +) +def test_schema_failures(tmp_path, raw, clue): + path = tmp_path / "snapshot_cases.toml" + with pytest.raises(HarnessError, match=clue) as exc: + parse_model(raw, path) + assert str(path) in str(exc.value) + + +def test_pure_model_allows_planned_files_and_separate_kind_ownership(tmp_path): + raw = b'format = 1\r\n[cases.a]\r\nstubs = {x = "shared"}\r\nerrors = {y = "shared"}\r\n' + assert parse_model(raw, tmp_path / "absent.toml") == { + "a": {"stubs": {"x": "shared"}, "errors": {"y": "shared"}} + } + + +@pytest.mark.parametrize( + "relative, dangling", + [ + ("tests/snapshot_cases.toml", False), + ("tests", False), + ("tests/stubs", False), + ("tests/errors", False), + ("tests/stubs/shared", False), + ("tests/stubs/shared/a.pyi", False), + ("tests/stubs/unused.pyi", False), + ("tests/stubs/unused.pyi", True), + ("tests/snapshot_cases.toml", True), + ], +) +def test_symlinks_are_rejected_without_outside_writes(tmp_path, relative, dangling): + repo = sample(tmp_path) + path = repo / relative + outside = tmp_path / "outside" + if path.is_dir(): + shutil.copytree(path, outside) + shutil.rmtree(path) + else: + outside.write_bytes(path.read_bytes()) + path.unlink() + path.symlink_to(tmp_path / "missing" if dangling else outside) + before = tree_state(tmp_path) + with pytest.raises(HarnessError, match="symlink") as exc: + load_catalog(repo) + assert str(path) in str(exc.value) + assert tree_state(tmp_path) == before + + +@pytest.mark.parametrize( + "relative, clue", + [ + ("tests/snapshot_cases.toml", "snapshot_cases.toml"), + ("tests/stubs", "stubs"), + ("tests/errors", "errors"), + ("tests/stubs/shared/a.pyi", "cases.*a.*stubs.*demo/a.pyi"), + ], +) +def test_missing_roots_and_mapped_files(tmp_path, relative, clue): + repo = sample(tmp_path) + path = repo / relative + if path.is_dir(): + shutil.rmtree(path) + else: + path.unlink() + before = tree_state(repo) + with pytest.raises(HarnessError, match=clue) as exc: + load_catalog(repo) + assert str(path) in str(exc.value) + assert tree_state(repo) == before + + +@pytest.mark.parametrize( + "relative, replacement", + [ + ("tests/snapshot_cases.toml", "directory"), + ("tests/stubs", "file"), + ("tests/stubs/shared/a.pyi", "directory"), + ("tests/stubs/unused.pyi", "fifo"), + ("tests/errors/special", "fifo"), + ], +) +def test_nonregular_entries(tmp_path, relative, replacement): + repo = sample(tmp_path) + path = repo / relative + if path.is_dir(): + shutil.rmtree(path) + elif path.exists(): + path.unlink() + if replacement == "directory": + path.mkdir() + elif replacement == "file": + path.write_bytes(b"not a directory") + else: + os.mkfifo(path) + before = tree_state(repo) + with pytest.raises(HarnessError, match="directory|file|entry") as exc: + load_catalog(repo) + assert str(path) in str(exc.value) + assert tree_state(repo) == before + + +def test_whole_catalog_is_validated_before_selection(tmp_path): + repo = sample(tmp_path) + path = repo / "tests/snapshot_cases.toml" + path.write_text( + path.read_text().replace( + '[cases."b".errors]', '[cases."b".errors]\n"err" = "absent"' + ) + ) + with pytest.raises(HarnessError, match="cases.*b.*errors.*err"): + check_case(repo, "a", "stubs", {"demo/a.pyi": b"old\n"}) + + +@pytest.mark.parametrize("operation", ["catalog-read", "payload-read", "scan", "lstat"]) +def test_filesystem_errors_retain_cause_and_context(tmp_path, monkeypatch, operation): + repo = sample(tmp_path) + target = { + "catalog-read": repo / "tests/snapshot_cases.toml", + "payload-read": repo / "tests/stubs/unused.pyi", + "scan": repo / "tests/stubs/unreferenced", + "lstat": repo / "tests/stubs", + }[operation] + if operation == "scan": + target.mkdir() + before = tree_state(repo) + failure = OSError("injected failure", str(target)) + if operation == "scan": + original = os.scandir + + def scan(path): + if Path(path) == target: + raise failure + return original(path) + + monkeypatch.setattr(os, "scandir", scan) + else: + method = "lstat" if operation == "lstat" else "read_bytes" + original = getattr(Path, method) + + def access(path, *args, **kwargs): + if path == target: + raise failure + return original(path, *args, **kwargs) + + monkeypatch.setattr(Path, method, access) + with pytest.raises(HarnessError) as exc: + load_catalog(repo) + assert str(target) in str(exc.value) + assert str(repo / "tests/snapshot_cases.toml") in str(exc.value) + cause = exc.value + while cause.__cause__ is not None: + cause = cause.__cause__ + assert cause is failure + if operation in {"payload-read", "scan"}: + assert "unreferenced" in str(exc.value) + assert "cases[" not in str(exc.value) # Never invent ownership for inert files. + monkeypatch.undo() + assert tree_state(repo) == before + + +@pytest.mark.parametrize( + "case, kind, clue", + [("absent", "stubs", "unknown case"), ("a", "bad", "unknown kind")], +) +def test_unknown_selection_never_falls_back(tmp_path, case, kind, clue): + repo = sample(tmp_path) + before = tree_state(repo) + with pytest.raises(HarnessError, match=clue): + check_case(repo, case, kind, {}) + assert tree_state(repo) == before + + +@pytest.mark.parametrize( + "actual, only, clue", + [ + ({"../escape": b"x"}, None, "Unsafe"), + ({"x\0y": b"x"}, None, "Invalid"), + ({1: b"x"}, None, "Invalid"), + ({"x": "text"}, None, "bytes"), + ({"x": bytearray(b"x")}, None, "bytes"), + ({"x": b"", "x/y": b""}, None, "Conflicting"), + ({}, frozenset({"../x"}), "Unsafe"), + ({"demo/a.pyi": b"old\n"}, frozenset(), "scope"), + ], +) +def test_invalid_actual_and_scope(tmp_path, actual, only, clue): + repo = sample(tmp_path) + before = tree_state(repo) + with pytest.raises(HarnessError, match=clue): + check_case(repo, "a", "stubs", actual, only=only) + assert tree_state(repo) == before + + +def test_scope_selects_exact_logical_names(tmp_path): + repo = sample(tmp_path) + catalog = load_catalog(repo) + assert select_expected(catalog, "a", "stubs", {}, frozenset()) == {} + assert select_expected(catalog, "a", "stubs", {}, frozenset({"demo/a.pyi"})) == { + "demo/a.pyi": b"old\n" + } + assert select_expected(catalog, "a", "stubs", {}, frozenset({"new"})) == {} + check_case(repo, "a", "stubs", {}, only=frozenset()) + with pytest.raises(SnapshotMismatch, match="Unexpected: new"): + check_case(repo, "a", "stubs", {"new": b""}, only=frozenset({"new"})) + + +def test_byte_identity_and_sorted_changes(tmp_path): + repo = write_catalog( + tmp_path / "repo", + {"a": {"stubs": {"é.pyi": "raw.pyi"}, "errors": {}}}, + {"stubs": {"raw.pyi": b"\xff\r\n"}}, + ) + before = tree_state(repo) + check_case(repo, "a", "stubs", {"é.pyi": b"\xff\r\n"}) + with pytest.raises(SnapshotMismatch, match="Changed: é.pyi"): + check_case(repo, "a", "stubs", {"é.pyi": b"\xff\n"}) + assert changed_names( + {"z": b"", "a": b"x", "same": b"y"}, {"b": b"", "a": b"X", "same": b"y"} + ) == ("a", "b", "z") + assert changed_names({"x": b""}, {"x": b""}) == () + assert tree_state(repo) == before + + +def test_safe_path_planning_and_trusted_repository_alias(tmp_path): + repo = sample(tmp_path) + before = tree_state(repo) + assert ( + safe_path(repo, "tests/stubs/new/deep.pyi") == repo / "tests/stubs/new/deep.pyi" + ) + with pytest.raises(HarnessError, match="Non-directory"): + safe_path(repo, "tests/stubs/unused.pyi/child") + assert validate_name("single", component=True) == Path("single") + with pytest.raises(HarnessError, match="case identifier"): + validate_name("two/parts", component=True) + alias = tmp_path / "trusted-alias" + alias.symlink_to(repo, target_is_directory=True) + assert load_catalog(alias).repo == repo.resolve() + assert tree_state(repo) == before + + +BLOCKER = """ +import importlib.abc +import sys +class Block(importlib.abc.MetaPathFinder): + def find_spec(self, fullname, path=None, target=None): + if fullname.split('.')[0] in BLOCKED: + raise ModuleNotFoundError('blocked: ' + fullname) +sys.meta_path.insert(0, Block()) +""" + + +def run_blocked(code, blocked): + support = Path(__file__).resolve().parents[1] + result = subprocess.run( + [ + sys.executable, + "-I", + "-c", + f"BLOCKED = {blocked!r}\n" + + BLOCKER + + f"\nsys.path.insert(0, {str(support)!r})\n" + + code, + ], + text=True, + capture_output=True, + timeout=30, + ) + assert result.returncode == 0, result.stdout + result.stderr + assert result.stderr == "" + return result.stdout + + +def test_read_only_never_imports_editor(tmp_path): + repo = sample(tmp_path) + before = tree_state(repo) + assert run_blocked( + f""" +from pathlib import Path +from snapshot_catalog import check_case +print(check_case(Path({str(repo)!r}), 'a', 'stubs', {{'demo/a.pyi': b'old\\n'}})) +assert not BLOCKED.intersection(sys.modules) +""", + {"tomlkit", "snapshot_updates"}, + ).endswith("changed: ()\n") + assert tree_state(repo) == before + + +def test_parser_dependency_is_actionable_and_version_selected(): + output = run_blocked( + """ +from pathlib import Path +from snapshot_catalog import parse_model +from snapshot_helpers import HarnessError +try: + result = parse_model(b'format = 1\\n[cases]\\n', Path('example.toml')) +except HarnessError as error: + assert sys.version_info < (3, 11) + print(error) +else: + assert sys.version_info >= (3, 11) + assert result == {} + print('stdlib parser') +""", + {"tomli", "tomlkit"}, + ) + if sys.version_info < (3, 11): + assert re.search(r"tomli>=2,<3", output) + else: + assert output == "stdlib parser\n" diff --git a/tox.ini b/tox.ini index 67a8aad0..e834ca55 100644 --- a/tox.ini +++ b/tox.ini @@ -43,6 +43,8 @@ setenv = allowlist_externals = uv deps = pytest>=8,<9 + tomli>=2,<3; python_version < '3.11' + tomlkit>=0.13,<1 typing_extensions>=4,<5 cmeel=={env:STUBGEN_CMEEL_VERSION} cmeel-eigen=={env:STUBGEN_CMEEL_EIGEN_VERSION} @@ -67,6 +69,8 @@ allowlist_externals = uv deps = pytest>=8,<9 + tomli>=2,<3; python_version < '3.11' + tomlkit>=0.13,<1 commands_pre = commands = uv pip install --python "{envpython}" . diff --git a/uv.lock b/uv.lock index 2ad86972..6ab12d86 100644 --- a/uv.lock +++ b/uv.lock @@ -342,6 +342,8 @@ dev = [ { name = "pytest" }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tomlkit" }, { name = "typing-extensions" }, ] @@ -357,6 +359,8 @@ dev = [ { name = "pre-commit", specifier = ">=4,<5" }, { name = "pytest", specifier = ">=8,<9" }, { name = "scipy", specifier = "~=1.0" }, + { name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2,<3" }, + { name = "tomlkit", specifier = ">=0.13,<1" }, { name = "typing-extensions", specifier = ">=4,<5" }, ] @@ -651,6 +655,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, ] +[[package]] +name = "tomlkit" +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/96/e07752635b98536177fa1f37671c8f3cdde2e724c6bcf6034b2cfb571565/tomlkit-0.15.1.tar.gz", hash = "sha256:e25bbf38843005246210a12982776f27f99cb9be67160e14434d0c0d21ee1e97", size = 180129, upload-time = "2026-07-17T01:48:04.562Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/bc/8c13eb66537dce1d2bd3a57132902f38d0e7f5bb46fa9f4daed9fe9d76ee/tomlkit-0.15.1-py3-none-any.whl", hash = "sha256:177a05aece5a8ca5266fd3c448abb47b8d352f09d477d3ca8332db4d89b24304", size = 49449, upload-time = "2026-07-17T01:48:05.728Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" From 4bdf3f3a38a13c8f64b063fbf9a752fd569c0e18 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 19:02:18 +0000 Subject: [PATCH 37/40] test: add case-scoped TOML snapshot updates --- tests/snapshot_catalog.py | 5 + tests/snapshot_updates.py | 292 +++++++++++ tests/unit/test_snapshot_updates.py | 773 ++++++++++++++++++++++++++++ 3 files changed, 1070 insertions(+) create mode 100644 tests/snapshot_updates.py create mode 100644 tests/unit/test_snapshot_updates.py diff --git a/tests/snapshot_catalog.py b/tests/snapshot_catalog.py index 53efc0b2..e67a57f3 100644 --- a/tests/snapshot_catalog.py +++ b/tests/snapshot_catalog.py @@ -239,8 +239,13 @@ def check_case( kind: str, actual: Snapshot, *, + update: bool = False, only: frozenset[str] | None = None, ) -> str: + if update: + from snapshot_updates import update_snapshot + + return update_snapshot(repo, case_id, kind, actual, only=only).describe() try: catalog = load_catalog(repo) expected = select_expected(catalog, case_id, kind, actual, only) diff --git a/tests/snapshot_updates.py b/tests/snapshot_updates.py new file mode 100644 index 00000000..b043d4be --- /dev/null +++ b/tests/snapshot_updates.py @@ -0,0 +1,292 @@ +"""Serial, case-scoped copy-on-write catalog editing and publication.""" + +from __future__ import annotations + +import copy +import errno +import os +import stat +import tempfile +from dataclasses import dataclass +from pathlib import Path + +from snapshot_catalog import ( + Cases, + Catalog, + changed_names, + load_catalog, + parse_model, + safe_path, + select_expected, + validate_files, +) +from snapshot_helpers import HarnessError, Snapshot + + +@dataclass(frozen=True) +class UpdateReport: + catalog: Path + case_id: str + kind: str + changed: tuple[str, ...] + old_refs: dict[str, str] + new_refs: dict[str, str] + created: tuple[str, ...] + reused: tuple[str, ...] + removed: tuple[str, ...] + + def describe(self): + root = self.catalog.parent / self.kind + lines = [ + f"Case: {self.case_id}/{self.kind}", + f"Catalog: {self.catalog}; changed: {self.changed}", + ] + for name in self.changed: + old = ( + str(root / self.old_refs[name]) + if name in self.old_refs + else "" + ) + new = ( + str(root / self.new_refs[name]) + if name in self.new_refs + else "" + ) + lines.append(f"{name}: {old} -> {new}") + for action in ("created", "reused", "removed"): + lines.append( + f"{action}: {tuple(str(root / p) for p in getattr(self, action))}" + ) + return "\n".join(lines) + + +@dataclass +class UpdatePlan: + case_id: str + kind: str + changed: tuple[str, ...] + old_refs: dict[str, str] + new_refs: dict[str, str] + cases: Cases + snapshot: Snapshot + directory: str | None + new_files: Snapshot + reused: tuple[str, ...] + removed: tuple[str, ...] + + +def plan_update(catalog, case_id, kind, actual, only) -> UpdatePlan: + expected = select_expected(catalog, case_id, kind, actual, only) + changed = changed_names(expected, actual) + old_refs = catalog.mapping(case_id, kind) + new_refs = dict(old_refs) + intended = catalog.snapshot(case_id, kind) + pending = {} + reused = set() + for name in changed: + if name not in actual: + new_refs.pop(name, None) + intended.pop(name, None) + continue + intended[name] = actual[name] + matches = sorted( + { + mapping[kind][name] + for mapping in catalog.cases.values() + if name in mapping[kind] + and catalog.pools[kind][mapping[kind][name]] == actual[name] + } + ) + if matches: + new_refs[name] = matches[0] + reused.add(matches[0]) + else: + pending[name] = actual[name] + directory = None + new_files = {} + if pending: + number = 1 + while True: + directory = f"variants/{case_id}/{number}" + candidate = safe_path(catalog.repo, f"tests/{kind}/{directory}") + if not candidate.exists(): + break + number += 1 + for name, content in sorted(pending.items()): + physical = f"{directory}/{name}" + new_refs[name] = physical + new_files[physical] = content + cases = copy.deepcopy(catalog.cases) + cases[case_id][kind] = new_refs + used = {ref for kinds in cases.values() for ref in kinds[kind].values()} + displaced = {old_refs[name] for name in changed if name in old_refs} + removed = tuple(sorted(displaced - used)) + return UpdatePlan( + case_id, + kind, + changed, + old_refs, + new_refs, + cases, + intended, + directory, + new_files, + tuple(sorted(reused)), + removed, + ) + + +def render_update(catalog, plan) -> bytes: + try: + import tomlkit + except ImportError as exc: + raise HarnessError("Catalog editing requires tomlkit>=0.13,<1") from exc + document = tomlkit.parse(catalog.raw.decode("utf-8")) + table = document["cases"][plan.case_id][plan.kind] + newline = "\r\n" if b"\r\n" in catalog.raw else "\n" + for name in plan.changed: + if name in plan.new_refs: + is_new = name not in table + table[name] = plan.new_refs[name] + if is_new: + # tomlkit defaults new items to LF, even in a CRLF document. + table[name].trivia.trail = newline + else: + del table[name] + raw = tomlkit.dumps(document).encode("utf-8") + if parse_model(raw, catalog.path) != plan.cases: + raise HarnessError("Serialized catalog differs from the planned mappings") + return raw + + +def write_payloads(catalog, plan) -> None: + if plan.directory is None: + return + directory = safe_path(catalog.repo, f"tests/{plan.kind}/{plan.directory}") + directory.parent.mkdir(parents=True, exist_ok=True) + directory.mkdir(exist_ok=False) + for name, content in sorted(plan.new_files.items()): + path = safe_path(catalog.repo, f"tests/{plan.kind}/{name}") + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("xb") as stream: + stream.write(content) + + +def write_temporary_catalog(catalog, raw) -> Path: + temporary = None + try: + parent = safe_path(catalog.repo, "tests") + if not parent.is_dir(): + raise HarnessError(f"Expected catalog parent directory: {parent}") + with tempfile.NamedTemporaryFile( + mode="wb", + delete=False, + dir=catalog.path.parent, + prefix=".snapshot_cases-", + suffix=".toml", + ) as stream: + temporary = Path(stream.name) + os.fchmod(stream.fileno(), stat.S_IMODE(catalog.path.stat().st_mode)) + stream.write(raw) + except Exception as exc: + raise HarnessError( + f"Temporary catalog write failed; temporary={temporary}: {exc}" + ) from exc + return temporary + + +def publish_catalog(catalog, temporary) -> None: + for path in (catalog.path, temporary): + safe_path(catalog.repo, path.relative_to(catalog.repo).as_posix()) + if not path.is_file(): + raise HarnessError(f"Expected regular catalog file: {path}") + if catalog.path.read_bytes() != catalog.raw: + raise HarnessError(f"Catalog changed since loading: {catalog.path}") + os.replace(temporary, catalog.path) + + +def cleanup_payloads(catalog, plan) -> None: + root = catalog.repo / "tests" / plan.kind + parents = set() + for name in plan.removed: + path = safe_path(catalog.repo, f"tests/{plan.kind}/{name}") + path.unlink() + for parent in path.parents: + if parent == root: + break + parents.add(parent) + for parent in sorted(parents, key=lambda path: (-len(path.parts), str(path))): + safe_path(catalog.repo, parent.relative_to(catalog.repo).as_posix()) + try: + parent.rmdir() + except OSError as exc: + if exc.errno not in (errno.ENOTEMPTY, errno.EEXIST): + raise + + +def update_snapshot(repo, case_id, kind, actual, *, only=None): + try: + catalog = load_catalog(repo) + plan = plan_update(catalog, case_id, kind, actual, only) + except HarnessError as exc: + raise HarnessError( + f"Case {case_id}/{kind}; catalog not published: {exc}" + ) from exc + report = UpdateReport( + catalog.path, + case_id, + kind, + plan.changed, + plan.old_refs, + plan.new_refs, + tuple(sorted(plan.new_files)), + plan.reused, + plan.removed, + ) + if not plan.changed: + return report + published = False + temporary = None + try: + raw = render_update(catalog, plan) + write_payloads(catalog, plan) + pools = validate_files(catalog.repo, plan.cases) + prospective = Catalog(catalog.repo, catalog.path, raw, plan.cases, pools) + if prospective.snapshot(case_id, kind) != plan.snapshot: + raise HarnessError("Written payloads differ from the planned snapshot") + for other_case in catalog.cases: + for other_kind in ("stubs", "errors"): + if (other_case, other_kind) != (case_id, kind): + if prospective.snapshot(other_case, other_kind) != catalog.snapshot( + other_case, other_kind + ): + raise HarnessError("An unselected expectation changed") + temporary = write_temporary_catalog(catalog, raw) + publish_catalog(catalog, temporary) + published = True + cleanup_payloads(catalog, plan) + except Exception as exc: + state = "published (mappings already changed)" if published else "not published" + paths = tuple( + str(catalog.repo / "tests" / kind / p) for p in sorted(plan.new_files) + ) + # lexists is diagnostic-only: inspection errors must not hide the primary error. + existing = tuple(path for path in paths if os.path.lexists(path)) + directory = ( + catalog.repo / "tests" / kind / plan.directory + if plan.directory is not None + else None + ) + existing_directory = ( + directory if directory is not None and os.path.lexists(directory) else None + ) + cleanup = tuple(str(catalog.repo / "tests" / kind / p) for p in plan.removed) + raise HarnessError( + catalog.context(case_id, kind) + + f"Update failed; catalog {state}; temporary={temporary}; " + + f"planned payloads={paths}; existing payloads={existing}; " + + f"planned directory={plan.directory}; existing directory={existing_directory}; " + + f"cleanup targets={plan.removed}; " + + f"cleanup paths={cleanup}: {exc}" + ) from exc + return report diff --git a/tests/unit/test_snapshot_updates.py b/tests/unit/test_snapshot_updates.py new file mode 100644 index 00000000..aeda44dd --- /dev/null +++ b/tests/unit/test_snapshot_updates.py @@ -0,0 +1,773 @@ +"""Case-scoped edits against independently authored, disposable catalogs.""" + +from __future__ import annotations + +import copy +import errno +import os +import stat +import subprocess +import sys +from pathlib import Path + +import pytest + +import snapshot_updates as editor +from snapshot_catalog import check_case, load_catalog +from snapshot_helpers import HarnessError +from snapshot_test_support import tree_state, write_catalog +from snapshot_updates import plan_update, update_snapshot + + +def shared_repo(tmp_path): + cases = { + key: { + "stubs": {"demo/a.pyi": "shared/a.pyi"}, + "errors": {"demo.errors.stderr.txt": "shared/error.txt"}, + } + for key in ("a", "b") + } + return write_catalog( + tmp_path / "repo", + cases, + { + "stubs": {"shared/a.pyi": b"old\n", "unrelated.pyi": b"leave\n"}, + "errors": {"shared/error.txt": b"fatal\n"}, + }, + ) + + +def assert_live_unchanged(original): + assert original.path.read_bytes() == original.raw + after = load_catalog(original.repo) + for case in original.cases: + for kind in ("stubs", "errors"): + assert after.snapshot(case, kind) == original.snapshot(case, kind) + for kind, pool in original.pools.items(): + for name, data in pool.items(): + assert (original.repo / "tests" / kind / name).read_bytes() == data + + +def test_copy_on_write_then_reuse_does_not_change_other_case(tmp_path): + repo = shared_repo(tmp_path) + first = update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"new\n"}) + after = load_catalog(repo) + assert after.snapshot("a", "stubs") == {"demo/a.pyi": b"new\n"} + assert after.snapshot("b", "stubs") == {"demo/a.pyi": b"old\n"} + assert after.snapshot("a", "errors") == {"demo.errors.stderr.txt": b"fatal\n"} + assert first.created == ("variants/a/1/demo/a.pyi",) + second = update_snapshot(repo, "b", "stubs", {"demo/a.pyi": b"new\n"}) + final = load_catalog(repo) + assert final.mapping("a", "stubs") == final.mapping("b", "stubs") + assert second.created == () + assert second.reused == first.created + assert second.removed == ("shared/a.pyi",) + assert not (repo / "tests/stubs/shared").exists() + assert (repo / "tests/stubs/unrelated.pyi").read_bytes() == b"leave\n" + assert (repo / "tests/errors/shared/error.txt").read_bytes() == b"fatal\n" + text = first.describe() + assert "Case: a/stubs" in text + assert str(repo / "tests/snapshot_cases.toml") in text + assert ( + f"demo/a.pyi: {repo}/tests/stubs/shared/a.pyi -> {repo}/tests/stubs/variants/a/1/demo/a.pyi" + in text + ) + assert "created:" in text and "reused: ()" in text and "removed: ()" in text + + +def test_noop_is_byte_identical(tmp_path): + repo = shared_repo(tmp_path) + # Even a blocked allocation namespace is irrelevant to a no-op. + (repo / "tests/stubs/variants").write_bytes(b"occupied") + before = tree_state(repo) + report = update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"old\n"}) + assert report.changed == report.created == report.reused == report.removed == () + assert tree_state(repo) == before + + +def test_planning_preserves_model_and_files(tmp_path): + repo = shared_repo(tmp_path) + catalog = load_catalog(repo) + model, files = copy.deepcopy(catalog.cases), tree_state(repo) + plan = plan_update(catalog, "a", "stubs", {"demo/a.pyi": b"new\n"}, None) + assert plan.cases != model + assert catalog.cases == model + assert tree_state(repo) == files + assert plan.snapshot == {"demo/a.pyi": b"new\n"} + plan.cases["b"]["errors"].clear() + assert catalog.cases == model + + +def test_full_add_delete_uses_one_directory_and_never_overwrites_single_owner(tmp_path): + repo = write_catalog( + tmp_path / "repo", + {"a": {"stubs": {"gone": "old/gone", "keep": "old/keep"}, "errors": {}}}, + {"stubs": {"old/gone": b"gone", "old/keep": b"keep", "old/inert": b"inert"}}, + ) + # An open descriptor still sees the old inode after orphan unlinking. + with (repo / "tests/stubs/old/keep").open("rb") as old_file: + report = update_snapshot( + repo, "a", "stubs", {"keep": b"changed", "new/deep": b"new"} + ) + assert old_file.read() == b"keep" + assert report.changed == ("gone", "keep", "new/deep") + assert report.old_refs == {"gone": "old/gone", "keep": "old/keep"} + assert report.new_refs == { + "keep": "variants/a/1/keep", + "new/deep": "variants/a/1/new/deep", + } + assert report.created == ("variants/a/1/keep", "variants/a/1/new/deep") + assert report.removed == ("old/gone", "old/keep") + assert (repo / "tests/stubs/old/inert").read_bytes() == b"inert" + assert load_catalog(repo).snapshot("a", "stubs") == { + "keep": b"changed", + "new/deep": b"new", + } + assert "gone:" in report.describe() and "-> " in report.describe() + assert "new/deep: ->" in report.describe() + + +def test_only_updates_selected_error_names(tmp_path): + repo = write_catalog( + tmp_path / "repo", + {"a": {"stubs": {}, "errors": {"one": "one", "two": "two", "gone": "gone"}}}, + {"errors": {"one": b"1", "two": b"2", "gone": b"g"}}, + ) + report = update_snapshot( + repo, + "a", + "errors", + {"one": b"new", "added": b"a"}, + only=frozenset({"one", "gone", "added"}), + ) + assert report.changed == ("added", "gone", "one") + assert load_catalog(repo).snapshot("a", "errors") == { + "one": b"new", + "two": b"2", + "added": b"a", + } + assert report.new_refs["two"] == "two" + assert load_catalog(repo).snapshot("a", "stubs") == {} + + +@pytest.mark.parametrize( + "actual,only", + [ + ({"outside": b"new"}, frozenset({"demo/a.pyi"})), + ({"demo": b"ancestor"}, frozenset({"demo"})), + ({"demo/a.pyi/child": b"descendant"}, frozenset({"demo/a.pyi/child"})), + ({"../escape": b"bad"}, None), + ({"demo/a.pyi": "not bytes"}, None), + ], +) +def test_invalid_update_fails_before_mutation(tmp_path, actual, only): + repo = shared_repo(tmp_path) + before = tree_state(repo) + with pytest.raises(HarnessError, match="not published"): + update_snapshot(repo, "a", "stubs", actual, only=only) + assert tree_state(repo) == before + + +@pytest.mark.parametrize("case,kind", [("unknown", "stubs"), ("a", "unknown")]) +def test_unknown_selection_is_not_created(tmp_path, case, kind): + repo = shared_repo(tmp_path) + before = tree_state(repo) + with pytest.raises(HarnessError, match="not published"): + update_snapshot(repo, case, kind, {}) + assert tree_state(repo) == before + + +@pytest.mark.parametrize( + "before,after", [("node", "node/child"), ("node/child", "node")] +) +def test_full_file_directory_shape_changes(tmp_path, before, after): + repo = write_catalog( + tmp_path / "repo", + {"a": {"stubs": {before: "old/payload"}, "errors": {}}}, + {"stubs": {"old/payload": b"old"}}, + ) + update_snapshot(repo, "a", "stubs", {after: b"new"}) + assert load_catalog(repo).snapshot("a", "stubs") == {after: b"new"} + assert not (repo / "tests/stubs/old").exists() + assert (repo / "tests/stubs").is_dir() + + +def test_reuse_is_lexicographic_and_only_referenced_same_kind_and_name(tmp_path): + repo = write_catalog( + tmp_path / "repo", + { + "a": {"stubs": {"name": "old"}, "errors": {"name": "000-error"}}, + "b": {"stubs": {"name": "z-match", "other": "001-other"}, "errors": {}}, + "c": {"stubs": {"name": "a-match"}, "errors": {}}, + }, + { + "stubs": { + "old": b"old", + "z-match": b"new", + "a-match": b"new", + "001-other": b"new", + "000-inert": b"new", + }, + "errors": {"000-error": b"new"}, + }, + ) + report = update_snapshot(repo, "a", "stubs", {"name": b"new"}) + assert report.new_refs == {"name": "a-match"} + assert report.reused == ("a-match",) + assert report.created == () + assert not (repo / "tests/stubs/variants").exists() + assert (repo / "tests/stubs/000-inert").read_bytes() == b"new" + + +def test_unreferenced_identical_payload_is_not_reused(tmp_path): + repo = shared_repo(tmp_path) + (repo / "tests/stubs/000-inert").write_bytes(b"new\n") + report = update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"new\n"}) + assert report.created == ("variants/a/1/demo/a.pyi",) + assert report.reused == () + assert (repo / "tests/stubs/000-inert").read_bytes() == b"new\n" + + +def test_allocation_skips_numbered_files_and_empty_directories(tmp_path): + repo = shared_repo(tmp_path) + parent = repo / "tests/stubs/variants/a" + (parent / "1").mkdir(parents=True) + (parent / "2").write_bytes(b"occupied") + report = update_snapshot( + repo, "a", "stubs", {"demo/a.pyi": b"new", "demo/b.pyi": b"other"} + ) + assert report.created == ("variants/a/3/demo/a.pyi", "variants/a/3/demo/b.pyi") + assert (parent / "1").is_dir() + assert (parent / "2").read_bytes() == b"occupied" + + +@pytest.mark.parametrize( + "relative,entry", + [ + ("variants", "file"), + ("variants/a", "file"), + ("variants/a/1", "symlink"), + ("variants/a", "symlink"), + ("variants/a/1", "fifo"), + ], +) +def test_bad_namespace_entries_fail_without_writes(tmp_path, relative, entry): + repo = shared_repo(tmp_path) + target = repo / "tests/stubs" / relative + target.parent.mkdir(parents=True, exist_ok=True) + if entry == "file": + target.write_bytes(b"occupied") + elif entry == "symlink": + target.symlink_to(tmp_path / "absent") + else: + os.mkfifo(target) + before = tree_state(repo) + with pytest.raises(HarnessError, match="not published"): + update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"new"}) + assert tree_state(repo) == before + + +@pytest.mark.parametrize("newline", ["\n", "\r\n"]) +def test_value_replacement_preserves_exact_comments_and_newlines(tmp_path, newline): + repo = shared_repo(tmp_path) + text = "\n".join( + [ + "# top comment", + "format = 1 # format", + "", + "[cases.a.stubs] # selected table", + '"demo/a.pyi" = "shared/a.pyi" # attached', + "# unrelated standalone", + "", + "[cases.a.errors]", + '"demo.errors.stderr.txt" = "shared/error.txt" # error', + "", + "[cases.b.stubs] # other case", + '"demo/a.pyi" = "shared/a.pyi"', + "[cases.b.errors]", + '"demo.errors.stderr.txt" = "shared/error.txt"', + "", + ] + ).replace("\n", newline) + path = repo / "tests/snapshot_cases.toml" + path.write_bytes(text.encode()) + update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"new"}) + expected = text.replace( + '"shared/a.pyi" # attached', '"variants/a/1/demo/a.pyi" # attached', 1 + ) + assert path.read_bytes() == expected.encode() + + +@pytest.mark.parametrize("newline", ["\n", "\r\n"]) +def test_delete_and_append_preserve_unrelated_comments_and_table_order( + tmp_path, newline +): + repo = write_catalog( + tmp_path / "repo", + {"a": {"stubs": {"gone": "gone", "keep": "keep"}, "errors": {}}}, + {"stubs": {"gone": b"g", "keep": b"k"}}, + ) + text = '# top\nformat = 1\n[cases.a.stubs] # table\n"gone" = "gone" # delete me\n# keep standalone\n"keep" = "keep" # inline\n[cases.a.errors] # last\n'.replace( + "\n", newline + ) + path = repo / "tests/snapshot_cases.toml" + path.write_bytes(text.encode()) + update_snapshot(repo, "a", "stubs", {"keep": b"k", "added": b"a"}) + expected = text.replace('"gone" = "gone" # delete me' + newline, "").replace( + "[cases.a.errors]", + 'added = "variants/a/1/added"' + newline + "[cases.a.errors]", + ) + assert path.read_bytes() == expected.encode() + + +def test_noop_and_readonly_work_without_editor_dependency(tmp_path): + repo = shared_repo(tmp_path) + before = tree_state(repo) + script = """ +import importlib.abc +import sys +from pathlib import Path +sys.path.insert(0, sys.argv[1]) +class Block(importlib.abc.MetaPathFinder): + def find_spec(self, fullname, *args): + if fullname == "tomlkit": + raise ImportError("blocked tomlkit") +sys.meta_path.insert(0, Block()) +from snapshot_catalog import check_case +from snapshot_helpers import HarnessError +repo = Path(sys.argv[2]) +assert "snapshot_updates" not in sys.modules +check_case(repo, "a", "stubs", {"demo/a.pyi": b"old\\n"}) +assert "snapshot_updates" not in sys.modules +check_case(repo, "a", "stubs", {"demo/a.pyi": b"old\\n"}, update=True) +assert "tomlkit" not in sys.modules +try: + check_case(repo, "a", "stubs", {"demo/a.pyi": b"new\\n"}, update=True) +except HarnessError as exc: + assert "tomlkit>=0.13,<1" in str(exc), str(exc) + assert "not published" in str(exc), str(exc) +else: + raise AssertionError("missing dependency accepted") +""" + result = subprocess.run( + [ + sys.executable, + "-I", + "-c", + script, + str(Path(__file__).resolve().parents[1]), + str(repo), + ], + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stdout + result.stderr + assert tree_state(repo) == before + + +def test_check_case_delegates_scope_and_reports_update(tmp_path): + repo = shared_repo(tmp_path) + text = check_case( + repo, "a", "stubs", {"added": b"new"}, update=True, only=frozenset({"added"}) + ) + assert "changed: ('added',)" in text + assert str(repo / "tests/stubs/variants/a/1/added") in text + assert load_catalog(repo).snapshot("a", "stubs") == { + "demo/a.pyi": b"old\n", + "added": b"new", + } + + +@pytest.mark.parametrize( + "corruption", ["path", "ownership", "serializer", "parser", "model"] +) +def test_render_validation_fails_before_allocation(tmp_path, monkeypatch, corruption): + import tomlkit + + repo = shared_repo(tmp_path) + before = tree_state(repo) + real_plan = editor.plan_update + if corruption in {"path", "ownership", "model"}: + + def bad_plan(*args): + plan = real_plan(*args) + if corruption == "path": + plan.new_refs["demo/a.pyi"] = "../escape" + elif corruption == "ownership": + plan.new_refs["different"] = "shared/a.pyi" + plan.changed += ("different",) + else: + plan.cases["b"]["errors"] = {} + return plan + + monkeypatch.setattr(editor, "plan_update", bad_plan) + else: + + def fail(*args): + raise ValueError(f"injected {corruption}") + + monkeypatch.setattr( + tomlkit, "dumps" if corruption == "serializer" else "parse", fail + ) + with pytest.raises(HarnessError, match="not published"): + update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"new"}) + assert tree_state(repo) == before + + +@pytest.mark.parametrize( + "stage", + ["render_update", "write_payloads", "write_temporary_catalog", "publish_catalog"], +) +def test_prepublication_failure_preserves_live_references(tmp_path, monkeypatch, stage): + repo = shared_repo(tmp_path) + original = load_catalog(repo) + + def fail(*args, **kwargs): + raise OSError(f"injected {stage}") + + monkeypatch.setattr(editor, stage, fail) + with pytest.raises(HarnessError, match="not published") as caught: + update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"changed\n"}) + assert f"injected {stage}" in str(caught.value) + assert isinstance(caught.value.__cause__, OSError) + assert_live_unchanged(original) + for path in (repo / "tests/stubs/variants").rglob("*"): + if path.is_file(): + assert str(path) in str(caught.value) + for path in (repo / "tests").glob(".snapshot_cases-*.toml"): + assert str(path) in str(caught.value) + + +def test_partial_exclusive_payload_write_reports_existing_not_uncreated_files( + tmp_path, monkeypatch +): + repo = shared_repo(tmp_path) + original = load_catalog(repo) + real_open = Path.open + + class PartialWrite: + def __init__(self, stream): + self.stream = stream + + def __enter__(self): + return self + + def __exit__(self, *args): + self.stream.close() + + def write(self, data): + self.stream.write(data[:2]) + self.stream.flush() + raise OSError("injected partial xb write") + + def open_file(path, mode="r", *args, **kwargs): + stream = real_open(path, mode, *args, **kwargs) + return PartialWrite(stream) if mode == "xb" else stream + + monkeypatch.setattr(Path, "open", open_file) + with pytest.raises(HarnessError, match="not published") as caught: + update_snapshot( + repo, "a", "stubs", {"demo/a.pyi": b"changed", "demo/z.pyi": b"later"} + ) + partial = repo / "tests/stubs/variants/a/1/demo/a.pyi" + absent = repo / "tests/stubs/variants/a/1/demo/z.pyi" + assert partial.read_bytes() == b"ch" + assert not absent.exists() + message = str(caught.value) + assert "injected partial xb write" in message + assert str(partial) in message and "planned directory=variants/a/1" in message + assert f"existing payloads={(str(partial),)}" in message + assert f"existing directory={repo}/tests/stubs/variants/a/1" in message + assert_live_unchanged(original) + + +def test_exclusive_directory_creation_rejects_late_occupant(tmp_path, monkeypatch): + repo = shared_repo(tmp_path) + original = load_catalog(repo) + real_write = editor.write_payloads + + def occupy(catalog, plan): + directory = repo / "tests/stubs" / plan.directory + directory.mkdir(parents=True) + (directory / "sentinel").write_bytes(b"external") + real_write(catalog, plan) + + monkeypatch.setattr(editor, "write_payloads", occupy) + with pytest.raises(HarnessError, match="not published"): + update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"new"}) + assert (repo / "tests/stubs/variants/a/1/sentinel").read_bytes() == b"external" + assert_live_unchanged(original) + + +def test_temporary_partial_write_reports_its_path_and_cause(tmp_path, monkeypatch): + repo = shared_repo(tmp_path) + original = load_catalog(repo) + real_temporary = editor.tempfile.NamedTemporaryFile + + class PartialTemporary: + def __init__(self, stream): + self.stream = stream + self.name = stream.name + + def __enter__(self): + return self + + def __exit__(self, *args): + self.stream.close() + + def write(self, data): + self.stream.write(data[:7]) + self.stream.flush() + raise OSError("injected temporary write") + + def fileno(self): + return self.stream.fileno() + + def temporary(*args, **kwargs): + return PartialTemporary(real_temporary(*args, **kwargs)) + + monkeypatch.setattr(editor.tempfile, "NamedTemporaryFile", temporary) + with pytest.raises(HarnessError, match="not published") as caught: + update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"new"}) + leftovers = list((repo / "tests").glob(".snapshot_cases-*.toml")) + assert len(leftovers) == 1 + assert len(leftovers[0].read_bytes()) == 7 + assert str(leftovers[0]) in str(caught.value) + assert "injected temporary write" in str(caught.value) + assert isinstance(caught.value.__cause__.__cause__, OSError) + assert_live_unchanged(original) + + +def test_replace_failure_retains_old_catalog_and_names_temporary(tmp_path, monkeypatch): + repo = shared_repo(tmp_path) + original = load_catalog(repo) + + def fail(*args): + raise OSError("injected replace") + + monkeypatch.setattr(editor.os, "replace", fail) + with pytest.raises(HarnessError, match="not published") as caught: + update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"new"}) + (temporary,) = (repo / "tests").glob(".snapshot_cases-*.toml") + assert str(temporary) in str(caught.value) + assert "injected replace" in str(caught.value) + assert isinstance(caught.value.__cause__, OSError) + assert_live_unchanged(original) + + +def test_publication_preserves_mode(tmp_path): + repo = shared_repo(tmp_path) + path = repo / "tests/snapshot_cases.toml" + path.chmod(0o640) + update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"new"}) + assert stat.S_IMODE(path.stat().st_mode) == 0o640 + assert not list(path.parent.glob(".snapshot_cases-*.toml")) + + +def test_stale_catalog_edit_survives_publication_attempt(tmp_path, monkeypatch): + repo = shared_repo(tmp_path) + original = load_catalog(repo) + external = original.raw + b"\n# external edit\n" + real_publish = editor.publish_catalog + + def edit_then_publish(catalog, temporary): + catalog.path.write_bytes(external) + real_publish(catalog, temporary) + + monkeypatch.setattr(editor, "publish_catalog", edit_then_publish) + with pytest.raises(HarnessError, match="not published") as caught: + update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"new"}) + assert "changed" in str(caught.value).lower() + assert original.path.read_bytes() == external + assert load_catalog(repo).snapshot("a", "stubs") == {"demo/a.pyi": b"old\n"} + + +@pytest.mark.parametrize( + "target,entry", + [ + ("catalog", "symlink"), + ("temporary", "symlink"), + ("catalog", "directory"), + ("temporary", "directory"), + ], +) +def test_publish_rejects_nonregular_substitutions(tmp_path, monkeypatch, target, entry): + repo = shared_repo(tmp_path) + outside = tmp_path / "outside" + outside.write_bytes(b"untouched") + real_publish = editor.publish_catalog + + def substitute(catalog, temporary): + path = catalog.path if target == "catalog" else temporary + path.unlink() + if entry == "symlink": + path.symlink_to(outside) + else: + path.mkdir() + real_publish(catalog, temporary) + + monkeypatch.setattr(editor, "publish_catalog", substitute) + with pytest.raises(HarnessError, match="not published"): + update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"new"}) + assert outside.read_bytes() == b"untouched" + assert (repo / "tests/stubs/shared/a.pyi").read_bytes() == b"old\n" + + +def test_corrupted_new_payload_is_not_published(tmp_path, monkeypatch): + repo = shared_repo(tmp_path) + original = load_catalog(repo) + real_write = editor.write_payloads + + def corrupt(catalog, plan): + real_write(catalog, plan) + (repo / "tests/stubs/variants/a/1/demo/a.pyi").write_bytes(b"corrupted") + + monkeypatch.setattr(editor, "write_payloads", corrupt) + with pytest.raises(HarnessError, match="not published.*Written payloads differ"): + update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"new"}) + assert_live_unchanged(original) + + +@pytest.mark.parametrize("operation", ["unlink", "rmdir", "wrapper"]) +def test_cleanup_failure_is_postpublication_and_names_leftovers( + tmp_path, monkeypatch, operation +): + repo = write_catalog( + tmp_path / "repo", + { + "a": {"stubs": {"name": "old/deep/payload"}, "errors": {}}, + "b": {"stubs": {"name": "other/payload"}, "errors": {}}, + }, + { + "stubs": { + "old/deep/payload": b"old", + "other/payload": b"other", + "inert/leave": b"leave", + } + }, + ) + + def fail(*args, **kwargs): + raise OSError(errno.EACCES, f"injected cleanup {operation}") + + if operation == "wrapper": + monkeypatch.setattr(editor, "cleanup_payloads", fail) + else: + monkeypatch.setattr(Path, operation, fail) + with pytest.raises(HarnessError, match="catalog published") as caught: + update_snapshot(repo, "a", "stubs", {"name": b"new"}) + message = str(caught.value) + assert "mappings already changed" in message + assert f"injected cleanup {operation}" in message + assert isinstance(caught.value.__cause__, OSError) + assert "cleanup targets=('old/deep/payload',)" in message + assert str(repo / "tests/stubs/old/deep/payload") in message + assert load_catalog(repo).snapshot("a", "stubs") == {"name": b"new"} + assert load_catalog(repo).snapshot("b", "stubs") == {"name": b"other"} + assert (repo / "tests/stubs/inert/leave").read_bytes() == b"leave" + if operation == "rmdir": + assert not (repo / "tests/stubs/old/deep/payload").exists() + assert (repo / "tests/stubs/old/deep").is_dir() + else: + assert (repo / "tests/stubs/old/deep/payload").read_bytes() == b"old" + + +@pytest.mark.parametrize("code", [errno.ENOTEMPTY, errno.EEXIST]) +def test_cleanup_suppresses_only_nonempty_directory_errors(tmp_path, monkeypatch, code): + repo = write_catalog( + tmp_path / "repo", + {"a": {"stubs": {"name": "old/deep/payload"}, "errors": {}}}, + {"stubs": {"old/deep/payload": b"old"}}, + ) + + def nonempty(*args): + raise OSError(code, "nonempty") + + monkeypatch.setattr(Path, "rmdir", nonempty) + report = update_snapshot(repo, "a", "stubs", {"name": b"new"}) + assert report.removed == ("old/deep/payload",) + assert not (repo / "tests/stubs/old/deep/payload").exists() + assert load_catalog(repo).snapshot("a", "stubs") == {"name": b"new"} + + +def test_payload_writer_rechecks_late_namespace_symlink(tmp_path, monkeypatch): + repo = shared_repo(tmp_path) + original = load_catalog(repo) + outside = tmp_path / "outside" + outside.mkdir() + real_write = editor.write_payloads + + def substitute(catalog, plan): + (repo / "tests/stubs/variants").symlink_to(outside, target_is_directory=True) + real_write(catalog, plan) + + monkeypatch.setattr(editor, "write_payloads", substitute) + with pytest.raises(HarnessError, match="not published.*symlink"): + update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"new"}) + assert tree_state(outside) == {} + assert original.path.read_bytes() == original.raw + assert (repo / "tests/stubs/shared/a.pyi").read_bytes() == b"old\n" + + +def test_payload_file_creation_is_exclusive(tmp_path, monkeypatch): + repo = shared_repo(tmp_path) + original = load_catalog(repo) + real_open = Path.open + target = repo / "tests/stubs/variants/a/1/demo/a.pyi" + + def occupy(path, mode="r", *args, **kwargs): + if path == target: + # Simulate an occupant appearing immediately before the real open. + if mode in {"xb", "wb"}: + with real_open(path, "wb") as stream: + stream.write(b"external") + return real_open(path, mode, *args, **kwargs) + + monkeypatch.setattr(Path, "open", occupy) + with pytest.raises(HarnessError, match="not published"): + update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"new"}) + assert target.read_bytes() == b"external" + assert_live_unchanged(original) + + +def test_temporary_writer_rechecks_parent_before_creation(tmp_path, monkeypatch): + repo = shared_repo(tmp_path) + original = load_catalog(repo) + saved = repo / "saved-tests" + real_write = editor.write_temporary_catalog + + def substitute(catalog, raw): + (repo / "tests").rename(saved) + (repo / "tests").symlink_to(saved, target_is_directory=True) + return real_write(catalog, raw) + + monkeypatch.setattr(editor, "write_temporary_catalog", substitute) + with pytest.raises(HarnessError, match="not published.*symlink"): + update_snapshot(repo, "a", "stubs", {"demo/a.pyi": b"new"}) + assert not list(saved.glob(".snapshot_cases-*.toml")) + assert (saved / "snapshot_cases.toml").read_bytes() == original.raw + assert (saved / "stubs/shared/a.pyi").read_bytes() == b"old\n" + + +def test_cleanup_rechecks_orphan_before_unlink(tmp_path, monkeypatch): + repo = write_catalog( + tmp_path / "repo", + {"a": {"stubs": {"name": "old"}, "errors": {}}}, + {"stubs": {"old": b"old"}}, + ) + outside = tmp_path / "outside" + outside.write_bytes(b"untouched") + real_cleanup = editor.cleanup_payloads + + def substitute(catalog, plan): + old = repo / "tests/stubs/old" + old.unlink() + old.symlink_to(outside) + real_cleanup(catalog, plan) + + monkeypatch.setattr(editor, "cleanup_payloads", substitute) + with pytest.raises(HarnessError, match="catalog published.*symlink"): + update_snapshot(repo, "a", "stubs", {"name": b"new"}) + assert (repo / "tests/stubs/old").is_symlink() + assert outside.read_bytes() == b"untouched" + assert b"variants/a/1/name" in (repo / "tests/snapshot_cases.toml").read_bytes() From fa1c133be4f2f033e093df974f0a748992306fae Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 25 Sep 2026 19:33:31 +0000 Subject: [PATCH 38/40] test: share snapshot payloads through explicit TOML cases --- tests/errors/pybind11-v2.11 | 1 - tests/errors/pybind11-v2.12 | 1 - tests/errors/pybind11-v2.13 | 1 - tests/snapshot_cases.toml | 469 ++++++++++++++++++ tests/snapshot_helpers.py | 25 +- .../__init__.pyi => demo/__init__/shared.pyi} | 0 .../_bindings/__init__/shared.pyi} | 0 .../__init__/py310-pb213-annotated.pyi} | 0 .../__init__/py310-pb30-annotated.pyi} | 0 .../_bindings/aliases/foreign_arg/shared.pyi} | 0 .../aliases/foreign_attr/shared.pyi} | 0 .../aliases/foreign_class_member/shared.pyi} | 0 .../aliases/foreign_method_arg/shared.pyi} | 0 .../aliases/foreign_method_return/shared.pyi} | 0 .../aliases/foreign_return/shared.pyi} | 0 .../aliases/missing_self_arg/shared.pyi} | 0 .../classes/py310-pb213-annotated.pyi} | 0 .../classes/py310-pb30-annotated.pyi} | 0 .../eigen/py310-pb213-annotated.pyi} | 0 .../_bindings/eigen/py310-pb30-annotated.pyi} | 0 .../_bindings/eigen/py313-pb213-type-var.pyi} | 0 .../_bindings/eigen/py313-pb30-type-var.pyi} | 0 .../_bindings/enum/py310-pb213-annotated.pyi} | 0 .../_bindings/enum/py310-pb30-annotated.pyi} | 0 .../_bindings/enum/py311-pb30-annotated.pyi} | 0 .../_bindings/enum/py312-pb30-annotated.pyi} | 0 .../py310-pb213-annotated.pyi} | 0 .../flawed_bindings/py310-pb30-annotated.pyi} | 0 .../functions/py310-pb213-annotated.pyi} | 0 .../functions/py310-pb30-annotated.pyi} | 0 .../functions/py312-pb213-annotated.pyi} | 0 .../functions/py312-pb30-annotated.pyi} | 0 .../functions/py313-pb211-annotated.pyi} | 0 .../functions/py313-pb29-annotated.pyi} | 0 .../_bindings/hidden_builtins/shared.pyi} | 0 .../issues/py310-pb213-annotated.pyi} | 0 .../issues/py310-pb30-annotated.pyi} | 0 .../methods/py310-pb213-annotated.pyi} | 0 .../methods/py310-pb30-annotated.pyi} | 0 .../numpy/py310-pb213-annotated.pyi} | 0 .../_bindings/numpy/py310-pb30-annotated.pyi} | 0 .../_bindings/numpy/py313-pb213-type-var.pyi} | 0 .../properties/py310-pb213-annotated.pyi} | 0 .../properties/py310-pb30-annotated.pyi} | 0 .../properties/py312-pb213-annotated.pyi} | 0 .../properties/py312-pb30-annotated.pyi} | 0 .../_bindings/stl/py310-pb213-annotated.pyi} | 0 .../_bindings/stl/py310-pb30-annotated.pyi} | 0 .../_bindings/stl/py313-pb29-annotated.pyi} | 0 .../stl_bind/py310-pb213-annotated.pyi} | 0 .../stl_bind/py310-pb30-annotated.pyi} | 0 .../stl_bind/py313-pb211-annotated.pyi} | 0 .../stl_bind/py313-pb29-annotated.pyi} | 0 .../typing/py310-pb213-annotated.pyi} | 0 .../typing/py310-pb30-annotated.pyi} | 0 .../typing/py312-pb30-annotated.pyi} | 0 .../_bindings/values/shared.pyi} | 0 .../demo/core.pyi => demo/core/shared.pyi} | 0 .../pure_python/__init__/shared.pyi} | 0 .../pure_python/classes/shared.pyi} | 0 .../pure_python/functions/shared.pyi} | 0 .../functions_3_8_plus/shared.pyi} | 0 .../functions_3_9_plus/shared.pyi} | 0 .../pure_python/values/shared.pyi} | 0 tests/stubs/python-3.10/pybind11-v2.13 | 1 - .../demo/__init__.pyi | 43 -- .../demo/_bindings/__init__.pyi | 37 -- .../demo/_bindings/aliases/foreign_arg.pyi | 7 - .../demo/_bindings/aliases/foreign_attr.pyi | 6 - .../aliases/foreign_class_member.pyi | 12 - .../_bindings/aliases/foreign_method_arg.pyi | 8 - .../aliases/foreign_method_return.pyi | 9 - .../demo/_bindings/aliases/foreign_return.pyi | 7 - .../_bindings/aliases/missing_self_arg.pyi | 8 - .../demo/_bindings/hidden_builtins.pyi | 7 - .../demo/_bindings/values.pyi | 36 -- .../demo/core.pyi | 37 -- .../demo/pure_python/__init__.pyi | 11 - .../demo/pure_python/classes.pyi | 21 - .../demo/pure_python/functions.pyi | 31 -- .../demo/pure_python/functions_3_8_plus.pyi | 30 -- .../demo/pure_python/functions_3_9_plus.pyi | 5 - .../demo/pure_python/values.pyi | 4 - .../demo/__init__.pyi | 43 -- .../demo/_bindings/__init__.pyi | 37 -- .../demo/_bindings/aliases/__init__.pyi | 52 -- .../demo/_bindings/aliases/foreign_arg.pyi | 7 - .../demo/_bindings/aliases/foreign_attr.pyi | 6 - .../aliases/foreign_class_member.pyi | 12 - .../_bindings/aliases/foreign_method_arg.pyi | 8 - .../aliases/foreign_method_return.pyi | 9 - .../demo/_bindings/aliases/foreign_return.pyi | 7 - .../_bindings/aliases/missing_self_arg.pyi | 8 - .../demo/_bindings/classes.pyi | 94 ---- .../demo/_bindings/eigen.pyi | 75 --- .../demo/_bindings/flawed_bindings.pyi | 27 - .../demo/_bindings/functions.pyi | 127 ----- .../demo/_bindings/hidden_builtins.pyi | 7 - .../demo/_bindings/issues.pyi | 46 -- .../demo/_bindings/methods.pyi | 12 - .../demo/_bindings/numpy.pyi | 24 - .../demo/_bindings/properties.pyi | 109 ---- .../demo/_bindings/stl.pyi | 32 -- .../demo/_bindings/stl_bind.pyi | 145 ------ .../demo/_bindings/typing.pyi | 10 - .../demo/_bindings/values.pyi | 36 -- .../demo/core.pyi | 37 -- .../demo/pure_python/__init__.pyi | 11 - .../demo/pure_python/classes.pyi | 21 - .../demo/pure_python/functions.pyi | 31 -- .../demo/pure_python/functions_3_8_plus.pyi | 30 -- .../demo/pure_python/functions_3_9_plus.pyi | 5 - .../demo/pure_python/values.pyi | 4 - .../demo/__init__.pyi | 43 -- .../demo/_bindings/__init__.pyi | 37 -- .../demo/_bindings/aliases/__init__.pyi | 52 -- .../demo/_bindings/aliases/foreign_arg.pyi | 7 - .../demo/_bindings/aliases/foreign_attr.pyi | 6 - .../aliases/foreign_class_member.pyi | 12 - .../_bindings/aliases/foreign_method_arg.pyi | 8 - .../aliases/foreign_method_return.pyi | 9 - .../demo/_bindings/aliases/foreign_return.pyi | 7 - .../_bindings/aliases/missing_self_arg.pyi | 8 - .../demo/_bindings/classes.pyi | 80 --- .../demo/_bindings/eigen.pyi | 101 ---- .../demo/_bindings/enum.pyi | 71 --- .../demo/_bindings/flawed_bindings.pyi | 23 - .../demo/_bindings/hidden_builtins.pyi | 7 - .../demo/_bindings/issues.pyi | 43 -- .../demo/_bindings/methods.pyi | 8 - .../demo/_bindings/numpy.pyi | 21 - .../demo/_bindings/stl.pyi | 21 - .../demo/_bindings/typing.pyi | 10 - .../demo/_bindings/values.pyi | 36 -- .../demo/core.pyi | 37 -- .../demo/pure_python/__init__.pyi | 11 - .../demo/pure_python/classes.pyi | 21 - .../demo/pure_python/functions.pyi | 31 -- .../demo/pure_python/functions_3_8_plus.pyi | 30 -- .../demo/pure_python/functions_3_9_plus.pyi | 5 - .../demo/pure_python/values.pyi | 4 - .../demo/__init__.pyi | 43 -- .../demo/_bindings/__init__.pyi | 37 -- .../demo/_bindings/aliases/__init__.pyi | 52 -- .../demo/_bindings/aliases/foreign_arg.pyi | 7 - .../demo/_bindings/aliases/foreign_attr.pyi | 6 - .../aliases/foreign_class_member.pyi | 12 - .../_bindings/aliases/foreign_method_arg.pyi | 8 - .../aliases/foreign_method_return.pyi | 9 - .../demo/_bindings/aliases/foreign_return.pyi | 7 - .../_bindings/aliases/missing_self_arg.pyi | 8 - .../demo/_bindings/classes.pyi | 80 --- .../demo/_bindings/eigen.pyi | 101 ---- .../demo/_bindings/enum.pyi | 71 --- .../demo/_bindings/flawed_bindings.pyi | 23 - .../demo/_bindings/hidden_builtins.pyi | 7 - .../demo/_bindings/issues.pyi | 43 -- .../demo/_bindings/methods.pyi | 8 - .../demo/_bindings/numpy.pyi | 21 - .../demo/_bindings/properties.pyi | 117 ----- .../demo/_bindings/stl.pyi | 21 - .../demo/_bindings/stl_bind.pyi | 124 ----- .../demo/_bindings/typing.pyi | 10 - .../demo/_bindings/values.pyi | 36 -- .../demo/core.pyi | 37 -- .../demo/pure_python/__init__.pyi | 11 - .../demo/pure_python/classes.pyi | 21 - .../demo/pure_python/functions.pyi | 31 -- .../demo/pure_python/functions_3_8_plus.pyi | 30 -- .../demo/pure_python/functions_3_9_plus.pyi | 5 - .../demo/pure_python/values.pyi | 4 - .../demo/__init__.pyi | 43 -- .../demo/_bindings/__init__.pyi | 37 -- .../demo/_bindings/aliases/__init__.pyi | 52 -- .../demo/_bindings/aliases/foreign_arg.pyi | 7 - .../demo/_bindings/aliases/foreign_attr.pyi | 6 - .../aliases/foreign_class_member.pyi | 12 - .../_bindings/aliases/foreign_method_arg.pyi | 8 - .../aliases/foreign_method_return.pyi | 9 - .../demo/_bindings/aliases/foreign_return.pyi | 7 - .../_bindings/aliases/missing_self_arg.pyi | 8 - .../demo/_bindings/classes.pyi | 80 --- .../demo/_bindings/enum.pyi | 71 --- .../demo/_bindings/flawed_bindings.pyi | 23 - .../demo/_bindings/functions.pyi | 76 --- .../demo/_bindings/hidden_builtins.pyi | 7 - .../demo/_bindings/issues.pyi | 43 -- .../demo/_bindings/methods.pyi | 8 - .../demo/_bindings/properties.pyi | 117 ----- .../demo/_bindings/stl.pyi | 21 - .../demo/_bindings/stl_bind.pyi | 124 ----- .../demo/_bindings/typing.pyi | 10 - .../demo/_bindings/values.pyi | 36 -- .../numpy-array-use-type-var/demo/core.pyi | 37 -- .../demo/pure_python/__init__.pyi | 11 - .../demo/pure_python/classes.pyi | 21 - .../demo/pure_python/functions.pyi | 31 -- .../demo/pure_python/functions_3_8_plus.pyi | 30 -- .../demo/pure_python/functions_3_9_plus.pyi | 5 - .../demo/pure_python/values.pyi | 4 - .../demo/__init__.pyi | 43 -- .../demo/_bindings/__init__.pyi | 37 -- .../demo/_bindings/aliases/__init__.pyi | 52 -- .../demo/_bindings/aliases/foreign_arg.pyi | 7 - .../demo/_bindings/aliases/foreign_attr.pyi | 6 - .../aliases/foreign_class_member.pyi | 12 - .../_bindings/aliases/foreign_method_arg.pyi | 8 - .../aliases/foreign_method_return.pyi | 9 - .../demo/_bindings/aliases/foreign_return.pyi | 7 - .../_bindings/aliases/missing_self_arg.pyi | 8 - .../demo/_bindings/classes.pyi | 80 --- .../demo/_bindings/eigen.pyi | 101 ---- .../demo/_bindings/enum.pyi | 71 --- .../demo/_bindings/flawed_bindings.pyi | 23 - .../demo/_bindings/functions.pyi | 76 --- .../demo/_bindings/hidden_builtins.pyi | 7 - .../demo/_bindings/issues.pyi | 43 -- .../demo/_bindings/methods.pyi | 8 - .../demo/_bindings/numpy.pyi | 21 - .../demo/_bindings/properties.pyi | 117 ----- .../demo/_bindings/stl.pyi | 21 - .../demo/_bindings/stl_bind.pyi | 124 ----- .../demo/_bindings/typing.pyi | 10 - .../demo/_bindings/values.pyi | 36 -- .../demo/core.pyi | 37 -- .../demo/pure_python/__init__.pyi | 11 - .../demo/pure_python/classes.pyi | 21 - .../demo/pure_python/functions.pyi | 31 -- .../demo/pure_python/functions_3_8_plus.pyi | 30 -- .../demo/pure_python/functions_3_9_plus.pyi | 5 - .../demo/pure_python/values.pyi | 4 - .../demo/__init__.pyi | 43 -- .../demo/_bindings/__init__.pyi | 37 -- .../demo/_bindings/aliases/__init__.pyi | 52 -- .../demo/_bindings/aliases/foreign_arg.pyi | 7 - .../demo/_bindings/aliases/foreign_attr.pyi | 6 - .../aliases/foreign_class_member.pyi | 12 - .../_bindings/aliases/foreign_method_arg.pyi | 8 - .../aliases/foreign_method_return.pyi | 9 - .../demo/_bindings/aliases/foreign_return.pyi | 7 - .../_bindings/aliases/missing_self_arg.pyi | 8 - .../demo/_bindings/classes.pyi | 80 --- .../demo/_bindings/eigen.pyi | 101 ---- .../demo/_bindings/enum.pyi | 71 --- .../demo/_bindings/flawed_bindings.pyi | 23 - .../demo/_bindings/hidden_builtins.pyi | 7 - .../demo/_bindings/issues.pyi | 43 -- .../demo/_bindings/methods.pyi | 8 - .../demo/_bindings/numpy.pyi | 21 - .../demo/_bindings/properties.pyi | 99 ---- .../demo/_bindings/typing.pyi | 10 - .../demo/_bindings/values.pyi | 36 -- .../demo/core.pyi | 37 -- .../demo/pure_python/__init__.pyi | 11 - .../demo/pure_python/classes.pyi | 21 - .../demo/pure_python/functions.pyi | 31 -- .../demo/pure_python/functions_3_8_plus.pyi | 30 -- .../demo/pure_python/functions_3_9_plus.pyi | 5 - .../demo/pure_python/values.pyi | 4 - .../demo/__init__.pyi | 43 -- .../demo/_bindings/__init__.pyi | 37 -- .../demo/_bindings/aliases/__init__.pyi | 52 -- .../demo/_bindings/aliases/foreign_arg.pyi | 7 - .../demo/_bindings/aliases/foreign_attr.pyi | 6 - .../aliases/foreign_class_member.pyi | 12 - .../_bindings/aliases/foreign_method_arg.pyi | 8 - .../aliases/foreign_method_return.pyi | 9 - .../demo/_bindings/aliases/foreign_return.pyi | 7 - .../_bindings/aliases/missing_self_arg.pyi | 8 - .../demo/_bindings/classes.pyi | 94 ---- .../demo/_bindings/flawed_bindings.pyi | 27 - .../demo/_bindings/hidden_builtins.pyi | 7 - .../demo/_bindings/issues.pyi | 46 -- .../demo/_bindings/methods.pyi | 12 - .../demo/_bindings/numpy.pyi | 24 - .../demo/_bindings/stl.pyi | 32 -- .../demo/_bindings/stl_bind.pyi | 145 ------ .../demo/_bindings/values.pyi | 36 -- .../numpy-array-use-type-var/demo/core.pyi | 37 -- .../demo/pure_python/__init__.pyi | 11 - .../demo/pure_python/classes.pyi | 21 - .../demo/pure_python/functions.pyi | 31 -- .../demo/pure_python/functions_3_8_plus.pyi | 30 -- .../demo/pure_python/functions_3_9_plus.pyi | 5 - .../demo/pure_python/values.pyi | 4 - .../demo/__init__.pyi | 43 -- .../demo/_bindings/__init__.pyi | 37 -- .../demo/_bindings/aliases/__init__.pyi | 52 -- .../demo/_bindings/aliases/foreign_arg.pyi | 7 - .../demo/_bindings/aliases/foreign_attr.pyi | 6 - .../aliases/foreign_class_member.pyi | 12 - .../_bindings/aliases/foreign_method_arg.pyi | 8 - .../aliases/foreign_method_return.pyi | 9 - .../demo/_bindings/aliases/foreign_return.pyi | 7 - .../_bindings/aliases/missing_self_arg.pyi | 8 - .../demo/_bindings/classes.pyi | 94 ---- .../demo/_bindings/eigen.pyi | 75 --- .../demo/_bindings/enum.pyi | 85 ---- .../demo/_bindings/flawed_bindings.pyi | 27 - .../demo/_bindings/functions.pyi | 117 ----- .../demo/_bindings/hidden_builtins.pyi | 7 - .../demo/_bindings/issues.pyi | 46 -- .../demo/_bindings/methods.pyi | 12 - .../demo/_bindings/numpy.pyi | 24 - .../demo/_bindings/properties.pyi | 127 ----- .../demo/_bindings/stl.pyi | 32 -- .../demo/_bindings/stl_bind.pyi | 145 ------ .../demo/_bindings/typing.pyi | 8 - .../demo/_bindings/values.pyi | 36 -- .../demo/core.pyi | 37 -- .../demo/pure_python/__init__.pyi | 11 - .../demo/pure_python/classes.pyi | 21 - .../demo/pure_python/functions.pyi | 31 -- .../demo/pure_python/functions_3_8_plus.pyi | 30 -- .../demo/pure_python/functions_3_9_plus.pyi | 5 - .../demo/pure_python/values.pyi | 4 - tests/stubs/python-3.13 | 1 - tests/test_demo_errors.py | 20 +- tests/test_demo_stubs.py | 15 +- tests/test_snapshot_helpers.py | 91 +++- tests/unit/test_snapshot_catalog.py | 43 ++ tests/unit/test_snapshot_native_glue.py | 143 ++++++ 322 files changed, 751 insertions(+), 7941 deletions(-) delete mode 120000 tests/errors/pybind11-v2.11 delete mode 120000 tests/errors/pybind11-v2.12 delete mode 120000 tests/errors/pybind11-v2.13 create mode 100644 tests/snapshot_cases.toml rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/__init__.pyi => demo/__init__/shared.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi => demo/_bindings/__init__/shared.pyi} (100%) rename tests/stubs/{python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi => demo/_bindings/aliases/__init__/py310-pb213-annotated.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi => demo/_bindings/aliases/__init__/py310-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi => demo/_bindings/aliases/foreign_arg/shared.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi => demo/_bindings/aliases/foreign_attr/shared.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi => demo/_bindings/aliases/foreign_class_member/shared.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi => demo/_bindings/aliases/foreign_method_arg/shared.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi => demo/_bindings/aliases/foreign_method_return/shared.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi => demo/_bindings/aliases/foreign_return/shared.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi => demo/_bindings/aliases/missing_self_arg/shared.pyi} (100%) rename tests/stubs/{python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi => demo/_bindings/classes/py310-pb213-annotated.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi => demo/_bindings/classes/py310-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi => demo/_bindings/eigen/py310-pb213-annotated.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi => demo/_bindings/eigen/py310-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/eigen.pyi => demo/_bindings/eigen/py313-pb213-type-var.pyi} (100%) rename tests/stubs/{python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/eigen.pyi => demo/_bindings/eigen/py313-pb30-type-var.pyi} (100%) rename tests/stubs/{python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi => demo/_bindings/enum/py310-pb213-annotated.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi => demo/_bindings/enum/py310-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi => demo/_bindings/enum/py311-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/enum.pyi => demo/_bindings/enum/py312-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi => demo/_bindings/flawed_bindings/py310-pb213-annotated.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi => demo/_bindings/flawed_bindings/py310-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi => demo/_bindings/functions/py310-pb213-annotated.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi => demo/_bindings/functions/py310-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi => demo/_bindings/functions/py312-pb213-annotated.pyi} (100%) rename tests/stubs/{python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/functions.pyi => demo/_bindings/functions/py312-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi => demo/_bindings/functions/py313-pb211-annotated.pyi} (100%) rename tests/stubs/{python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi => demo/_bindings/functions/py313-pb29-annotated.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi => demo/_bindings/hidden_builtins/shared.pyi} (100%) rename tests/stubs/{python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi => demo/_bindings/issues/py310-pb213-annotated.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi => demo/_bindings/issues/py310-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi => demo/_bindings/methods/py310-pb213-annotated.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi => demo/_bindings/methods/py310-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi => demo/_bindings/numpy/py310-pb213-annotated.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi => demo/_bindings/numpy/py310-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/numpy.pyi => demo/_bindings/numpy/py313-pb213-type-var.pyi} (100%) rename tests/stubs/{python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi => demo/_bindings/properties/py310-pb213-annotated.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi => demo/_bindings/properties/py310-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi => demo/_bindings/properties/py312-pb213-annotated.pyi} (100%) rename tests/stubs/{python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/properties.pyi => demo/_bindings/properties/py312-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi => demo/_bindings/stl/py310-pb213-annotated.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi => demo/_bindings/stl/py310-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi => demo/_bindings/stl/py313-pb29-annotated.pyi} (100%) rename tests/stubs/{python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi => demo/_bindings/stl_bind/py310-pb213-annotated.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi => demo/_bindings/stl_bind/py310-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi => demo/_bindings/stl_bind/py313-pb211-annotated.pyi} (100%) rename tests/stubs/{python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi => demo/_bindings/stl_bind/py313-pb29-annotated.pyi} (100%) rename tests/stubs/{python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi => demo/_bindings/typing/py310-pb213-annotated.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi => demo/_bindings/typing/py310-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/typing.pyi => demo/_bindings/typing/py312-pb30-annotated.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi => demo/_bindings/values/shared.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/core.pyi => demo/core/shared.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi => demo/pure_python/__init__/shared.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi => demo/pure_python/classes/shared.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi => demo/pure_python/functions/shared.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi => demo/pure_python/functions_3_8_plus/shared.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi => demo/pure_python/functions_3_9_plus/shared.pyi} (100%) rename tests/stubs/{python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi => demo/pure_python/values/shared.pyi} (100%) delete mode 120000 tests/stubs/python-3.10/pybind11-v2.13 delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/core.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/core.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi delete mode 100644 tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/core.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/core.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_attr.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_class_member.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_return.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_return.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/missing_self_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/classes.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/enum.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/flawed_bindings.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/functions.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/hidden_builtins.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/issues.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/methods.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/properties.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/stl.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/stl_bind.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/typing.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/values.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/core.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/classes.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions_3_8_plus.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions_3_9_plus.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/values.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/core.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/core.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_attr.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_class_member.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_return.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_return.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/missing_self_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/classes.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/flawed_bindings.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/hidden_builtins.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/issues.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/methods.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/numpy.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/stl.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/stl_bind.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/values.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/core.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/classes.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions_3_8_plus.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions_3_9_plus.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/values.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/core.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi delete mode 100644 tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi delete mode 120000 tests/stubs/python-3.13 create mode 100644 tests/unit/test_snapshot_native_glue.py diff --git a/tests/errors/pybind11-v2.11 b/tests/errors/pybind11-v2.11 deleted file mode 120000 index 2369df54..00000000 --- a/tests/errors/pybind11-v2.11 +++ /dev/null @@ -1 +0,0 @@ -pybind11-v2.9 \ No newline at end of file diff --git a/tests/errors/pybind11-v2.12 b/tests/errors/pybind11-v2.12 deleted file mode 120000 index e6ae88f7..00000000 --- a/tests/errors/pybind11-v2.12 +++ /dev/null @@ -1 +0,0 @@ -pybind11-v2.11 \ No newline at end of file diff --git a/tests/errors/pybind11-v2.13 b/tests/errors/pybind11-v2.13 deleted file mode 120000 index 6480bdb7..00000000 --- a/tests/errors/pybind11-v2.13 +++ /dev/null @@ -1 +0,0 @@ -pybind11-v2.12 \ No newline at end of file diff --git a/tests/snapshot_cases.toml b/tests/snapshot_cases.toml new file mode 100644 index 00000000..2ef04a89 --- /dev/null +++ b/tests/snapshot_cases.toml @@ -0,0 +1,469 @@ +format = 1 + +[cases."python-3.10-pybind11-v2.13-numpy-array-wrap-with-annotated".stubs] +"demo/__init__.pyi" = "demo/__init__/shared.pyi" +"demo/_bindings/__init__.pyi" = "demo/_bindings/__init__/shared.pyi" +"demo/_bindings/aliases/__init__.pyi" = "demo/_bindings/aliases/__init__/py310-pb213-annotated.pyi" +"demo/_bindings/aliases/foreign_arg.pyi" = "demo/_bindings/aliases/foreign_arg/shared.pyi" +"demo/_bindings/aliases/foreign_attr.pyi" = "demo/_bindings/aliases/foreign_attr/shared.pyi" +"demo/_bindings/aliases/foreign_class_member.pyi" = "demo/_bindings/aliases/foreign_class_member/shared.pyi" +"demo/_bindings/aliases/foreign_method_arg.pyi" = "demo/_bindings/aliases/foreign_method_arg/shared.pyi" +"demo/_bindings/aliases/foreign_method_return.pyi" = "demo/_bindings/aliases/foreign_method_return/shared.pyi" +"demo/_bindings/aliases/foreign_return.pyi" = "demo/_bindings/aliases/foreign_return/shared.pyi" +"demo/_bindings/aliases/missing_self_arg.pyi" = "demo/_bindings/aliases/missing_self_arg/shared.pyi" +"demo/_bindings/classes.pyi" = "demo/_bindings/classes/py310-pb213-annotated.pyi" +"demo/_bindings/eigen.pyi" = "demo/_bindings/eigen/py310-pb213-annotated.pyi" +"demo/_bindings/enum.pyi" = "demo/_bindings/enum/py310-pb213-annotated.pyi" +"demo/_bindings/flawed_bindings.pyi" = "demo/_bindings/flawed_bindings/py310-pb213-annotated.pyi" +"demo/_bindings/functions.pyi" = "demo/_bindings/functions/py310-pb213-annotated.pyi" +"demo/_bindings/hidden_builtins.pyi" = "demo/_bindings/hidden_builtins/shared.pyi" +"demo/_bindings/issues.pyi" = "demo/_bindings/issues/py310-pb213-annotated.pyi" +"demo/_bindings/methods.pyi" = "demo/_bindings/methods/py310-pb213-annotated.pyi" +"demo/_bindings/numpy.pyi" = "demo/_bindings/numpy/py310-pb213-annotated.pyi" +"demo/_bindings/properties.pyi" = "demo/_bindings/properties/py310-pb213-annotated.pyi" +"demo/_bindings/stl.pyi" = "demo/_bindings/stl/py310-pb213-annotated.pyi" +"demo/_bindings/stl_bind.pyi" = "demo/_bindings/stl_bind/py310-pb213-annotated.pyi" +"demo/_bindings/typing.pyi" = "demo/_bindings/typing/py310-pb213-annotated.pyi" +"demo/_bindings/values.pyi" = "demo/_bindings/values/shared.pyi" +"demo/core.pyi" = "demo/core/shared.pyi" +"demo/pure_python/__init__.pyi" = "demo/pure_python/__init__/shared.pyi" +"demo/pure_python/classes.pyi" = "demo/pure_python/classes/shared.pyi" +"demo/pure_python/functions.pyi" = "demo/pure_python/functions/shared.pyi" +"demo/pure_python/functions_3_8_plus.pyi" = "demo/pure_python/functions_3_8_plus/shared.pyi" +"demo/pure_python/functions_3_9_plus.pyi" = "demo/pure_python/functions_3_9_plus/shared.pyi" +"demo/pure_python/values.pyi" = "demo/pure_python/values/shared.pyi" + +[cases."python-3.10-pybind11-v2.13-numpy-array-wrap-with-annotated".errors] +"demo.errors.stderr.txt" = "pybind11-v2.9/demo.errors.stderr.txt" + +[cases."python-3.10-pybind11-v3.0-numpy-array-wrap-with-annotated".stubs] +"demo/__init__.pyi" = "demo/__init__/shared.pyi" +"demo/_bindings/__init__.pyi" = "demo/_bindings/__init__/shared.pyi" +"demo/_bindings/aliases/__init__.pyi" = "demo/_bindings/aliases/__init__/py310-pb30-annotated.pyi" +"demo/_bindings/aliases/foreign_arg.pyi" = "demo/_bindings/aliases/foreign_arg/shared.pyi" +"demo/_bindings/aliases/foreign_attr.pyi" = "demo/_bindings/aliases/foreign_attr/shared.pyi" +"demo/_bindings/aliases/foreign_class_member.pyi" = "demo/_bindings/aliases/foreign_class_member/shared.pyi" +"demo/_bindings/aliases/foreign_method_arg.pyi" = "demo/_bindings/aliases/foreign_method_arg/shared.pyi" +"demo/_bindings/aliases/foreign_method_return.pyi" = "demo/_bindings/aliases/foreign_method_return/shared.pyi" +"demo/_bindings/aliases/foreign_return.pyi" = "demo/_bindings/aliases/foreign_return/shared.pyi" +"demo/_bindings/aliases/missing_self_arg.pyi" = "demo/_bindings/aliases/missing_self_arg/shared.pyi" +"demo/_bindings/classes.pyi" = "demo/_bindings/classes/py310-pb30-annotated.pyi" +"demo/_bindings/eigen.pyi" = "demo/_bindings/eigen/py310-pb30-annotated.pyi" +"demo/_bindings/enum.pyi" = "demo/_bindings/enum/py310-pb30-annotated.pyi" +"demo/_bindings/flawed_bindings.pyi" = "demo/_bindings/flawed_bindings/py310-pb30-annotated.pyi" +"demo/_bindings/functions.pyi" = "demo/_bindings/functions/py310-pb30-annotated.pyi" +"demo/_bindings/hidden_builtins.pyi" = "demo/_bindings/hidden_builtins/shared.pyi" +"demo/_bindings/issues.pyi" = "demo/_bindings/issues/py310-pb30-annotated.pyi" +"demo/_bindings/methods.pyi" = "demo/_bindings/methods/py310-pb30-annotated.pyi" +"demo/_bindings/numpy.pyi" = "demo/_bindings/numpy/py310-pb30-annotated.pyi" +"demo/_bindings/properties.pyi" = "demo/_bindings/properties/py310-pb30-annotated.pyi" +"demo/_bindings/stl.pyi" = "demo/_bindings/stl/py310-pb30-annotated.pyi" +"demo/_bindings/stl_bind.pyi" = "demo/_bindings/stl_bind/py310-pb30-annotated.pyi" +"demo/_bindings/typing.pyi" = "demo/_bindings/typing/py310-pb30-annotated.pyi" +"demo/_bindings/values.pyi" = "demo/_bindings/values/shared.pyi" +"demo/core.pyi" = "demo/core/shared.pyi" +"demo/pure_python/__init__.pyi" = "demo/pure_python/__init__/shared.pyi" +"demo/pure_python/classes.pyi" = "demo/pure_python/classes/shared.pyi" +"demo/pure_python/functions.pyi" = "demo/pure_python/functions/shared.pyi" +"demo/pure_python/functions_3_8_plus.pyi" = "demo/pure_python/functions_3_8_plus/shared.pyi" +"demo/pure_python/functions_3_9_plus.pyi" = "demo/pure_python/functions_3_9_plus/shared.pyi" +"demo/pure_python/values.pyi" = "demo/pure_python/values/shared.pyi" + +[cases."python-3.10-pybind11-v3.0-numpy-array-wrap-with-annotated".errors] +"demo.errors.stderr.txt" = "pybind11-v3.0/demo.errors.stderr.txt" + +[cases."python-3.11-pybind11-v2.13-numpy-array-wrap-with-annotated".stubs] +"demo/__init__.pyi" = "demo/__init__/shared.pyi" +"demo/_bindings/__init__.pyi" = "demo/_bindings/__init__/shared.pyi" +"demo/_bindings/aliases/__init__.pyi" = "demo/_bindings/aliases/__init__/py310-pb213-annotated.pyi" +"demo/_bindings/aliases/foreign_arg.pyi" = "demo/_bindings/aliases/foreign_arg/shared.pyi" +"demo/_bindings/aliases/foreign_attr.pyi" = "demo/_bindings/aliases/foreign_attr/shared.pyi" +"demo/_bindings/aliases/foreign_class_member.pyi" = "demo/_bindings/aliases/foreign_class_member/shared.pyi" +"demo/_bindings/aliases/foreign_method_arg.pyi" = "demo/_bindings/aliases/foreign_method_arg/shared.pyi" +"demo/_bindings/aliases/foreign_method_return.pyi" = "demo/_bindings/aliases/foreign_method_return/shared.pyi" +"demo/_bindings/aliases/foreign_return.pyi" = "demo/_bindings/aliases/foreign_return/shared.pyi" +"demo/_bindings/aliases/missing_self_arg.pyi" = "demo/_bindings/aliases/missing_self_arg/shared.pyi" +"demo/_bindings/classes.pyi" = "demo/_bindings/classes/py310-pb213-annotated.pyi" +"demo/_bindings/eigen.pyi" = "demo/_bindings/eigen/py310-pb213-annotated.pyi" +"demo/_bindings/enum.pyi" = "demo/_bindings/enum/py310-pb213-annotated.pyi" +"demo/_bindings/flawed_bindings.pyi" = "demo/_bindings/flawed_bindings/py310-pb213-annotated.pyi" +"demo/_bindings/functions.pyi" = "demo/_bindings/functions/py310-pb213-annotated.pyi" +"demo/_bindings/hidden_builtins.pyi" = "demo/_bindings/hidden_builtins/shared.pyi" +"demo/_bindings/issues.pyi" = "demo/_bindings/issues/py310-pb213-annotated.pyi" +"demo/_bindings/methods.pyi" = "demo/_bindings/methods/py310-pb213-annotated.pyi" +"demo/_bindings/numpy.pyi" = "demo/_bindings/numpy/py310-pb213-annotated.pyi" +"demo/_bindings/properties.pyi" = "demo/_bindings/properties/py310-pb213-annotated.pyi" +"demo/_bindings/stl.pyi" = "demo/_bindings/stl/py310-pb213-annotated.pyi" +"demo/_bindings/stl_bind.pyi" = "demo/_bindings/stl_bind/py310-pb213-annotated.pyi" +"demo/_bindings/typing.pyi" = "demo/_bindings/typing/py310-pb213-annotated.pyi" +"demo/_bindings/values.pyi" = "demo/_bindings/values/shared.pyi" +"demo/core.pyi" = "demo/core/shared.pyi" +"demo/pure_python/__init__.pyi" = "demo/pure_python/__init__/shared.pyi" +"demo/pure_python/classes.pyi" = "demo/pure_python/classes/shared.pyi" +"demo/pure_python/functions.pyi" = "demo/pure_python/functions/shared.pyi" +"demo/pure_python/functions_3_8_plus.pyi" = "demo/pure_python/functions_3_8_plus/shared.pyi" +"demo/pure_python/functions_3_9_plus.pyi" = "demo/pure_python/functions_3_9_plus/shared.pyi" +"demo/pure_python/values.pyi" = "demo/pure_python/values/shared.pyi" + +[cases."python-3.11-pybind11-v2.13-numpy-array-wrap-with-annotated".errors] +"demo.errors.stderr.txt" = "pybind11-v2.9/demo.errors.stderr.txt" + +[cases."python-3.11-pybind11-v3.0-numpy-array-wrap-with-annotated".stubs] +"demo/__init__.pyi" = "demo/__init__/shared.pyi" +"demo/_bindings/__init__.pyi" = "demo/_bindings/__init__/shared.pyi" +"demo/_bindings/aliases/__init__.pyi" = "demo/_bindings/aliases/__init__/py310-pb30-annotated.pyi" +"demo/_bindings/aliases/foreign_arg.pyi" = "demo/_bindings/aliases/foreign_arg/shared.pyi" +"demo/_bindings/aliases/foreign_attr.pyi" = "demo/_bindings/aliases/foreign_attr/shared.pyi" +"demo/_bindings/aliases/foreign_class_member.pyi" = "demo/_bindings/aliases/foreign_class_member/shared.pyi" +"demo/_bindings/aliases/foreign_method_arg.pyi" = "demo/_bindings/aliases/foreign_method_arg/shared.pyi" +"demo/_bindings/aliases/foreign_method_return.pyi" = "demo/_bindings/aliases/foreign_method_return/shared.pyi" +"demo/_bindings/aliases/foreign_return.pyi" = "demo/_bindings/aliases/foreign_return/shared.pyi" +"demo/_bindings/aliases/missing_self_arg.pyi" = "demo/_bindings/aliases/missing_self_arg/shared.pyi" +"demo/_bindings/classes.pyi" = "demo/_bindings/classes/py310-pb30-annotated.pyi" +"demo/_bindings/eigen.pyi" = "demo/_bindings/eigen/py310-pb30-annotated.pyi" +"demo/_bindings/enum.pyi" = "demo/_bindings/enum/py311-pb30-annotated.pyi" +"demo/_bindings/flawed_bindings.pyi" = "demo/_bindings/flawed_bindings/py310-pb30-annotated.pyi" +"demo/_bindings/functions.pyi" = "demo/_bindings/functions/py310-pb30-annotated.pyi" +"demo/_bindings/hidden_builtins.pyi" = "demo/_bindings/hidden_builtins/shared.pyi" +"demo/_bindings/issues.pyi" = "demo/_bindings/issues/py310-pb30-annotated.pyi" +"demo/_bindings/methods.pyi" = "demo/_bindings/methods/py310-pb30-annotated.pyi" +"demo/_bindings/numpy.pyi" = "demo/_bindings/numpy/py310-pb30-annotated.pyi" +"demo/_bindings/properties.pyi" = "demo/_bindings/properties/py310-pb30-annotated.pyi" +"demo/_bindings/stl.pyi" = "demo/_bindings/stl/py310-pb30-annotated.pyi" +"demo/_bindings/stl_bind.pyi" = "demo/_bindings/stl_bind/py310-pb30-annotated.pyi" +"demo/_bindings/typing.pyi" = "demo/_bindings/typing/py310-pb30-annotated.pyi" +"demo/_bindings/values.pyi" = "demo/_bindings/values/shared.pyi" +"demo/core.pyi" = "demo/core/shared.pyi" +"demo/pure_python/__init__.pyi" = "demo/pure_python/__init__/shared.pyi" +"demo/pure_python/classes.pyi" = "demo/pure_python/classes/shared.pyi" +"demo/pure_python/functions.pyi" = "demo/pure_python/functions/shared.pyi" +"demo/pure_python/functions_3_8_plus.pyi" = "demo/pure_python/functions_3_8_plus/shared.pyi" +"demo/pure_python/functions_3_9_plus.pyi" = "demo/pure_python/functions_3_9_plus/shared.pyi" +"demo/pure_python/values.pyi" = "demo/pure_python/values/shared.pyi" + +[cases."python-3.11-pybind11-v3.0-numpy-array-wrap-with-annotated".errors] +"demo.errors.stderr.txt" = "pybind11-v3.0/demo.errors.stderr.txt" + +[cases."python-3.12-pybind11-v2.13-numpy-array-wrap-with-annotated".stubs] +"demo/__init__.pyi" = "demo/__init__/shared.pyi" +"demo/_bindings/__init__.pyi" = "demo/_bindings/__init__/shared.pyi" +"demo/_bindings/aliases/__init__.pyi" = "demo/_bindings/aliases/__init__/py310-pb213-annotated.pyi" +"demo/_bindings/aliases/foreign_arg.pyi" = "demo/_bindings/aliases/foreign_arg/shared.pyi" +"demo/_bindings/aliases/foreign_attr.pyi" = "demo/_bindings/aliases/foreign_attr/shared.pyi" +"demo/_bindings/aliases/foreign_class_member.pyi" = "demo/_bindings/aliases/foreign_class_member/shared.pyi" +"demo/_bindings/aliases/foreign_method_arg.pyi" = "demo/_bindings/aliases/foreign_method_arg/shared.pyi" +"demo/_bindings/aliases/foreign_method_return.pyi" = "demo/_bindings/aliases/foreign_method_return/shared.pyi" +"demo/_bindings/aliases/foreign_return.pyi" = "demo/_bindings/aliases/foreign_return/shared.pyi" +"demo/_bindings/aliases/missing_self_arg.pyi" = "demo/_bindings/aliases/missing_self_arg/shared.pyi" +"demo/_bindings/classes.pyi" = "demo/_bindings/classes/py310-pb213-annotated.pyi" +"demo/_bindings/eigen.pyi" = "demo/_bindings/eigen/py310-pb213-annotated.pyi" +"demo/_bindings/enum.pyi" = "demo/_bindings/enum/py310-pb213-annotated.pyi" +"demo/_bindings/flawed_bindings.pyi" = "demo/_bindings/flawed_bindings/py310-pb213-annotated.pyi" +"demo/_bindings/functions.pyi" = "demo/_bindings/functions/py312-pb213-annotated.pyi" +"demo/_bindings/hidden_builtins.pyi" = "demo/_bindings/hidden_builtins/shared.pyi" +"demo/_bindings/issues.pyi" = "demo/_bindings/issues/py310-pb213-annotated.pyi" +"demo/_bindings/methods.pyi" = "demo/_bindings/methods/py310-pb213-annotated.pyi" +"demo/_bindings/numpy.pyi" = "demo/_bindings/numpy/py310-pb213-annotated.pyi" +"demo/_bindings/properties.pyi" = "demo/_bindings/properties/py312-pb213-annotated.pyi" +"demo/_bindings/stl.pyi" = "demo/_bindings/stl/py310-pb213-annotated.pyi" +"demo/_bindings/stl_bind.pyi" = "demo/_bindings/stl_bind/py310-pb213-annotated.pyi" +"demo/_bindings/typing.pyi" = "demo/_bindings/typing/py310-pb213-annotated.pyi" +"demo/_bindings/values.pyi" = "demo/_bindings/values/shared.pyi" +"demo/core.pyi" = "demo/core/shared.pyi" +"demo/pure_python/__init__.pyi" = "demo/pure_python/__init__/shared.pyi" +"demo/pure_python/classes.pyi" = "demo/pure_python/classes/shared.pyi" +"demo/pure_python/functions.pyi" = "demo/pure_python/functions/shared.pyi" +"demo/pure_python/functions_3_8_plus.pyi" = "demo/pure_python/functions_3_8_plus/shared.pyi" +"demo/pure_python/functions_3_9_plus.pyi" = "demo/pure_python/functions_3_9_plus/shared.pyi" +"demo/pure_python/values.pyi" = "demo/pure_python/values/shared.pyi" + +[cases."python-3.12-pybind11-v2.13-numpy-array-wrap-with-annotated".errors] +"demo.errors.stderr.txt" = "pybind11-v2.9/demo.errors.stderr.txt" + +[cases."python-3.12-pybind11-v3.0-numpy-array-wrap-with-annotated".stubs] +"demo/__init__.pyi" = "demo/__init__/shared.pyi" +"demo/_bindings/__init__.pyi" = "demo/_bindings/__init__/shared.pyi" +"demo/_bindings/aliases/__init__.pyi" = "demo/_bindings/aliases/__init__/py310-pb30-annotated.pyi" +"demo/_bindings/aliases/foreign_arg.pyi" = "demo/_bindings/aliases/foreign_arg/shared.pyi" +"demo/_bindings/aliases/foreign_attr.pyi" = "demo/_bindings/aliases/foreign_attr/shared.pyi" +"demo/_bindings/aliases/foreign_class_member.pyi" = "demo/_bindings/aliases/foreign_class_member/shared.pyi" +"demo/_bindings/aliases/foreign_method_arg.pyi" = "demo/_bindings/aliases/foreign_method_arg/shared.pyi" +"demo/_bindings/aliases/foreign_method_return.pyi" = "demo/_bindings/aliases/foreign_method_return/shared.pyi" +"demo/_bindings/aliases/foreign_return.pyi" = "demo/_bindings/aliases/foreign_return/shared.pyi" +"demo/_bindings/aliases/missing_self_arg.pyi" = "demo/_bindings/aliases/missing_self_arg/shared.pyi" +"demo/_bindings/classes.pyi" = "demo/_bindings/classes/py310-pb30-annotated.pyi" +"demo/_bindings/eigen.pyi" = "demo/_bindings/eigen/py310-pb30-annotated.pyi" +"demo/_bindings/enum.pyi" = "demo/_bindings/enum/py312-pb30-annotated.pyi" +"demo/_bindings/flawed_bindings.pyi" = "demo/_bindings/flawed_bindings/py310-pb30-annotated.pyi" +"demo/_bindings/functions.pyi" = "demo/_bindings/functions/py312-pb30-annotated.pyi" +"demo/_bindings/hidden_builtins.pyi" = "demo/_bindings/hidden_builtins/shared.pyi" +"demo/_bindings/issues.pyi" = "demo/_bindings/issues/py310-pb30-annotated.pyi" +"demo/_bindings/methods.pyi" = "demo/_bindings/methods/py310-pb30-annotated.pyi" +"demo/_bindings/numpy.pyi" = "demo/_bindings/numpy/py310-pb30-annotated.pyi" +"demo/_bindings/properties.pyi" = "demo/_bindings/properties/py312-pb30-annotated.pyi" +"demo/_bindings/stl.pyi" = "demo/_bindings/stl/py310-pb30-annotated.pyi" +"demo/_bindings/stl_bind.pyi" = "demo/_bindings/stl_bind/py310-pb30-annotated.pyi" +"demo/_bindings/typing.pyi" = "demo/_bindings/typing/py312-pb30-annotated.pyi" +"demo/_bindings/values.pyi" = "demo/_bindings/values/shared.pyi" +"demo/core.pyi" = "demo/core/shared.pyi" +"demo/pure_python/__init__.pyi" = "demo/pure_python/__init__/shared.pyi" +"demo/pure_python/classes.pyi" = "demo/pure_python/classes/shared.pyi" +"demo/pure_python/functions.pyi" = "demo/pure_python/functions/shared.pyi" +"demo/pure_python/functions_3_8_plus.pyi" = "demo/pure_python/functions_3_8_plus/shared.pyi" +"demo/pure_python/functions_3_9_plus.pyi" = "demo/pure_python/functions_3_9_plus/shared.pyi" +"demo/pure_python/values.pyi" = "demo/pure_python/values/shared.pyi" + +[cases."python-3.12-pybind11-v3.0-numpy-array-wrap-with-annotated".errors] +"demo.errors.stderr.txt" = "pybind11-v3.0/demo.errors.stderr.txt" + +[cases."python-3.13-pybind11-v2.11-numpy-array-wrap-with-annotated".stubs] +"demo/__init__.pyi" = "demo/__init__/shared.pyi" +"demo/_bindings/__init__.pyi" = "demo/_bindings/__init__/shared.pyi" +"demo/_bindings/aliases/__init__.pyi" = "demo/_bindings/aliases/__init__/py310-pb213-annotated.pyi" +"demo/_bindings/aliases/foreign_arg.pyi" = "demo/_bindings/aliases/foreign_arg/shared.pyi" +"demo/_bindings/aliases/foreign_attr.pyi" = "demo/_bindings/aliases/foreign_attr/shared.pyi" +"demo/_bindings/aliases/foreign_class_member.pyi" = "demo/_bindings/aliases/foreign_class_member/shared.pyi" +"demo/_bindings/aliases/foreign_method_arg.pyi" = "demo/_bindings/aliases/foreign_method_arg/shared.pyi" +"demo/_bindings/aliases/foreign_method_return.pyi" = "demo/_bindings/aliases/foreign_method_return/shared.pyi" +"demo/_bindings/aliases/foreign_return.pyi" = "demo/_bindings/aliases/foreign_return/shared.pyi" +"demo/_bindings/aliases/missing_self_arg.pyi" = "demo/_bindings/aliases/missing_self_arg/shared.pyi" +"demo/_bindings/classes.pyi" = "demo/_bindings/classes/py310-pb213-annotated.pyi" +"demo/_bindings/eigen.pyi" = "demo/_bindings/eigen/py310-pb213-annotated.pyi" +"demo/_bindings/enum.pyi" = "demo/_bindings/enum/py310-pb213-annotated.pyi" +"demo/_bindings/flawed_bindings.pyi" = "demo/_bindings/flawed_bindings/py310-pb213-annotated.pyi" +"demo/_bindings/functions.pyi" = "demo/_bindings/functions/py313-pb211-annotated.pyi" +"demo/_bindings/hidden_builtins.pyi" = "demo/_bindings/hidden_builtins/shared.pyi" +"demo/_bindings/issues.pyi" = "demo/_bindings/issues/py310-pb213-annotated.pyi" +"demo/_bindings/methods.pyi" = "demo/_bindings/methods/py310-pb213-annotated.pyi" +"demo/_bindings/numpy.pyi" = "demo/_bindings/numpy/py310-pb213-annotated.pyi" +"demo/_bindings/properties.pyi" = "demo/_bindings/properties/py312-pb213-annotated.pyi" +"demo/_bindings/stl.pyi" = "demo/_bindings/stl/py310-pb213-annotated.pyi" +"demo/_bindings/stl_bind.pyi" = "demo/_bindings/stl_bind/py313-pb211-annotated.pyi" +"demo/_bindings/typing.pyi" = "demo/_bindings/typing/py310-pb213-annotated.pyi" +"demo/_bindings/values.pyi" = "demo/_bindings/values/shared.pyi" +"demo/core.pyi" = "demo/core/shared.pyi" +"demo/pure_python/__init__.pyi" = "demo/pure_python/__init__/shared.pyi" +"demo/pure_python/classes.pyi" = "demo/pure_python/classes/shared.pyi" +"demo/pure_python/functions.pyi" = "demo/pure_python/functions/shared.pyi" +"demo/pure_python/functions_3_8_plus.pyi" = "demo/pure_python/functions_3_8_plus/shared.pyi" +"demo/pure_python/functions_3_9_plus.pyi" = "demo/pure_python/functions_3_9_plus/shared.pyi" +"demo/pure_python/values.pyi" = "demo/pure_python/values/shared.pyi" + +[cases."python-3.13-pybind11-v2.11-numpy-array-wrap-with-annotated".errors] +"demo.errors.stderr.txt" = "pybind11-v2.9/demo.errors.stderr.txt" + +[cases."python-3.13-pybind11-v2.12-numpy-array-wrap-with-annotated".stubs] +"demo/__init__.pyi" = "demo/__init__/shared.pyi" +"demo/_bindings/__init__.pyi" = "demo/_bindings/__init__/shared.pyi" +"demo/_bindings/aliases/__init__.pyi" = "demo/_bindings/aliases/__init__/py310-pb213-annotated.pyi" +"demo/_bindings/aliases/foreign_arg.pyi" = "demo/_bindings/aliases/foreign_arg/shared.pyi" +"demo/_bindings/aliases/foreign_attr.pyi" = "demo/_bindings/aliases/foreign_attr/shared.pyi" +"demo/_bindings/aliases/foreign_class_member.pyi" = "demo/_bindings/aliases/foreign_class_member/shared.pyi" +"demo/_bindings/aliases/foreign_method_arg.pyi" = "demo/_bindings/aliases/foreign_method_arg/shared.pyi" +"demo/_bindings/aliases/foreign_method_return.pyi" = "demo/_bindings/aliases/foreign_method_return/shared.pyi" +"demo/_bindings/aliases/foreign_return.pyi" = "demo/_bindings/aliases/foreign_return/shared.pyi" +"demo/_bindings/aliases/missing_self_arg.pyi" = "demo/_bindings/aliases/missing_self_arg/shared.pyi" +"demo/_bindings/classes.pyi" = "demo/_bindings/classes/py310-pb213-annotated.pyi" +"demo/_bindings/eigen.pyi" = "demo/_bindings/eigen/py310-pb213-annotated.pyi" +"demo/_bindings/enum.pyi" = "demo/_bindings/enum/py310-pb213-annotated.pyi" +"demo/_bindings/flawed_bindings.pyi" = "demo/_bindings/flawed_bindings/py310-pb213-annotated.pyi" +"demo/_bindings/functions.pyi" = "demo/_bindings/functions/py312-pb213-annotated.pyi" +"demo/_bindings/hidden_builtins.pyi" = "demo/_bindings/hidden_builtins/shared.pyi" +"demo/_bindings/issues.pyi" = "demo/_bindings/issues/py310-pb213-annotated.pyi" +"demo/_bindings/methods.pyi" = "demo/_bindings/methods/py310-pb213-annotated.pyi" +"demo/_bindings/numpy.pyi" = "demo/_bindings/numpy/py310-pb213-annotated.pyi" +"demo/_bindings/properties.pyi" = "demo/_bindings/properties/py312-pb213-annotated.pyi" +"demo/_bindings/stl.pyi" = "demo/_bindings/stl/py310-pb213-annotated.pyi" +"demo/_bindings/stl_bind.pyi" = "demo/_bindings/stl_bind/py310-pb213-annotated.pyi" +"demo/_bindings/typing.pyi" = "demo/_bindings/typing/py310-pb213-annotated.pyi" +"demo/_bindings/values.pyi" = "demo/_bindings/values/shared.pyi" +"demo/core.pyi" = "demo/core/shared.pyi" +"demo/pure_python/__init__.pyi" = "demo/pure_python/__init__/shared.pyi" +"demo/pure_python/classes.pyi" = "demo/pure_python/classes/shared.pyi" +"demo/pure_python/functions.pyi" = "demo/pure_python/functions/shared.pyi" +"demo/pure_python/functions_3_8_plus.pyi" = "demo/pure_python/functions_3_8_plus/shared.pyi" +"demo/pure_python/functions_3_9_plus.pyi" = "demo/pure_python/functions_3_9_plus/shared.pyi" +"demo/pure_python/values.pyi" = "demo/pure_python/values/shared.pyi" + +[cases."python-3.13-pybind11-v2.12-numpy-array-wrap-with-annotated".errors] +"demo.errors.stderr.txt" = "pybind11-v2.9/demo.errors.stderr.txt" + +[cases."python-3.13-pybind11-v2.13-numpy-array-use-type-var".stubs] +"demo/__init__.pyi" = "demo/__init__/shared.pyi" +"demo/_bindings/__init__.pyi" = "demo/_bindings/__init__/shared.pyi" +"demo/_bindings/aliases/__init__.pyi" = "demo/_bindings/aliases/__init__/py310-pb213-annotated.pyi" +"demo/_bindings/aliases/foreign_arg.pyi" = "demo/_bindings/aliases/foreign_arg/shared.pyi" +"demo/_bindings/aliases/foreign_attr.pyi" = "demo/_bindings/aliases/foreign_attr/shared.pyi" +"demo/_bindings/aliases/foreign_class_member.pyi" = "demo/_bindings/aliases/foreign_class_member/shared.pyi" +"demo/_bindings/aliases/foreign_method_arg.pyi" = "demo/_bindings/aliases/foreign_method_arg/shared.pyi" +"demo/_bindings/aliases/foreign_method_return.pyi" = "demo/_bindings/aliases/foreign_method_return/shared.pyi" +"demo/_bindings/aliases/foreign_return.pyi" = "demo/_bindings/aliases/foreign_return/shared.pyi" +"demo/_bindings/aliases/missing_self_arg.pyi" = "demo/_bindings/aliases/missing_self_arg/shared.pyi" +"demo/_bindings/classes.pyi" = "demo/_bindings/classes/py310-pb213-annotated.pyi" +"demo/_bindings/eigen.pyi" = "demo/_bindings/eigen/py313-pb213-type-var.pyi" +"demo/_bindings/enum.pyi" = "demo/_bindings/enum/py310-pb213-annotated.pyi" +"demo/_bindings/flawed_bindings.pyi" = "demo/_bindings/flawed_bindings/py310-pb213-annotated.pyi" +"demo/_bindings/functions.pyi" = "demo/_bindings/functions/py312-pb213-annotated.pyi" +"demo/_bindings/hidden_builtins.pyi" = "demo/_bindings/hidden_builtins/shared.pyi" +"demo/_bindings/issues.pyi" = "demo/_bindings/issues/py310-pb213-annotated.pyi" +"demo/_bindings/methods.pyi" = "demo/_bindings/methods/py310-pb213-annotated.pyi" +"demo/_bindings/numpy.pyi" = "demo/_bindings/numpy/py313-pb213-type-var.pyi" +"demo/_bindings/properties.pyi" = "demo/_bindings/properties/py312-pb213-annotated.pyi" +"demo/_bindings/stl.pyi" = "demo/_bindings/stl/py310-pb213-annotated.pyi" +"demo/_bindings/stl_bind.pyi" = "demo/_bindings/stl_bind/py310-pb213-annotated.pyi" +"demo/_bindings/typing.pyi" = "demo/_bindings/typing/py310-pb213-annotated.pyi" +"demo/_bindings/values.pyi" = "demo/_bindings/values/shared.pyi" +"demo/core.pyi" = "demo/core/shared.pyi" +"demo/pure_python/__init__.pyi" = "demo/pure_python/__init__/shared.pyi" +"demo/pure_python/classes.pyi" = "demo/pure_python/classes/shared.pyi" +"demo/pure_python/functions.pyi" = "demo/pure_python/functions/shared.pyi" +"demo/pure_python/functions_3_8_plus.pyi" = "demo/pure_python/functions_3_8_plus/shared.pyi" +"demo/pure_python/functions_3_9_plus.pyi" = "demo/pure_python/functions_3_9_plus/shared.pyi" +"demo/pure_python/values.pyi" = "demo/pure_python/values/shared.pyi" + +[cases."python-3.13-pybind11-v2.13-numpy-array-use-type-var".errors] +"demo.errors.stderr.txt" = "pybind11-v2.9/demo.errors.stderr.txt" + +[cases."python-3.13-pybind11-v2.13-numpy-array-wrap-with-annotated".stubs] +"demo/__init__.pyi" = "demo/__init__/shared.pyi" +"demo/_bindings/__init__.pyi" = "demo/_bindings/__init__/shared.pyi" +"demo/_bindings/aliases/__init__.pyi" = "demo/_bindings/aliases/__init__/py310-pb213-annotated.pyi" +"demo/_bindings/aliases/foreign_arg.pyi" = "demo/_bindings/aliases/foreign_arg/shared.pyi" +"demo/_bindings/aliases/foreign_attr.pyi" = "demo/_bindings/aliases/foreign_attr/shared.pyi" +"demo/_bindings/aliases/foreign_class_member.pyi" = "demo/_bindings/aliases/foreign_class_member/shared.pyi" +"demo/_bindings/aliases/foreign_method_arg.pyi" = "demo/_bindings/aliases/foreign_method_arg/shared.pyi" +"demo/_bindings/aliases/foreign_method_return.pyi" = "demo/_bindings/aliases/foreign_method_return/shared.pyi" +"demo/_bindings/aliases/foreign_return.pyi" = "demo/_bindings/aliases/foreign_return/shared.pyi" +"demo/_bindings/aliases/missing_self_arg.pyi" = "demo/_bindings/aliases/missing_self_arg/shared.pyi" +"demo/_bindings/classes.pyi" = "demo/_bindings/classes/py310-pb213-annotated.pyi" +"demo/_bindings/eigen.pyi" = "demo/_bindings/eigen/py310-pb213-annotated.pyi" +"demo/_bindings/enum.pyi" = "demo/_bindings/enum/py310-pb213-annotated.pyi" +"demo/_bindings/flawed_bindings.pyi" = "demo/_bindings/flawed_bindings/py310-pb213-annotated.pyi" +"demo/_bindings/functions.pyi" = "demo/_bindings/functions/py312-pb213-annotated.pyi" +"demo/_bindings/hidden_builtins.pyi" = "demo/_bindings/hidden_builtins/shared.pyi" +"demo/_bindings/issues.pyi" = "demo/_bindings/issues/py310-pb213-annotated.pyi" +"demo/_bindings/methods.pyi" = "demo/_bindings/methods/py310-pb213-annotated.pyi" +"demo/_bindings/numpy.pyi" = "demo/_bindings/numpy/py310-pb213-annotated.pyi" +"demo/_bindings/properties.pyi" = "demo/_bindings/properties/py312-pb213-annotated.pyi" +"demo/_bindings/stl.pyi" = "demo/_bindings/stl/py310-pb213-annotated.pyi" +"demo/_bindings/stl_bind.pyi" = "demo/_bindings/stl_bind/py310-pb213-annotated.pyi" +"demo/_bindings/typing.pyi" = "demo/_bindings/typing/py310-pb213-annotated.pyi" +"demo/_bindings/values.pyi" = "demo/_bindings/values/shared.pyi" +"demo/core.pyi" = "demo/core/shared.pyi" +"demo/pure_python/__init__.pyi" = "demo/pure_python/__init__/shared.pyi" +"demo/pure_python/classes.pyi" = "demo/pure_python/classes/shared.pyi" +"demo/pure_python/functions.pyi" = "demo/pure_python/functions/shared.pyi" +"demo/pure_python/functions_3_8_plus.pyi" = "demo/pure_python/functions_3_8_plus/shared.pyi" +"demo/pure_python/functions_3_9_plus.pyi" = "demo/pure_python/functions_3_9_plus/shared.pyi" +"demo/pure_python/values.pyi" = "demo/pure_python/values/shared.pyi" + +[cases."python-3.13-pybind11-v2.13-numpy-array-wrap-with-annotated".errors] +"demo.errors.stderr.txt" = "pybind11-v2.9/demo.errors.stderr.txt" + +[cases."python-3.13-pybind11-v2.9-numpy-array-wrap-with-annotated".stubs] +"demo/__init__.pyi" = "demo/__init__/shared.pyi" +"demo/_bindings/__init__.pyi" = "demo/_bindings/__init__/shared.pyi" +"demo/_bindings/aliases/__init__.pyi" = "demo/_bindings/aliases/__init__/py310-pb213-annotated.pyi" +"demo/_bindings/aliases/foreign_arg.pyi" = "demo/_bindings/aliases/foreign_arg/shared.pyi" +"demo/_bindings/aliases/foreign_attr.pyi" = "demo/_bindings/aliases/foreign_attr/shared.pyi" +"demo/_bindings/aliases/foreign_class_member.pyi" = "demo/_bindings/aliases/foreign_class_member/shared.pyi" +"demo/_bindings/aliases/foreign_method_arg.pyi" = "demo/_bindings/aliases/foreign_method_arg/shared.pyi" +"demo/_bindings/aliases/foreign_method_return.pyi" = "demo/_bindings/aliases/foreign_method_return/shared.pyi" +"demo/_bindings/aliases/foreign_return.pyi" = "demo/_bindings/aliases/foreign_return/shared.pyi" +"demo/_bindings/aliases/missing_self_arg.pyi" = "demo/_bindings/aliases/missing_self_arg/shared.pyi" +"demo/_bindings/classes.pyi" = "demo/_bindings/classes/py310-pb213-annotated.pyi" +"demo/_bindings/eigen.pyi" = "demo/_bindings/eigen/py310-pb213-annotated.pyi" +"demo/_bindings/enum.pyi" = "demo/_bindings/enum/py310-pb213-annotated.pyi" +"demo/_bindings/flawed_bindings.pyi" = "demo/_bindings/flawed_bindings/py310-pb213-annotated.pyi" +"demo/_bindings/functions.pyi" = "demo/_bindings/functions/py313-pb29-annotated.pyi" +"demo/_bindings/hidden_builtins.pyi" = "demo/_bindings/hidden_builtins/shared.pyi" +"demo/_bindings/issues.pyi" = "demo/_bindings/issues/py310-pb213-annotated.pyi" +"demo/_bindings/methods.pyi" = "demo/_bindings/methods/py310-pb213-annotated.pyi" +"demo/_bindings/numpy.pyi" = "demo/_bindings/numpy/py310-pb213-annotated.pyi" +"demo/_bindings/properties.pyi" = "demo/_bindings/properties/py310-pb213-annotated.pyi" +"demo/_bindings/stl.pyi" = "demo/_bindings/stl/py313-pb29-annotated.pyi" +"demo/_bindings/stl_bind.pyi" = "demo/_bindings/stl_bind/py313-pb29-annotated.pyi" +"demo/_bindings/typing.pyi" = "demo/_bindings/typing/py310-pb213-annotated.pyi" +"demo/_bindings/values.pyi" = "demo/_bindings/values/shared.pyi" +"demo/core.pyi" = "demo/core/shared.pyi" +"demo/pure_python/__init__.pyi" = "demo/pure_python/__init__/shared.pyi" +"demo/pure_python/classes.pyi" = "demo/pure_python/classes/shared.pyi" +"demo/pure_python/functions.pyi" = "demo/pure_python/functions/shared.pyi" +"demo/pure_python/functions_3_8_plus.pyi" = "demo/pure_python/functions_3_8_plus/shared.pyi" +"demo/pure_python/functions_3_9_plus.pyi" = "demo/pure_python/functions_3_9_plus/shared.pyi" +"demo/pure_python/values.pyi" = "demo/pure_python/values/shared.pyi" + +[cases."python-3.13-pybind11-v2.9-numpy-array-wrap-with-annotated".errors] +"demo.errors.stderr.txt" = "pybind11-v2.9/demo.errors.stderr.txt" + +[cases."python-3.13-pybind11-v3.0-numpy-array-use-type-var".stubs] +"demo/__init__.pyi" = "demo/__init__/shared.pyi" +"demo/_bindings/__init__.pyi" = "demo/_bindings/__init__/shared.pyi" +"demo/_bindings/aliases/__init__.pyi" = "demo/_bindings/aliases/__init__/py310-pb30-annotated.pyi" +"demo/_bindings/aliases/foreign_arg.pyi" = "demo/_bindings/aliases/foreign_arg/shared.pyi" +"demo/_bindings/aliases/foreign_attr.pyi" = "demo/_bindings/aliases/foreign_attr/shared.pyi" +"demo/_bindings/aliases/foreign_class_member.pyi" = "demo/_bindings/aliases/foreign_class_member/shared.pyi" +"demo/_bindings/aliases/foreign_method_arg.pyi" = "demo/_bindings/aliases/foreign_method_arg/shared.pyi" +"demo/_bindings/aliases/foreign_method_return.pyi" = "demo/_bindings/aliases/foreign_method_return/shared.pyi" +"demo/_bindings/aliases/foreign_return.pyi" = "demo/_bindings/aliases/foreign_return/shared.pyi" +"demo/_bindings/aliases/missing_self_arg.pyi" = "demo/_bindings/aliases/missing_self_arg/shared.pyi" +"demo/_bindings/classes.pyi" = "demo/_bindings/classes/py310-pb30-annotated.pyi" +"demo/_bindings/eigen.pyi" = "demo/_bindings/eigen/py313-pb30-type-var.pyi" +"demo/_bindings/enum.pyi" = "demo/_bindings/enum/py312-pb30-annotated.pyi" +"demo/_bindings/flawed_bindings.pyi" = "demo/_bindings/flawed_bindings/py310-pb30-annotated.pyi" +"demo/_bindings/functions.pyi" = "demo/_bindings/functions/py312-pb30-annotated.pyi" +"demo/_bindings/hidden_builtins.pyi" = "demo/_bindings/hidden_builtins/shared.pyi" +"demo/_bindings/issues.pyi" = "demo/_bindings/issues/py310-pb30-annotated.pyi" +"demo/_bindings/methods.pyi" = "demo/_bindings/methods/py310-pb30-annotated.pyi" +"demo/_bindings/numpy.pyi" = "demo/_bindings/numpy/py310-pb30-annotated.pyi" +"demo/_bindings/properties.pyi" = "demo/_bindings/properties/py312-pb30-annotated.pyi" +"demo/_bindings/stl.pyi" = "demo/_bindings/stl/py310-pb30-annotated.pyi" +"demo/_bindings/stl_bind.pyi" = "demo/_bindings/stl_bind/py310-pb30-annotated.pyi" +"demo/_bindings/typing.pyi" = "demo/_bindings/typing/py312-pb30-annotated.pyi" +"demo/_bindings/values.pyi" = "demo/_bindings/values/shared.pyi" +"demo/core.pyi" = "demo/core/shared.pyi" +"demo/pure_python/__init__.pyi" = "demo/pure_python/__init__/shared.pyi" +"demo/pure_python/classes.pyi" = "demo/pure_python/classes/shared.pyi" +"demo/pure_python/functions.pyi" = "demo/pure_python/functions/shared.pyi" +"demo/pure_python/functions_3_8_plus.pyi" = "demo/pure_python/functions_3_8_plus/shared.pyi" +"demo/pure_python/functions_3_9_plus.pyi" = "demo/pure_python/functions_3_9_plus/shared.pyi" +"demo/pure_python/values.pyi" = "demo/pure_python/values/shared.pyi" + +[cases."python-3.13-pybind11-v3.0-numpy-array-use-type-var".errors] +"demo.errors.stderr.txt" = "pybind11-v3.0/demo.errors.stderr.txt" + +[cases."python-3.13-pybind11-v3.0-numpy-array-wrap-with-annotated".stubs] +"demo/__init__.pyi" = "demo/__init__/shared.pyi" +"demo/_bindings/__init__.pyi" = "demo/_bindings/__init__/shared.pyi" +"demo/_bindings/aliases/__init__.pyi" = "demo/_bindings/aliases/__init__/py310-pb30-annotated.pyi" +"demo/_bindings/aliases/foreign_arg.pyi" = "demo/_bindings/aliases/foreign_arg/shared.pyi" +"demo/_bindings/aliases/foreign_attr.pyi" = "demo/_bindings/aliases/foreign_attr/shared.pyi" +"demo/_bindings/aliases/foreign_class_member.pyi" = "demo/_bindings/aliases/foreign_class_member/shared.pyi" +"demo/_bindings/aliases/foreign_method_arg.pyi" = "demo/_bindings/aliases/foreign_method_arg/shared.pyi" +"demo/_bindings/aliases/foreign_method_return.pyi" = "demo/_bindings/aliases/foreign_method_return/shared.pyi" +"demo/_bindings/aliases/foreign_return.pyi" = "demo/_bindings/aliases/foreign_return/shared.pyi" +"demo/_bindings/aliases/missing_self_arg.pyi" = "demo/_bindings/aliases/missing_self_arg/shared.pyi" +"demo/_bindings/classes.pyi" = "demo/_bindings/classes/py310-pb30-annotated.pyi" +"demo/_bindings/eigen.pyi" = "demo/_bindings/eigen/py310-pb30-annotated.pyi" +"demo/_bindings/enum.pyi" = "demo/_bindings/enum/py312-pb30-annotated.pyi" +"demo/_bindings/flawed_bindings.pyi" = "demo/_bindings/flawed_bindings/py310-pb30-annotated.pyi" +"demo/_bindings/functions.pyi" = "demo/_bindings/functions/py312-pb30-annotated.pyi" +"demo/_bindings/hidden_builtins.pyi" = "demo/_bindings/hidden_builtins/shared.pyi" +"demo/_bindings/issues.pyi" = "demo/_bindings/issues/py310-pb30-annotated.pyi" +"demo/_bindings/methods.pyi" = "demo/_bindings/methods/py310-pb30-annotated.pyi" +"demo/_bindings/numpy.pyi" = "demo/_bindings/numpy/py310-pb30-annotated.pyi" +"demo/_bindings/properties.pyi" = "demo/_bindings/properties/py312-pb30-annotated.pyi" +"demo/_bindings/stl.pyi" = "demo/_bindings/stl/py310-pb30-annotated.pyi" +"demo/_bindings/stl_bind.pyi" = "demo/_bindings/stl_bind/py310-pb30-annotated.pyi" +"demo/_bindings/typing.pyi" = "demo/_bindings/typing/py312-pb30-annotated.pyi" +"demo/_bindings/values.pyi" = "demo/_bindings/values/shared.pyi" +"demo/core.pyi" = "demo/core/shared.pyi" +"demo/pure_python/__init__.pyi" = "demo/pure_python/__init__/shared.pyi" +"demo/pure_python/classes.pyi" = "demo/pure_python/classes/shared.pyi" +"demo/pure_python/functions.pyi" = "demo/pure_python/functions/shared.pyi" +"demo/pure_python/functions_3_8_plus.pyi" = "demo/pure_python/functions_3_8_plus/shared.pyi" +"demo/pure_python/functions_3_9_plus.pyi" = "demo/pure_python/functions_3_9_plus/shared.pyi" +"demo/pure_python/values.pyi" = "demo/pure_python/values/shared.pyi" + +[cases."python-3.13-pybind11-v3.0-numpy-array-wrap-with-annotated".errors] +"demo.errors.stderr.txt" = "pybind11-v3.0/demo.errors.stderr.txt" diff --git a/tests/snapshot_helpers.py b/tests/snapshot_helpers.py index 31145b52..d757078e 100644 --- a/tests/snapshot_helpers.py +++ b/tests/snapshot_helpers.py @@ -311,6 +311,7 @@ def diagnostics( case_id: str, check_name: str, expected: Path, + reference_details: str = "", ) -> Iterator[None]: workspace = workspace.resolve() for root in reference_roots: @@ -334,6 +335,7 @@ def diagnostics( if artifact_parent.is_relative_to(workspace): raise HarnessError("Artifact destination is inside the working workspace") context = f"Case: {case_id}/{check_name}\nReference: {expected.resolve()}\nWorkspace: {workspace}\n" + context += reference_details try: workspace.mkdir(parents=True, exist_ok=True) _write_diagnostic(workspace / "context.txt", context) @@ -397,17 +399,8 @@ def errors_root(self) -> Path: return self.repo / "tests/errors" @property - def stub_profile(self) -> Path: - major, minor = self.python_version - return ( - Path(f"python-{major}.{minor}") - / f"pybind11-{self.branch}" - / self.numpy_format - ) - - @property - def error_profile(self) -> Path: - return Path(f"pybind11-{self.branch}") + def catalog_path(self) -> Path: + return self.repo / "tests/snapshot_cases.toml" def make_case( @@ -423,6 +416,12 @@ def make_case( "Native tests require --pybind11-branch and --numpy-format; use tox to build the matching demo" ) case = DemoCase(repo.resolve(), python_version, branch, numpy_format) - resolve_profile(case.stubs_root, case.stub_profile) - resolve_profile(case.errors_root, case.error_profile) + from snapshot_catalog import load_catalog + + try: + catalog = load_catalog(case.repo) + for kind in ("stubs", "errors"): + catalog.mapping(case.id, kind) + except HarnessError as error: + raise HarnessError(f"Case {case.id}: {error}") from error return case diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/__init__.pyi b/tests/stubs/demo/__init__/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/__init__.pyi rename to tests/stubs/demo/__init__/shared.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi b/tests/stubs/demo/_bindings/__init__/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi rename to tests/stubs/demo/_bindings/__init__/shared.pyi diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi b/tests/stubs/demo/_bindings/aliases/__init__/py310-pb213-annotated.pyi similarity index 100% rename from tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi rename to tests/stubs/demo/_bindings/aliases/__init__/py310-pb213-annotated.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi b/tests/stubs/demo/_bindings/aliases/__init__/py310-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi rename to tests/stubs/demo/_bindings/aliases/__init__/py310-pb30-annotated.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi b/tests/stubs/demo/_bindings/aliases/foreign_arg/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi rename to tests/stubs/demo/_bindings/aliases/foreign_arg/shared.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi b/tests/stubs/demo/_bindings/aliases/foreign_attr/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi rename to tests/stubs/demo/_bindings/aliases/foreign_attr/shared.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi b/tests/stubs/demo/_bindings/aliases/foreign_class_member/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi rename to tests/stubs/demo/_bindings/aliases/foreign_class_member/shared.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi b/tests/stubs/demo/_bindings/aliases/foreign_method_arg/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi rename to tests/stubs/demo/_bindings/aliases/foreign_method_arg/shared.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi b/tests/stubs/demo/_bindings/aliases/foreign_method_return/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi rename to tests/stubs/demo/_bindings/aliases/foreign_method_return/shared.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi b/tests/stubs/demo/_bindings/aliases/foreign_return/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi rename to tests/stubs/demo/_bindings/aliases/foreign_return/shared.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi b/tests/stubs/demo/_bindings/aliases/missing_self_arg/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi rename to tests/stubs/demo/_bindings/aliases/missing_self_arg/shared.pyi diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi b/tests/stubs/demo/_bindings/classes/py310-pb213-annotated.pyi similarity index 100% rename from tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi rename to tests/stubs/demo/_bindings/classes/py310-pb213-annotated.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi b/tests/stubs/demo/_bindings/classes/py310-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi rename to tests/stubs/demo/_bindings/classes/py310-pb30-annotated.pyi diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi b/tests/stubs/demo/_bindings/eigen/py310-pb213-annotated.pyi similarity index 100% rename from tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi rename to tests/stubs/demo/_bindings/eigen/py310-pb213-annotated.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi b/tests/stubs/demo/_bindings/eigen/py310-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi rename to tests/stubs/demo/_bindings/eigen/py310-pb30-annotated.pyi diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/eigen.pyi b/tests/stubs/demo/_bindings/eigen/py313-pb213-type-var.pyi similarity index 100% rename from tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/eigen.pyi rename to tests/stubs/demo/_bindings/eigen/py313-pb213-type-var.pyi diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/eigen.pyi b/tests/stubs/demo/_bindings/eigen/py313-pb30-type-var.pyi similarity index 100% rename from tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/eigen.pyi rename to tests/stubs/demo/_bindings/eigen/py313-pb30-type-var.pyi diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi b/tests/stubs/demo/_bindings/enum/py310-pb213-annotated.pyi similarity index 100% rename from tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi rename to tests/stubs/demo/_bindings/enum/py310-pb213-annotated.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi b/tests/stubs/demo/_bindings/enum/py310-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi rename to tests/stubs/demo/_bindings/enum/py310-pb30-annotated.pyi diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi b/tests/stubs/demo/_bindings/enum/py311-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi rename to tests/stubs/demo/_bindings/enum/py311-pb30-annotated.pyi diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/enum.pyi b/tests/stubs/demo/_bindings/enum/py312-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/enum.pyi rename to tests/stubs/demo/_bindings/enum/py312-pb30-annotated.pyi diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi b/tests/stubs/demo/_bindings/flawed_bindings/py310-pb213-annotated.pyi similarity index 100% rename from tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi rename to tests/stubs/demo/_bindings/flawed_bindings/py310-pb213-annotated.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi b/tests/stubs/demo/_bindings/flawed_bindings/py310-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi rename to tests/stubs/demo/_bindings/flawed_bindings/py310-pb30-annotated.pyi diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi b/tests/stubs/demo/_bindings/functions/py310-pb213-annotated.pyi similarity index 100% rename from tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi rename to tests/stubs/demo/_bindings/functions/py310-pb213-annotated.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi b/tests/stubs/demo/_bindings/functions/py310-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi rename to tests/stubs/demo/_bindings/functions/py310-pb30-annotated.pyi diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi b/tests/stubs/demo/_bindings/functions/py312-pb213-annotated.pyi similarity index 100% rename from tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi rename to tests/stubs/demo/_bindings/functions/py312-pb213-annotated.pyi diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/functions.pyi b/tests/stubs/demo/_bindings/functions/py312-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/functions.pyi rename to tests/stubs/demo/_bindings/functions/py312-pb30-annotated.pyi diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi b/tests/stubs/demo/_bindings/functions/py313-pb211-annotated.pyi similarity index 100% rename from tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi rename to tests/stubs/demo/_bindings/functions/py313-pb211-annotated.pyi diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi b/tests/stubs/demo/_bindings/functions/py313-pb29-annotated.pyi similarity index 100% rename from tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi rename to tests/stubs/demo/_bindings/functions/py313-pb29-annotated.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi b/tests/stubs/demo/_bindings/hidden_builtins/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi rename to tests/stubs/demo/_bindings/hidden_builtins/shared.pyi diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi b/tests/stubs/demo/_bindings/issues/py310-pb213-annotated.pyi similarity index 100% rename from tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi rename to tests/stubs/demo/_bindings/issues/py310-pb213-annotated.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi b/tests/stubs/demo/_bindings/issues/py310-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi rename to tests/stubs/demo/_bindings/issues/py310-pb30-annotated.pyi diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi b/tests/stubs/demo/_bindings/methods/py310-pb213-annotated.pyi similarity index 100% rename from tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi rename to tests/stubs/demo/_bindings/methods/py310-pb213-annotated.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi b/tests/stubs/demo/_bindings/methods/py310-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi rename to tests/stubs/demo/_bindings/methods/py310-pb30-annotated.pyi diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi b/tests/stubs/demo/_bindings/numpy/py310-pb213-annotated.pyi similarity index 100% rename from tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi rename to tests/stubs/demo/_bindings/numpy/py310-pb213-annotated.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi b/tests/stubs/demo/_bindings/numpy/py310-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi rename to tests/stubs/demo/_bindings/numpy/py310-pb30-annotated.pyi diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/numpy.pyi b/tests/stubs/demo/_bindings/numpy/py313-pb213-type-var.pyi similarity index 100% rename from tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/numpy.pyi rename to tests/stubs/demo/_bindings/numpy/py313-pb213-type-var.pyi diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi b/tests/stubs/demo/_bindings/properties/py310-pb213-annotated.pyi similarity index 100% rename from tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi rename to tests/stubs/demo/_bindings/properties/py310-pb213-annotated.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi b/tests/stubs/demo/_bindings/properties/py310-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi rename to tests/stubs/demo/_bindings/properties/py310-pb30-annotated.pyi diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi b/tests/stubs/demo/_bindings/properties/py312-pb213-annotated.pyi similarity index 100% rename from tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi rename to tests/stubs/demo/_bindings/properties/py312-pb213-annotated.pyi diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/properties.pyi b/tests/stubs/demo/_bindings/properties/py312-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/properties.pyi rename to tests/stubs/demo/_bindings/properties/py312-pb30-annotated.pyi diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi b/tests/stubs/demo/_bindings/stl/py310-pb213-annotated.pyi similarity index 100% rename from tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi rename to tests/stubs/demo/_bindings/stl/py310-pb213-annotated.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi b/tests/stubs/demo/_bindings/stl/py310-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi rename to tests/stubs/demo/_bindings/stl/py310-pb30-annotated.pyi diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi b/tests/stubs/demo/_bindings/stl/py313-pb29-annotated.pyi similarity index 100% rename from tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi rename to tests/stubs/demo/_bindings/stl/py313-pb29-annotated.pyi diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi b/tests/stubs/demo/_bindings/stl_bind/py310-pb213-annotated.pyi similarity index 100% rename from tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi rename to tests/stubs/demo/_bindings/stl_bind/py310-pb213-annotated.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi b/tests/stubs/demo/_bindings/stl_bind/py310-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi rename to tests/stubs/demo/_bindings/stl_bind/py310-pb30-annotated.pyi diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi b/tests/stubs/demo/_bindings/stl_bind/py313-pb211-annotated.pyi similarity index 100% rename from tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi rename to tests/stubs/demo/_bindings/stl_bind/py313-pb211-annotated.pyi diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi b/tests/stubs/demo/_bindings/stl_bind/py313-pb29-annotated.pyi similarity index 100% rename from tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi rename to tests/stubs/demo/_bindings/stl_bind/py313-pb29-annotated.pyi diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi b/tests/stubs/demo/_bindings/typing/py310-pb213-annotated.pyi similarity index 100% rename from tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi rename to tests/stubs/demo/_bindings/typing/py310-pb213-annotated.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi b/tests/stubs/demo/_bindings/typing/py310-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi rename to tests/stubs/demo/_bindings/typing/py310-pb30-annotated.pyi diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/typing.pyi b/tests/stubs/demo/_bindings/typing/py312-pb30-annotated.pyi similarity index 100% rename from tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/typing.pyi rename to tests/stubs/demo/_bindings/typing/py312-pb30-annotated.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi b/tests/stubs/demo/_bindings/values/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi rename to tests/stubs/demo/_bindings/values/shared.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/core.pyi b/tests/stubs/demo/core/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/core.pyi rename to tests/stubs/demo/core/shared.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi b/tests/stubs/demo/pure_python/__init__/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi rename to tests/stubs/demo/pure_python/__init__/shared.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi b/tests/stubs/demo/pure_python/classes/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi rename to tests/stubs/demo/pure_python/classes/shared.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi b/tests/stubs/demo/pure_python/functions/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi rename to tests/stubs/demo/pure_python/functions/shared.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi b/tests/stubs/demo/pure_python/functions_3_8_plus/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi rename to tests/stubs/demo/pure_python/functions_3_8_plus/shared.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi b/tests/stubs/demo/pure_python/functions_3_9_plus/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi rename to tests/stubs/demo/pure_python/functions_3_9_plus/shared.pyi diff --git a/tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi b/tests/stubs/demo/pure_python/values/shared.pyi similarity index 100% rename from tests/stubs/python-3.10/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi rename to tests/stubs/demo/pure_python/values/shared.pyi diff --git a/tests/stubs/python-3.10/pybind11-v2.13 b/tests/stubs/python-3.10/pybind11-v2.13 deleted file mode 120000 index 0d64df17..00000000 --- a/tests/stubs/python-3.10/pybind11-v2.13 +++ /dev/null @@ -1 +0,0 @@ -../python-3.11/pybind11-v2.13 \ No newline at end of file diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/__init__.pyi deleted file mode 100644 index 9f27c427..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/__init__.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -from . import _bindings, core, pure_python - -__all__: list[str] = [ - "aliases", - "classes", - "core", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "pure_python", - "stl", - "stl_bind", - "typing", - "values", - "version", -] -version: str = "0.0.0" diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi deleted file mode 100644 index 9ea9beb4..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from . import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi deleted file mode 100644 index de75e6fb..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["set_foo"] - -def set_foo(arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi deleted file mode 100644 index 10c4a712..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["value"] -value: demo._bindings.classes.Foo # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi deleted file mode 100644 index 497a4e2a..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.classes - -__all__: list[str] = ["Bar1"] - -class Bar1: - foo: typing.ClassVar[ - demo._bindings.classes.Foo - ] # value = diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi deleted file mode 100644 index 8d4ed4ac..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar2"] - -class Bar2: - def set_foo(self, arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi deleted file mode 100644 index 95d218e1..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar3"] - -class Bar3: - @staticmethod - def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi deleted file mode 100644 index 7b5b428f..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["get_foo"] - -def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi deleted file mode 100644 index ee1a09ee..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar4"] - -class Bar4: - def set_foo(self: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi deleted file mode 100644 index 52c09281..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import types - -__all__: list[str] = ["mapping_proxy", "none"] -mapping_proxy: types.MappingProxyType # value = mappingproxy({}) -none = None diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi deleted file mode 100644 index 7c36d3fd..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -import datetime - -import numpy -from numpy import random - -__all__: list[str] = [ - "Dummy", - "Foo", - "add_day", - "foolist", - "foovar", - "list_with_none", - "none", - "random", - "t_10ms", - "t_20ns", - "t_30s", -] - -class Dummy: - linalg = numpy.linalg - -class Foo: - pass - -def add_day(arg0: datetime.datetime) -> datetime.datetime: ... - -foolist: list # value = [, ] -foovar: Foo # value = -list_with_none: list = [None, 2, {}] -none = None -t_10ms: datetime.timedelta # value = datetime.timedelta(microseconds=10000) -t_20ns: datetime.timedelta # value = datetime.timedelta(0) -t_30s: datetime.timedelta # value = datetime.timedelta(seconds=30) diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/core.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/core.pyi deleted file mode 100644 index 95cb8455..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/core.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi deleted file mode 100644 index 10d343e0..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from . import classes, functions, functions_3_8_plus, functions_3_9_plus, values - -__all__: list[str] = [ - "classes", - "functions", - "functions_3_8_plus", - "functions_3_9_plus", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi deleted file mode 100644 index 32aa068b..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["A", "B", "C", "X"] - -class A: - """ - A - """ - -class B(A): - """ - B - """ - -class C(B): - """ - C - """ - -class X: - pass diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi deleted file mode 100644 index 5ed9ef3c..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import annotations - -import sys as sys -import typing as typing - -from demo.pure_python.functions_3_8_plus import args_mix -from demo.pure_python.functions_3_9_plus import generic_alias_annotation - -__all__: list[str] = [ - "accept_frozenset", - "args_mix", - "builtin_function_as_default_arg", - "function_as_default_arg", - "generic_alias_annotation", - "lambda_as_default_arg", - "search", - "static_method_as_default_arg", - "sys", - "typing", -] - -class _Dummy: - @staticmethod - def foo(): ... - -def accept_frozenset(arg: frozenset[int | float]) -> int | None: ... -def builtin_function_as_default_arg(func: type(len) = len): ... -def function_as_default_arg(func: type(search) = search): ... -def lambda_as_default_arg(callback=...): ... -def search(a: int, b: list[int]) -> int: ... -def static_method_as_default_arg(callback=_Dummy.foo): ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi deleted file mode 100644 index c55aed1d..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import typing as typing - -__all__: list[str] = [ - "Expression", - "Token", - "args_mix", - "nested_current_module_annotations", - "typing", -] - -class Token: - pass - -class Expression: - pass - -def args_mix( - a: int, - b: float = 0.5, - c: str = "", - *args: int, - x: int = 1, - y=int, - **kwargs: dict[int, str], -): ... -def nested_current_module_annotations( - tokens: list[Token], expr: Expression | None = None -) -> dict[str, Expression]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi deleted file mode 100644 index 59b9b80c..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["generic_alias_annotation"] - -def generic_alias_annotation(a: list[tuple[int]], b: dict[int, str]) -> list[float]: ... diff --git a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi b/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi deleted file mode 100644 index 07913391..00000000 --- a/tests/stubs/python-3.11/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["callables_dict"] -callables_dict: dict = {"len": len, "int": int} diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/__init__.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/__init__.pyi deleted file mode 100644 index 9f27c427..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/__init__.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -from . import _bindings, core, pure_python - -__all__: list[str] = [ - "aliases", - "classes", - "core", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "pure_python", - "stl", - "stl_bind", - "typing", - "values", - "version", -] -version: str = "0.0.0" diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi deleted file mode 100644 index 9ea9beb4..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from . import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi deleted file mode 100644 index c696ef48..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.enum -import numpy -from demo._bindings.aliases.foreign_method_arg import Bar2 as foreign_type_alias -from demo._bindings.aliases.foreign_return import get_foo as foreign_class_alias -from numpy import random - -from . import ( - foreign_arg, - foreign_attr, - foreign_class_member, - foreign_method_arg, - foreign_method_return, - foreign_return, - missing_self_arg, -) - -__all__: list[str] = [ - "Color", - "Dummy", - "foreign_arg", - "foreign_attr", - "foreign_class_alias", - "foreign_class_member", - "foreign_enum_default", - "foreign_method_arg", - "foreign_method_return", - "foreign_return", - "foreign_type_alias", - "func", - "local_func_alias", - "local_type_alias", - "missing_self_arg", - "random", -] - -class Dummy: - linalg = numpy.linalg - -class Color: - pass - -def foreign_enum_default( - color: typing.Any = demo._bindings.enum.ConsoleForegroundColor.Blue, -) -> None: ... -def func(arg0: typing.SupportsInt | typing.SupportsIndex) -> int: ... - -local_type_alias = Color -local_func_alias = func diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi deleted file mode 100644 index de75e6fb..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["set_foo"] - -def set_foo(arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi deleted file mode 100644 index 10c4a712..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["value"] -value: demo._bindings.classes.Foo # value = diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi deleted file mode 100644 index 497a4e2a..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.classes - -__all__: list[str] = ["Bar1"] - -class Bar1: - foo: typing.ClassVar[ - demo._bindings.classes.Foo - ] # value = diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi deleted file mode 100644 index 8d4ed4ac..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar2"] - -class Bar2: - def set_foo(self, arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi deleted file mode 100644 index 95d218e1..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar3"] - -class Bar3: - @staticmethod - def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi deleted file mode 100644 index 7b5b428f..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["get_foo"] - -def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi deleted file mode 100644 index ee1a09ee..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar4"] - -class Bar4: - def set_foo(self: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi deleted file mode 100644 index d85d9249..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi +++ /dev/null @@ -1,94 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "CppException", - "Derived", - "Foo", - "MyBase", - "Outer", - "ParIter", - "ParIterBase", - "ParticleContainer", -] - -class Outer: - class Inner: - class NestedEnum: - """ - Members: - - ONE - - TWO - """ - - ONE: typing.ClassVar[Outer.Inner.NestedEnum] # value = - TWO: typing.ClassVar[Outer.Inner.NestedEnum] # value = - __members__: typing.ClassVar[ - dict[str, Outer.Inner.NestedEnum] - ] # value = {'ONE': , 'TWO': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__( - self, value: typing.SupportsInt | typing.SupportsIndex - ) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__( - self, state: typing.SupportsInt | typing.SupportsIndex - ) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - - value: Outer.Inner.NestedEnum - - inner: Outer.Inner - -class MyBase: - class Inner: - pass - - name: str - -class Derived(MyBase): - @property - def count(self) -> int: ... - @count.setter - def count(self, arg0: typing.SupportsInt | typing.SupportsIndex) -> None: ... - -class Foo: - class FooChild: - def __init__(self) -> None: ... - def g(self) -> None: ... - - def __init__(self) -> None: ... - def f(self) -> None: ... - -class ParIterBase: - @property - def level(self) -> int: ... - @level.setter - def level(self, arg0: typing.SupportsInt | typing.SupportsIndex) -> None: ... - -class ParIter(ParIterBase): - def __init__( - self, - particle_container: ParticleContainer, - level: typing.SupportsInt | typing.SupportsIndex, - ) -> None: ... - -class ParticleContainer: - name: str - Iterator = ParIter - def process(self, arg0: ParIter) -> None: ... - -class CppException(Exception): - pass diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi deleted file mode 100644 index 1bf5cd79..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi +++ /dev/null @@ -1,75 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy -import numpy.typing -import scipy.sparse - -__all__: list[str] = [ - "accept_matrix_int", - "accept_vector_float64", - "dense_matrix_c", - "dense_matrix_r", - "fixed_mutator_a", - "fixed_mutator_c", - "fixed_mutator_r", - "four_col_matrix_r", - "four_row_matrix_r", - "get_matrix_int", - "get_vector_float64", - "sparse_matrix_c", - "sparse_matrix_r", -] - -def accept_matrix_int( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.int32, "[3, 3]"], -) -> None: ... -def accept_vector_float64( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float64, "[3, 1]"], -) -> None: ... -def dense_matrix_c( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float32, "[m, n]"], -) -> typing.Annotated[numpy.typing.NDArray[numpy.float32], "[m, n]"]: ... -def dense_matrix_r( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float32, "[m, n]"], -) -> typing.Annotated[numpy.typing.NDArray[numpy.float32], "[m, n]"]: ... -def fixed_mutator_a( - arg0: typing.Annotated[ - numpy.typing.NDArray[numpy.float32], "[5, 6]", "flags.writeable" - ], -) -> None: ... -def fixed_mutator_c( - arg0: typing.Annotated[ - numpy.typing.NDArray[numpy.float32], - "[5, 6]", - "flags.writeable", - "flags.f_contiguous", - ], -) -> None: ... -def fixed_mutator_r( - arg0: typing.Annotated[ - numpy.typing.NDArray[numpy.float32], - "[5, 6]", - "flags.writeable", - "flags.c_contiguous", - ], -) -> None: ... -def four_col_matrix_r( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float32, "[m, 4]"], -) -> typing.Annotated[numpy.typing.NDArray[numpy.float32], "[m, 4]"]: ... -def four_row_matrix_r( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float32, "[4, n]"], -) -> typing.Annotated[numpy.typing.NDArray[numpy.float32], "[4, n]"]: ... -def get_matrix_int() -> typing.Annotated[ - numpy.typing.NDArray[numpy.int32], "[3, 3]" -]: ... -def get_vector_float64() -> typing.Annotated[ - numpy.typing.NDArray[numpy.float64], "[3, 1]" -]: ... -def sparse_matrix_c( - arg0: typing.Annotated[scipy.sparse.csc_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csc_matrix, numpy.float32]: ... -def sparse_matrix_r( - arg0: typing.Annotated[scipy.sparse.csr_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csr_matrix, numpy.float32]: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi deleted file mode 100644 index f8c22db8..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Enum", - "Unbound", - "accept_unbound_enum", - "accept_unbound_enum_defaulted", - "accept_unbound_type", - "accept_unbound_type_defaulted", - "get_unbound_type", -] - -class Unbound: - pass - -class Enum: - pass - -def accept_unbound_enum(arg0: ...) -> int: ... -def accept_unbound_enum_defaulted(x: Enum = ...) -> int: ... -def accept_unbound_type( - arg0: tuple[..., typing.SupportsInt | typing.SupportsIndex], -) -> int: ... -def accept_unbound_type_defaulted(x: Unbound = ...) -> int: ... -def get_unbound_type() -> ...: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi deleted file mode 100644 index 4bdd39a6..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi +++ /dev/null @@ -1,127 +0,0 @@ -from __future__ import annotations - -import collections.abc -import typing - -__all__: list[str] = [ - "Foo", - "U", - "accept_annotated_callable", - "accept_callable", - "accept_frozenset", - "accept_py_handle", - "accept_py_object", - "accept_set", - "add", - "default_custom_arg", - "default_int_arg", - "default_list_arg", - "default_optional_arg", - "func_w_anon_args", - "func_w_named_pos_args", - "generic", - "mul", - "nested_types", - "pass_callback", - "passthrough1", - "passthrough2", - "passthrough3", - "passthrough_backwards", - "pos_kw_only_mix", - "pos_kw_only_variadic_mix", -] - -class Foo: - def __init__(self, arg0: typing.SupportsInt | typing.SupportsIndex) -> None: ... - -def accept_annotated_callable( - arg0: collections.abc.Callable[ - [ - typing.SupportsInt | typing.SupportsIndex, - typing.SupportsInt | typing.SupportsIndex, - ], - int, - ], -) -> typing.Any: ... -def accept_callable(arg0: collections.abc.Callable) -> typing.Any: ... -def accept_frozenset(arg0: frozenset) -> None: ... -def accept_py_handle(arg0: typing.Any) -> str: ... -def accept_py_object(arg0: typing.Any) -> str: ... -def accept_set(arg0: set) -> None: ... -def add( - arg0: typing.SupportsInt | typing.SupportsIndex, - arg1: typing.SupportsInt | typing.SupportsIndex, -) -> int: ... -def default_custom_arg(foo: Foo = Foo(5)) -> None: ... -def default_int_arg(n: typing.SupportsInt | typing.SupportsIndex = 5) -> None: ... -def default_list_arg(l: list = [1, 2, 6, 18]) -> None: ... -def default_optional_arg( - n: typing.SupportsInt | typing.SupportsIndex | None = None, -) -> None: ... -def func_w_anon_args( - arg0: typing.SupportsInt | typing.SupportsIndex, - arg1: typing.SupportsInt | typing.SupportsIndex, - arg2: typing.SupportsInt | typing.SupportsIndex, -) -> None: ... -def func_w_named_pos_args( - x: typing.SupportsInt | typing.SupportsIndex, - y: typing.SupportsInt | typing.SupportsIndex, - z: typing.SupportsInt | typing.SupportsIndex, -) -> None: ... -def generic(*args, **kwargs) -> None: ... -@typing.overload -def mul( - x: typing.SupportsInt | typing.SupportsIndex, - y: typing.SupportsInt | typing.SupportsIndex, -) -> int: - """ - Multiply x and y (int) - """ - -@typing.overload -def mul( - p: typing.SupportsFloat | typing.SupportsIndex, - q: typing.SupportsFloat | typing.SupportsIndex, -) -> float: - """ - Multiply p and q (double) - """ - -def nested_types(arg0: collections.abc.Sequence[Foo] | Foo) -> list[Foo] | Foo: ... -def pass_callback(arg0: collections.abc.Callable[[Foo], Foo]) -> Foo: ... -def passthrough1(*args, **kwargs): - """ - passthrough1[T](obj: T) -> T - """ - -@typing.overload -def passthrough2() -> None: - """ - 2. passthrough2[T](obj: T) -> T - """ - -@typing.overload -def passthrough3() -> tuple[None, None]: - """ - 2. passthrough3[T](obj: T) -> tuple[T, None] - 3. passthrough3[T1, T2](obj1: T1, obj2: T2) -> tuple[T1, T2] - """ - -def passthrough_backwards(obj: U) -> U: ... -def pos_kw_only_mix( - i: typing.SupportsInt | typing.SupportsIndex, - /, - j: typing.SupportsInt | typing.SupportsIndex, - *, - k: typing.SupportsInt | typing.SupportsIndex, -) -> tuple[int, int, int]: ... -def pos_kw_only_variadic_mix( - i: typing.SupportsInt | typing.SupportsIndex, - /, - j: typing.SupportsInt | typing.SupportsIndex, - *args, - k: typing.SupportsInt | typing.SupportsIndex, - **kwargs, -) -> tuple[int, int, int]: ... - -U: typing.TypeVar # value = ~U diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi deleted file mode 100644 index 52c09281..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import types - -__all__: list[str] = ["mapping_proxy", "none"] -mapping_proxy: types.MappingProxyType # value = mappingproxy({}) -none = None diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi deleted file mode 100644 index e8bd056c..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi +++ /dev/null @@ -1,46 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "backslashes_should_be_escaped", - "issue_51_catastrophic_regex", - "issue_73_utf8_doc_chars", - "issue_274_docstring_leading_newline", -] - -def backslashes_should_be_escaped() -> None: - """ - \\brief A brief description of this function. - - A detailed description of this function. - - Here's some reStructuredText: :math:`x = [x, y, \\theta]^T` - """ - -def issue_274_docstring_leading_newline() -> None: - """ - This is a docstring - """ - -def issue_51_catastrophic_regex( - arg0: typing.SupportsInt | typing.SupportsIndex, - arg1: typing.SupportsInt | typing.SupportsIndex, -) -> None: - """ - Use-case: - issue_51(os.get_handle_inheritable, os.set_handle_inheritable) - """ - -def issue_73_utf8_doc_chars() -> None: - """ - Construct a Ramsete unicycle controller. - - Tuning parameter (b > 0 rad²/m²) for which larger values make - - convergence more aggressive like a proportional term. - Tuning parameter (0 rad⁻¹ < zeta < 1 rad⁻¹) for which larger - values provide more damping in response. - """ - -_cleanup: typing.Any # value = diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi deleted file mode 100644 index 0ac0df06..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = ["Dummy"] - -class Dummy: - @staticmethod - def static_method(arg0: typing.SupportsInt | typing.SupportsIndex) -> int: ... - def regular_method( - self, arg0: typing.SupportsInt | typing.SupportsIndex - ) -> int: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi deleted file mode 100644 index ca0a64b9..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy -import numpy.typing - -__all__: list[str] = [ - "accept_ndarray_float64", - "accept_ndarray_int", - "get_ndarray_float64", - "get_ndarray_int", - "return_dtype", -] - -def accept_ndarray_float64( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float64], -) -> None: ... -def accept_ndarray_int( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.int32], -) -> None: ... -def get_ndarray_float64() -> numpy.typing.NDArray[numpy.float64]: ... -def get_ndarray_int() -> numpy.typing.NDArray[numpy.int32]: ... -def return_dtype() -> numpy.dtype[typing.Any]: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi deleted file mode 100644 index e24ef6ac..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi +++ /dev/null @@ -1,109 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "WithGetterSetterDoc", - "WithPropAndGetterSetterDoc", - "WithPropDoc", - "WithoutDoc", -] - -class WithoutDoc: - """ - No user docstring provided - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: ... - @def_property.setter - def def_property(self, arg1: typing.SupportsInt | typing.SupportsIndex) -> None: ... - @property - def def_property_readonly(self) -> int: ... - @property - def def_readonly(self) -> int: ... - @property - def def_readwrite(self) -> int: ... - @def_readwrite.setter - def def_readwrite( - self, arg0: typing.SupportsInt | typing.SupportsIndex - ) -> None: ... - -class WithPropDoc: - """ - User docstring provided only to `def_` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - prop doc token - """ - @def_property.setter - def def_property(self, arg1: typing.SupportsInt | typing.SupportsIndex) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ - @property - def def_readonly(self) -> int: - """ - prop doc token - """ - @property - def def_readwrite(self) -> int: - """ - prop doc token - """ - @def_readwrite.setter - def def_readwrite( - self, arg0: typing.SupportsInt | typing.SupportsIndex - ) -> None: ... - -class WithGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters, but NOT to `def_*(..., doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - getter doc token - """ - @def_property.setter - def def_property(self, arg1: typing.SupportsInt | typing.SupportsIndex) -> None: - """ - setter doc token - """ - @property - def def_property_readonly(self) -> int: - """ - getter doc token - """ - -class WithPropAndGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters and to `def_*(, doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - prop doc token - """ - @def_property.setter - def def_property(self, arg1: typing.SupportsInt | typing.SupportsIndex) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi deleted file mode 100644 index 41ef06d3..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import annotations - -import collections.abc -import typing - -__all__: list[str] = [ - "std_array", - "std_map", - "std_optional", - "std_variant", - "std_vector", -] - -def std_array( - arg0: typing.Annotated[ - collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex], - "FixedSize(3)", - ], -) -> typing.Annotated[list[int], "FixedSize(3)"]: ... -def std_map() -> dict[int, complex]: ... -def std_optional(arg0: typing.SupportsInt | typing.SupportsIndex | None) -> None: ... -def std_variant( - arg0: typing.SupportsInt - | typing.SupportsIndex - | typing.SupportsFloat - | typing.SupportsIndex - | tuple[ - typing.SupportsInt | typing.SupportsIndex, - typing.SupportsInt | typing.SupportsIndex, - ], -) -> None: ... -def std_vector() -> list[tuple[int, float]]: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi deleted file mode 100644 index b1ae812b..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi +++ /dev/null @@ -1,145 +0,0 @@ -from __future__ import annotations - -import collections.abc -import typing - -__all__: list[str] = [ - "MapStringComplex", - "VectorPairStringDouble", - "get_complex_map", - "get_vector_of_pairs", -] - -class VectorPairStringDouble: - __hash__: typing.ClassVar[None] = None - def __bool__(self) -> bool: - """ - Check whether the list is nonempty - """ - def __contains__( - self, x: tuple[str, typing.SupportsFloat | typing.SupportsIndex] - ) -> bool: - """ - Return true the container contains ``x`` - """ - @typing.overload - def __delitem__(self, arg0: typing.SupportsInt | typing.SupportsIndex) -> None: - """ - Delete the list elements at index ``i`` - """ - @typing.overload - def __delitem__(self, arg0: slice) -> None: - """ - Delete list elements using a slice object - """ - def __eq__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __getitem__(self, s: slice) -> VectorPairStringDouble: - """ - Retrieve list elements using a slice object - """ - @typing.overload - def __getitem__( - self, arg0: typing.SupportsInt | typing.SupportsIndex - ) -> tuple[str, float]: ... - @typing.overload - def __init__(self) -> None: ... - @typing.overload - def __init__(self, arg0: VectorPairStringDouble) -> None: - """ - Copy constructor - """ - @typing.overload - def __init__(self, arg0: collections.abc.Iterable) -> None: ... - def __iter__(self) -> collections.abc.Iterator[tuple[str, float]]: ... - def __len__(self) -> int: ... - def __ne__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __setitem__( - self, - arg0: typing.SupportsInt | typing.SupportsIndex, - arg1: tuple[str, typing.SupportsFloat | typing.SupportsIndex], - ) -> None: ... - @typing.overload - def __setitem__(self, arg0: slice, arg1: VectorPairStringDouble) -> None: - """ - Assign list elements using a slice object - """ - def append( - self, x: tuple[str, typing.SupportsFloat | typing.SupportsIndex] - ) -> None: - """ - Add an item to the end of the list - """ - def clear(self) -> None: - """ - Clear the contents - """ - def count(self, x: tuple[str, typing.SupportsFloat | typing.SupportsIndex]) -> int: - """ - Return the number of times ``x`` appears in the list - """ - @typing.overload - def extend(self, L: VectorPairStringDouble) -> None: - """ - Extend the list by appending all the items in the given list - """ - @typing.overload - def extend(self, L: collections.abc.Iterable) -> None: - """ - Extend the list by appending all the items in the given list - """ - def insert( - self, - i: typing.SupportsInt | typing.SupportsIndex, - x: tuple[str, typing.SupportsFloat | typing.SupportsIndex], - ) -> None: - """ - Insert an item at a given position. - """ - @typing.overload - def pop(self) -> tuple[str, float]: - """ - Remove and return the last item - """ - @typing.overload - def pop(self, i: typing.SupportsInt | typing.SupportsIndex) -> tuple[str, float]: - """ - Remove and return the item at index ``i`` - """ - def remove( - self, x: tuple[str, typing.SupportsFloat | typing.SupportsIndex] - ) -> None: - """ - Remove the first item from the list whose value is x. It is an error if there is no such item. - """ - -class MapStringComplex: - def __bool__(self) -> bool: - """ - Check whether the map is nonempty - """ - @typing.overload - def __contains__(self, arg0: str) -> bool: ... - @typing.overload - def __contains__(self, arg0: typing.Any) -> bool: ... - def __delitem__(self, arg0: str) -> None: ... - def __getitem__(self, arg0: str) -> complex: ... - def __init__(self) -> None: ... - def __iter__(self) -> collections.abc.Iterator[str]: ... - def __len__(self) -> int: ... - def __repr__(self) -> str: - """ - Return the canonical string representation of this map. - """ - def __setitem__( - self, - arg0: str, - arg1: typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex, - ) -> None: ... - def items(self) -> typing.ItemsView: ... - def keys(self) -> typing.KeysView: ... - def values(self) -> typing.ValuesView: ... - -def get_complex_map() -> MapStringComplex: ... -def get_vector_of_pairs() -> VectorPairStringDouble: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi deleted file mode 100644 index dc505538..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import annotations - -import collections.abc - -import typing_extensions - -__all__: list[str] = ["get_buffer", "get_sequence"] - -def get_buffer(arg0: typing_extensions.Buffer) -> typing_extensions.Buffer: ... -def get_sequence(arg0: collections.abc.Sequence) -> collections.abc.Sequence: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi deleted file mode 100644 index 7c36d3fd..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -import datetime - -import numpy -from numpy import random - -__all__: list[str] = [ - "Dummy", - "Foo", - "add_day", - "foolist", - "foovar", - "list_with_none", - "none", - "random", - "t_10ms", - "t_20ns", - "t_30s", -] - -class Dummy: - linalg = numpy.linalg - -class Foo: - pass - -def add_day(arg0: datetime.datetime) -> datetime.datetime: ... - -foolist: list # value = [, ] -foovar: Foo # value = -list_with_none: list = [None, 2, {}] -none = None -t_10ms: datetime.timedelta # value = datetime.timedelta(microseconds=10000) -t_20ns: datetime.timedelta # value = datetime.timedelta(0) -t_30s: datetime.timedelta # value = datetime.timedelta(seconds=30) diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/core.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/core.pyi deleted file mode 100644 index 95cb8455..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/core.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi deleted file mode 100644 index 10d343e0..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from . import classes, functions, functions_3_8_plus, functions_3_9_plus, values - -__all__: list[str] = [ - "classes", - "functions", - "functions_3_8_plus", - "functions_3_9_plus", - "values", -] diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi deleted file mode 100644 index 32aa068b..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["A", "B", "C", "X"] - -class A: - """ - A - """ - -class B(A): - """ - B - """ - -class C(B): - """ - C - """ - -class X: - pass diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi deleted file mode 100644 index 5ed9ef3c..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import annotations - -import sys as sys -import typing as typing - -from demo.pure_python.functions_3_8_plus import args_mix -from demo.pure_python.functions_3_9_plus import generic_alias_annotation - -__all__: list[str] = [ - "accept_frozenset", - "args_mix", - "builtin_function_as_default_arg", - "function_as_default_arg", - "generic_alias_annotation", - "lambda_as_default_arg", - "search", - "static_method_as_default_arg", - "sys", - "typing", -] - -class _Dummy: - @staticmethod - def foo(): ... - -def accept_frozenset(arg: frozenset[int | float]) -> int | None: ... -def builtin_function_as_default_arg(func: type(len) = len): ... -def function_as_default_arg(func: type(search) = search): ... -def lambda_as_default_arg(callback=...): ... -def search(a: int, b: list[int]) -> int: ... -def static_method_as_default_arg(callback=_Dummy.foo): ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi deleted file mode 100644 index c55aed1d..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import typing as typing - -__all__: list[str] = [ - "Expression", - "Token", - "args_mix", - "nested_current_module_annotations", - "typing", -] - -class Token: - pass - -class Expression: - pass - -def args_mix( - a: int, - b: float = 0.5, - c: str = "", - *args: int, - x: int = 1, - y=int, - **kwargs: dict[int, str], -): ... -def nested_current_module_annotations( - tokens: list[Token], expr: Expression | None = None -) -> dict[str, Expression]: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi deleted file mode 100644 index 59b9b80c..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["generic_alias_annotation"] - -def generic_alias_annotation(a: list[tuple[int]], b: dict[int, str]) -> list[float]: ... diff --git a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi b/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi deleted file mode 100644 index 07913391..00000000 --- a/tests/stubs/python-3.11/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["callables_dict"] -callables_dict: dict = {"len": len, "int": int} diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/__init__.pyi deleted file mode 100644 index 9f27c427..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/__init__.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -from . import _bindings, core, pure_python - -__all__: list[str] = [ - "aliases", - "classes", - "core", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "pure_python", - "stl", - "stl_bind", - "typing", - "values", - "version", -] -version: str = "0.0.0" diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi deleted file mode 100644 index 9ea9beb4..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from . import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi deleted file mode 100644 index d8c8d8ac..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.enum -import numpy -from demo._bindings.aliases.foreign_method_arg import Bar2 as foreign_type_alias -from demo._bindings.aliases.foreign_return import get_foo as foreign_class_alias -from numpy import random - -from . import ( - foreign_arg, - foreign_attr, - foreign_class_member, - foreign_method_arg, - foreign_method_return, - foreign_return, - missing_self_arg, -) - -__all__: list[str] = [ - "Color", - "Dummy", - "foreign_arg", - "foreign_attr", - "foreign_class_alias", - "foreign_class_member", - "foreign_enum_default", - "foreign_method_arg", - "foreign_method_return", - "foreign_return", - "foreign_type_alias", - "func", - "local_func_alias", - "local_type_alias", - "missing_self_arg", - "random", -] - -class Dummy: - linalg = numpy.linalg - -class Color: - pass - -def foreign_enum_default( - color: typing.Any = demo._bindings.enum.ConsoleForegroundColor.Blue, -) -> None: ... -def func(arg0: int) -> int: ... - -local_type_alias = Color -local_func_alias = func diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi deleted file mode 100644 index de75e6fb..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["set_foo"] - -def set_foo(arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi deleted file mode 100644 index 10c4a712..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["value"] -value: demo._bindings.classes.Foo # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi deleted file mode 100644 index 497a4e2a..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.classes - -__all__: list[str] = ["Bar1"] - -class Bar1: - foo: typing.ClassVar[ - demo._bindings.classes.Foo - ] # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi deleted file mode 100644 index 8d4ed4ac..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar2"] - -class Bar2: - def set_foo(self, arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi deleted file mode 100644 index 95d218e1..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar3"] - -class Bar3: - @staticmethod - def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi deleted file mode 100644 index 7b5b428f..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["get_foo"] - -def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi deleted file mode 100644 index ee1a09ee..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar4"] - -class Bar4: - def set_foo(self: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi deleted file mode 100644 index 73c6e950..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "CppException", - "Derived", - "Foo", - "MyBase", - "Outer", - "ParIter", - "ParIterBase", - "ParticleContainer", -] - -class Outer: - class Inner: - class NestedEnum: - """ - Members: - - ONE - - TWO - """ - - ONE: typing.ClassVar[Outer.Inner.NestedEnum] # value = - TWO: typing.ClassVar[Outer.Inner.NestedEnum] # value = - __members__: typing.ClassVar[ - dict[str, Outer.Inner.NestedEnum] - ] # value = {'ONE': , 'TWO': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - - value: Outer.Inner.NestedEnum - - inner: Outer.Inner - -class MyBase: - class Inner: - pass - - name: str - -class Derived(MyBase): - count: int - -class Foo: - class FooChild: - def __init__(self) -> None: ... - def g(self) -> None: ... - - def __init__(self) -> None: ... - def f(self) -> None: ... - -class ParIterBase: - level: int - -class ParIter(ParIterBase): - def __init__(self, particle_container: ParticleContainer, level: int) -> None: ... - -class ParticleContainer: - name: str - Iterator = ParIter - def process(self, arg0: ParIter) -> None: ... - -class CppException(Exception): - pass diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi deleted file mode 100644 index 795a711b..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi +++ /dev/null @@ -1,101 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy -import scipy.sparse - -import pybind11_stubgen.typing_ext - -__all__: list[str] = [ - "accept_matrix_int", - "accept_vector_float64", - "dense_matrix_c", - "dense_matrix_r", - "fixed_mutator_a", - "fixed_mutator_c", - "fixed_mutator_r", - "four_col_matrix_r", - "four_row_matrix_r", - "get_matrix_int", - "get_vector_float64", - "sparse_matrix_c", - "sparse_matrix_r", -] - -def accept_matrix_int( - arg0: typing.Annotated[ - numpy.ndarray, numpy.int32, pybind11_stubgen.typing_ext.FixedSize(3, 3) - ], -) -> None: ... -def accept_vector_float64( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float64, pybind11_stubgen.typing_ext.FixedSize(3, 1) - ], -) -> None: ... -def dense_matrix_c( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") -]: ... -def dense_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") -]: ... -def fixed_mutator_a( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - ], -) -> None: ... -def fixed_mutator_c( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - numpy.ndarray.flags.f_contiguous, - ], -) -> None: ... -def fixed_mutator_r( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - numpy.ndarray.flags.c_contiguous, - ], -) -> None: ... -def four_col_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", 4) - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", 4) -]: ... -def four_row_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize(4, "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize(4, "n") -]: ... -def get_matrix_int() -> typing.Annotated[ - numpy.ndarray, numpy.int32, pybind11_stubgen.typing_ext.FixedSize(3, 3) -]: ... -def get_vector_float64() -> typing.Annotated[ - numpy.ndarray, numpy.float64, pybind11_stubgen.typing_ext.FixedSize(3, 1) -]: ... -def sparse_matrix_c( - arg0: typing.Annotated[scipy.sparse.csc_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csc_matrix, numpy.float32]: ... -def sparse_matrix_r( - arg0: typing.Annotated[scipy.sparse.csr_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csr_matrix, numpy.float32]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi deleted file mode 100644 index 57aba5b9..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Blue", - "ConsoleForegroundColor", - "Green", - "Magenta", - "None_", - "Yellow", - "accept_defaulted_enum", -] - -class ConsoleForegroundColor: - """ - Members: - - Green - - Yellow - - Blue - - Magenta - - None_ - """ - - Blue: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Green: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Magenta: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - None_: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Yellow: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - __members__: typing.ClassVar[ - dict[str, ConsoleForegroundColor] - ] # value = {'Green': , 'Yellow': , 'Blue': , 'Magenta': , 'None_': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - -def accept_defaulted_enum( - color: ConsoleForegroundColor = ConsoleForegroundColor.None_, -) -> None: ... - -Blue: ConsoleForegroundColor # value = -Green: ConsoleForegroundColor # value = -Magenta: ConsoleForegroundColor # value = -None_: ConsoleForegroundColor # value = -Yellow: ConsoleForegroundColor # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi deleted file mode 100644 index 96774842..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = [ - "Enum", - "Unbound", - "accept_unbound_enum", - "accept_unbound_enum_defaulted", - "accept_unbound_type", - "accept_unbound_type_defaulted", - "get_unbound_type", -] - -class Unbound: - pass - -class Enum: - pass - -def accept_unbound_enum(arg0: ...) -> int: ... -def accept_unbound_enum_defaulted(x: Enum = ...) -> int: ... -def accept_unbound_type(arg0: tuple[..., int]) -> int: ... -def accept_unbound_type_defaulted(x: Unbound = ...) -> int: ... -def get_unbound_type() -> ...: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi deleted file mode 100644 index 52c09281..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import types - -__all__: list[str] = ["mapping_proxy", "none"] -mapping_proxy: types.MappingProxyType # value = mappingproxy({}) -none = None diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi deleted file mode 100644 index a537a958..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "backslashes_should_be_escaped", - "issue_51_catastrophic_regex", - "issue_73_utf8_doc_chars", - "issue_274_docstring_leading_newline", -] - -def backslashes_should_be_escaped() -> None: - """ - \\brief A brief description of this function. - - A detailed description of this function. - - Here's some reStructuredText: :math:`x = [x, y, \\theta]^T` - """ - -def issue_274_docstring_leading_newline() -> None: - """ - This is a docstring - """ - -def issue_51_catastrophic_regex(arg0: int, arg1: int) -> None: - """ - Use-case: - issue_51(os.get_handle_inheritable, os.set_handle_inheritable) - """ - -def issue_73_utf8_doc_chars() -> None: - """ - Construct a Ramsete unicycle controller. - - Tuning parameter (b > 0 rad²/m²) for which larger values make - - convergence more aggressive like a proportional term. - Tuning parameter (0 rad⁻¹ < zeta < 1 rad⁻¹) for which larger - values provide more damping in response. - """ - -_cleanup: typing.Any # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi deleted file mode 100644 index 6c115cd1..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["Dummy"] - -class Dummy: - @staticmethod - def static_method(arg0: int) -> int: ... - def regular_method(self, arg0: int) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi deleted file mode 100644 index 34943048..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy - -__all__: list[str] = [ - "accept_ndarray_float64", - "accept_ndarray_int", - "get_ndarray_float64", - "get_ndarray_int", - "return_dtype", -] - -def accept_ndarray_float64( - arg0: typing.Annotated[numpy.ndarray, numpy.float64], -) -> None: ... -def accept_ndarray_int(arg0: typing.Annotated[numpy.ndarray, numpy.int32]) -> None: ... -def get_ndarray_float64() -> typing.Annotated[numpy.ndarray, numpy.float64]: ... -def get_ndarray_int() -> typing.Annotated[numpy.ndarray, numpy.int32]: ... -def return_dtype() -> numpy.dtype[typing.Any]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi deleted file mode 100644 index e0faec55..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -import typing - -import pybind11_stubgen.typing_ext - -__all__: list[str] = [ - "std_array", - "std_map", - "std_optional", - "std_variant", - "std_vector", -] - -def std_array( - arg0: typing.Annotated[list[int], pybind11_stubgen.typing_ext.FixedSize(3)], -) -> typing.Annotated[list[int], pybind11_stubgen.typing_ext.FixedSize(3)]: ... -def std_map() -> dict[int, complex]: ... -def std_optional(arg0: int | None) -> None: ... -def std_variant(arg0: int | float | tuple[int, int]) -> None: ... -def std_vector() -> list[tuple[int, float]]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi deleted file mode 100644 index f2c4ab95..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import annotations - -import typing - -import typing_extensions - -__all__: list[str] = ["get_buffer", "get_sequence"] - -def get_buffer(arg0: typing_extensions.Buffer) -> typing_extensions.Buffer: ... -def get_sequence(arg0: typing.Sequence) -> typing.Sequence: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi deleted file mode 100644 index 7c36d3fd..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -import datetime - -import numpy -from numpy import random - -__all__: list[str] = [ - "Dummy", - "Foo", - "add_day", - "foolist", - "foovar", - "list_with_none", - "none", - "random", - "t_10ms", - "t_20ns", - "t_30s", -] - -class Dummy: - linalg = numpy.linalg - -class Foo: - pass - -def add_day(arg0: datetime.datetime) -> datetime.datetime: ... - -foolist: list # value = [, ] -foovar: Foo # value = -list_with_none: list = [None, 2, {}] -none = None -t_10ms: datetime.timedelta # value = datetime.timedelta(microseconds=10000) -t_20ns: datetime.timedelta # value = datetime.timedelta(0) -t_30s: datetime.timedelta # value = datetime.timedelta(seconds=30) diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/core.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/core.pyi deleted file mode 100644 index 95cb8455..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/core.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi deleted file mode 100644 index 10d343e0..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from . import classes, functions, functions_3_8_plus, functions_3_9_plus, values - -__all__: list[str] = [ - "classes", - "functions", - "functions_3_8_plus", - "functions_3_9_plus", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi deleted file mode 100644 index 32aa068b..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["A", "B", "C", "X"] - -class A: - """ - A - """ - -class B(A): - """ - B - """ - -class C(B): - """ - C - """ - -class X: - pass diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi deleted file mode 100644 index 5ed9ef3c..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import annotations - -import sys as sys -import typing as typing - -from demo.pure_python.functions_3_8_plus import args_mix -from demo.pure_python.functions_3_9_plus import generic_alias_annotation - -__all__: list[str] = [ - "accept_frozenset", - "args_mix", - "builtin_function_as_default_arg", - "function_as_default_arg", - "generic_alias_annotation", - "lambda_as_default_arg", - "search", - "static_method_as_default_arg", - "sys", - "typing", -] - -class _Dummy: - @staticmethod - def foo(): ... - -def accept_frozenset(arg: frozenset[int | float]) -> int | None: ... -def builtin_function_as_default_arg(func: type(len) = len): ... -def function_as_default_arg(func: type(search) = search): ... -def lambda_as_default_arg(callback=...): ... -def search(a: int, b: list[int]) -> int: ... -def static_method_as_default_arg(callback=_Dummy.foo): ... diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi deleted file mode 100644 index c55aed1d..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import typing as typing - -__all__: list[str] = [ - "Expression", - "Token", - "args_mix", - "nested_current_module_annotations", - "typing", -] - -class Token: - pass - -class Expression: - pass - -def args_mix( - a: int, - b: float = 0.5, - c: str = "", - *args: int, - x: int = 1, - y=int, - **kwargs: dict[int, str], -): ... -def nested_current_module_annotations( - tokens: list[Token], expr: Expression | None = None -) -> dict[str, Expression]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi deleted file mode 100644 index 59b9b80c..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["generic_alias_annotation"] - -def generic_alias_annotation(a: list[tuple[int]], b: dict[int, str]) -> list[float]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi deleted file mode 100644 index 07913391..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["callables_dict"] -callables_dict: dict = {"len": len, "int": int} diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/__init__.pyi deleted file mode 100644 index 9f27c427..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/__init__.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -from . import _bindings, core, pure_python - -__all__: list[str] = [ - "aliases", - "classes", - "core", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "pure_python", - "stl", - "stl_bind", - "typing", - "values", - "version", -] -version: str = "0.0.0" diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi deleted file mode 100644 index 9ea9beb4..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from . import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi deleted file mode 100644 index d8c8d8ac..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.enum -import numpy -from demo._bindings.aliases.foreign_method_arg import Bar2 as foreign_type_alias -from demo._bindings.aliases.foreign_return import get_foo as foreign_class_alias -from numpy import random - -from . import ( - foreign_arg, - foreign_attr, - foreign_class_member, - foreign_method_arg, - foreign_method_return, - foreign_return, - missing_self_arg, -) - -__all__: list[str] = [ - "Color", - "Dummy", - "foreign_arg", - "foreign_attr", - "foreign_class_alias", - "foreign_class_member", - "foreign_enum_default", - "foreign_method_arg", - "foreign_method_return", - "foreign_return", - "foreign_type_alias", - "func", - "local_func_alias", - "local_type_alias", - "missing_self_arg", - "random", -] - -class Dummy: - linalg = numpy.linalg - -class Color: - pass - -def foreign_enum_default( - color: typing.Any = demo._bindings.enum.ConsoleForegroundColor.Blue, -) -> None: ... -def func(arg0: int) -> int: ... - -local_type_alias = Color -local_func_alias = func diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi deleted file mode 100644 index de75e6fb..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["set_foo"] - -def set_foo(arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi deleted file mode 100644 index 10c4a712..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["value"] -value: demo._bindings.classes.Foo # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi deleted file mode 100644 index 497a4e2a..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.classes - -__all__: list[str] = ["Bar1"] - -class Bar1: - foo: typing.ClassVar[ - demo._bindings.classes.Foo - ] # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi deleted file mode 100644 index 8d4ed4ac..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar2"] - -class Bar2: - def set_foo(self, arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi deleted file mode 100644 index 95d218e1..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar3"] - -class Bar3: - @staticmethod - def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi deleted file mode 100644 index 7b5b428f..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["get_foo"] - -def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi deleted file mode 100644 index ee1a09ee..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar4"] - -class Bar4: - def set_foo(self: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi deleted file mode 100644 index 73c6e950..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "CppException", - "Derived", - "Foo", - "MyBase", - "Outer", - "ParIter", - "ParIterBase", - "ParticleContainer", -] - -class Outer: - class Inner: - class NestedEnum: - """ - Members: - - ONE - - TWO - """ - - ONE: typing.ClassVar[Outer.Inner.NestedEnum] # value = - TWO: typing.ClassVar[Outer.Inner.NestedEnum] # value = - __members__: typing.ClassVar[ - dict[str, Outer.Inner.NestedEnum] - ] # value = {'ONE': , 'TWO': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - - value: Outer.Inner.NestedEnum - - inner: Outer.Inner - -class MyBase: - class Inner: - pass - - name: str - -class Derived(MyBase): - count: int - -class Foo: - class FooChild: - def __init__(self) -> None: ... - def g(self) -> None: ... - - def __init__(self) -> None: ... - def f(self) -> None: ... - -class ParIterBase: - level: int - -class ParIter(ParIterBase): - def __init__(self, particle_container: ParticleContainer, level: int) -> None: ... - -class ParticleContainer: - name: str - Iterator = ParIter - def process(self, arg0: ParIter) -> None: ... - -class CppException(Exception): - pass diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi deleted file mode 100644 index 795a711b..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi +++ /dev/null @@ -1,101 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy -import scipy.sparse - -import pybind11_stubgen.typing_ext - -__all__: list[str] = [ - "accept_matrix_int", - "accept_vector_float64", - "dense_matrix_c", - "dense_matrix_r", - "fixed_mutator_a", - "fixed_mutator_c", - "fixed_mutator_r", - "four_col_matrix_r", - "four_row_matrix_r", - "get_matrix_int", - "get_vector_float64", - "sparse_matrix_c", - "sparse_matrix_r", -] - -def accept_matrix_int( - arg0: typing.Annotated[ - numpy.ndarray, numpy.int32, pybind11_stubgen.typing_ext.FixedSize(3, 3) - ], -) -> None: ... -def accept_vector_float64( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float64, pybind11_stubgen.typing_ext.FixedSize(3, 1) - ], -) -> None: ... -def dense_matrix_c( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") -]: ... -def dense_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") -]: ... -def fixed_mutator_a( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - ], -) -> None: ... -def fixed_mutator_c( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - numpy.ndarray.flags.f_contiguous, - ], -) -> None: ... -def fixed_mutator_r( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - numpy.ndarray.flags.c_contiguous, - ], -) -> None: ... -def four_col_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", 4) - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", 4) -]: ... -def four_row_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize(4, "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize(4, "n") -]: ... -def get_matrix_int() -> typing.Annotated[ - numpy.ndarray, numpy.int32, pybind11_stubgen.typing_ext.FixedSize(3, 3) -]: ... -def get_vector_float64() -> typing.Annotated[ - numpy.ndarray, numpy.float64, pybind11_stubgen.typing_ext.FixedSize(3, 1) -]: ... -def sparse_matrix_c( - arg0: typing.Annotated[scipy.sparse.csc_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csc_matrix, numpy.float32]: ... -def sparse_matrix_r( - arg0: typing.Annotated[scipy.sparse.csr_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csr_matrix, numpy.float32]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi deleted file mode 100644 index 57aba5b9..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Blue", - "ConsoleForegroundColor", - "Green", - "Magenta", - "None_", - "Yellow", - "accept_defaulted_enum", -] - -class ConsoleForegroundColor: - """ - Members: - - Green - - Yellow - - Blue - - Magenta - - None_ - """ - - Blue: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Green: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Magenta: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - None_: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Yellow: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - __members__: typing.ClassVar[ - dict[str, ConsoleForegroundColor] - ] # value = {'Green': , 'Yellow': , 'Blue': , 'Magenta': , 'None_': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - -def accept_defaulted_enum( - color: ConsoleForegroundColor = ConsoleForegroundColor.None_, -) -> None: ... - -Blue: ConsoleForegroundColor # value = -Green: ConsoleForegroundColor # value = -Magenta: ConsoleForegroundColor # value = -None_: ConsoleForegroundColor # value = -Yellow: ConsoleForegroundColor # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi deleted file mode 100644 index 96774842..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = [ - "Enum", - "Unbound", - "accept_unbound_enum", - "accept_unbound_enum_defaulted", - "accept_unbound_type", - "accept_unbound_type_defaulted", - "get_unbound_type", -] - -class Unbound: - pass - -class Enum: - pass - -def accept_unbound_enum(arg0: ...) -> int: ... -def accept_unbound_enum_defaulted(x: Enum = ...) -> int: ... -def accept_unbound_type(arg0: tuple[..., int]) -> int: ... -def accept_unbound_type_defaulted(x: Unbound = ...) -> int: ... -def get_unbound_type() -> ...: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi deleted file mode 100644 index 52c09281..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import types - -__all__: list[str] = ["mapping_proxy", "none"] -mapping_proxy: types.MappingProxyType # value = mappingproxy({}) -none = None diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi deleted file mode 100644 index a537a958..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "backslashes_should_be_escaped", - "issue_51_catastrophic_regex", - "issue_73_utf8_doc_chars", - "issue_274_docstring_leading_newline", -] - -def backslashes_should_be_escaped() -> None: - """ - \\brief A brief description of this function. - - A detailed description of this function. - - Here's some reStructuredText: :math:`x = [x, y, \\theta]^T` - """ - -def issue_274_docstring_leading_newline() -> None: - """ - This is a docstring - """ - -def issue_51_catastrophic_regex(arg0: int, arg1: int) -> None: - """ - Use-case: - issue_51(os.get_handle_inheritable, os.set_handle_inheritable) - """ - -def issue_73_utf8_doc_chars() -> None: - """ - Construct a Ramsete unicycle controller. - - Tuning parameter (b > 0 rad²/m²) for which larger values make - - convergence more aggressive like a proportional term. - Tuning parameter (0 rad⁻¹ < zeta < 1 rad⁻¹) for which larger - values provide more damping in response. - """ - -_cleanup: typing.Any # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi deleted file mode 100644 index 6c115cd1..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["Dummy"] - -class Dummy: - @staticmethod - def static_method(arg0: int) -> int: ... - def regular_method(self, arg0: int) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi deleted file mode 100644 index 34943048..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy - -__all__: list[str] = [ - "accept_ndarray_float64", - "accept_ndarray_int", - "get_ndarray_float64", - "get_ndarray_int", - "return_dtype", -] - -def accept_ndarray_float64( - arg0: typing.Annotated[numpy.ndarray, numpy.float64], -) -> None: ... -def accept_ndarray_int(arg0: typing.Annotated[numpy.ndarray, numpy.int32]) -> None: ... -def get_ndarray_float64() -> typing.Annotated[numpy.ndarray, numpy.float64]: ... -def get_ndarray_int() -> typing.Annotated[numpy.ndarray, numpy.int32]: ... -def return_dtype() -> numpy.dtype[typing.Any]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi deleted file mode 100644 index e47254e8..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi +++ /dev/null @@ -1,117 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "WithGetterSetterDoc", - "WithPropAndGetterSetterDoc", - "WithPropDoc", - "WithoutDoc", -] - -class WithoutDoc: - """ - No user docstring provided - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - def_property: int - def_readwrite: int - @property - def def_property_readonly(self) -> int: ... - @property - def def_readonly(self) -> int: ... - -class WithPropDoc: - """ - User docstring provided only to `def_` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - """ - prop doc token - """ - def_property_static: typing.ClassVar[int] = 0 - """ - prop doc token - """ - @property - def def_property(self) -> int: - """ - prop doc token - """ - @def_property.setter - def def_property(self, arg1: int) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ - @property - def def_readonly(self) -> int: - """ - prop doc token - """ - @property - def def_readwrite(self) -> int: - """ - prop doc token - """ - @def_readwrite.setter - def def_readwrite(self, arg0: int) -> None: ... - -class WithGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters, but NOT to `def_*(..., doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - """ - getter doc token - """ - def_property_static: typing.ClassVar[int] = 0 - """ - getter doc token - """ - @property - def def_property(self) -> int: - """ - getter doc token - """ - @def_property.setter - def def_property(self, arg1: int) -> None: - """ - setter doc token - """ - @property - def def_property_readonly(self) -> int: - """ - getter doc token - """ - -class WithPropAndGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters and to `def_*(, doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - """ - prop doc token - """ - def_property_static: typing.ClassVar[int] = 0 - """ - prop doc token - """ - @property - def def_property(self) -> int: - """ - prop doc token - """ - @def_property.setter - def def_property(self, arg1: int) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi deleted file mode 100644 index e0faec55..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -import typing - -import pybind11_stubgen.typing_ext - -__all__: list[str] = [ - "std_array", - "std_map", - "std_optional", - "std_variant", - "std_vector", -] - -def std_array( - arg0: typing.Annotated[list[int], pybind11_stubgen.typing_ext.FixedSize(3)], -) -> typing.Annotated[list[int], pybind11_stubgen.typing_ext.FixedSize(3)]: ... -def std_map() -> dict[int, complex]: ... -def std_optional(arg0: int | None) -> None: ... -def std_variant(arg0: int | float | tuple[int, int]) -> None: ... -def std_vector() -> list[tuple[int, float]]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi deleted file mode 100644 index 2cac91aa..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi +++ /dev/null @@ -1,124 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "MapStringComplex", - "VectorPairStringDouble", - "get_complex_map", - "get_vector_of_pairs", -] - -class VectorPairStringDouble: - __hash__: typing.ClassVar[None] = None - def __bool__(self) -> bool: - """ - Check whether the list is nonempty - """ - def __contains__(self, x: tuple[str, float]) -> bool: - """ - Return true the container contains ``x`` - """ - @typing.overload - def __delitem__(self, arg0: int) -> None: - """ - Delete the list elements at index ``i`` - """ - @typing.overload - def __delitem__(self, arg0: slice) -> None: - """ - Delete list elements using a slice object - """ - def __eq__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __getitem__(self, s: slice) -> VectorPairStringDouble: - """ - Retrieve list elements using a slice object - """ - @typing.overload - def __getitem__(self, arg0: int) -> tuple[str, float]: ... - @typing.overload - def __init__(self) -> None: ... - @typing.overload - def __init__(self, arg0: VectorPairStringDouble) -> None: - """ - Copy constructor - """ - @typing.overload - def __init__(self, arg0: typing.Iterable) -> None: ... - def __iter__(self) -> typing.Iterator[tuple[str, float]]: ... - def __len__(self) -> int: ... - def __ne__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __setitem__(self, arg0: int, arg1: tuple[str, float]) -> None: ... - @typing.overload - def __setitem__(self, arg0: slice, arg1: VectorPairStringDouble) -> None: - """ - Assign list elements using a slice object - """ - def append(self, x: tuple[str, float]) -> None: - """ - Add an item to the end of the list - """ - def clear(self) -> None: - """ - Clear the contents - """ - def count(self, x: tuple[str, float]) -> int: - """ - Return the number of times ``x`` appears in the list - """ - @typing.overload - def extend(self, L: VectorPairStringDouble) -> None: - """ - Extend the list by appending all the items in the given list - """ - @typing.overload - def extend(self, L: typing.Iterable) -> None: - """ - Extend the list by appending all the items in the given list - """ - def insert(self, i: int, x: tuple[str, float]) -> None: - """ - Insert an item at a given position. - """ - @typing.overload - def pop(self) -> tuple[str, float]: - """ - Remove and return the last item - """ - @typing.overload - def pop(self, i: int) -> tuple[str, float]: - """ - Remove and return the item at index ``i`` - """ - def remove(self, x: tuple[str, float]) -> None: - """ - Remove the first item from the list whose value is x. It is an error if there is no such item. - """ - -class MapStringComplex: - def __bool__(self) -> bool: - """ - Check whether the map is nonempty - """ - @typing.overload - def __contains__(self, arg0: str) -> bool: ... - @typing.overload - def __contains__(self, arg0: typing.Any) -> bool: ... - def __delitem__(self, arg0: str) -> None: ... - def __getitem__(self, arg0: str) -> complex: ... - def __init__(self) -> None: ... - def __iter__(self) -> typing.Iterator[str]: ... - def __len__(self) -> int: ... - def __repr__(self) -> str: - """ - Return the canonical string representation of this map. - """ - def __setitem__(self, arg0: str, arg1: complex) -> None: ... - def items(self) -> typing.ItemsView: ... - def keys(self) -> typing.KeysView: ... - def values(self) -> typing.ValuesView: ... - -def get_complex_map() -> MapStringComplex: ... -def get_vector_of_pairs() -> VectorPairStringDouble: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi deleted file mode 100644 index f2c4ab95..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import annotations - -import typing - -import typing_extensions - -__all__: list[str] = ["get_buffer", "get_sequence"] - -def get_buffer(arg0: typing_extensions.Buffer) -> typing_extensions.Buffer: ... -def get_sequence(arg0: typing.Sequence) -> typing.Sequence: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi deleted file mode 100644 index 7c36d3fd..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -import datetime - -import numpy -from numpy import random - -__all__: list[str] = [ - "Dummy", - "Foo", - "add_day", - "foolist", - "foovar", - "list_with_none", - "none", - "random", - "t_10ms", - "t_20ns", - "t_30s", -] - -class Dummy: - linalg = numpy.linalg - -class Foo: - pass - -def add_day(arg0: datetime.datetime) -> datetime.datetime: ... - -foolist: list # value = [, ] -foovar: Foo # value = -list_with_none: list = [None, 2, {}] -none = None -t_10ms: datetime.timedelta # value = datetime.timedelta(microseconds=10000) -t_20ns: datetime.timedelta # value = datetime.timedelta(0) -t_30s: datetime.timedelta # value = datetime.timedelta(seconds=30) diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/core.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/core.pyi deleted file mode 100644 index 95cb8455..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/core.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi deleted file mode 100644 index 10d343e0..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from . import classes, functions, functions_3_8_plus, functions_3_9_plus, values - -__all__: list[str] = [ - "classes", - "functions", - "functions_3_8_plus", - "functions_3_9_plus", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi deleted file mode 100644 index 32aa068b..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["A", "B", "C", "X"] - -class A: - """ - A - """ - -class B(A): - """ - B - """ - -class C(B): - """ - C - """ - -class X: - pass diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi deleted file mode 100644 index 5ed9ef3c..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import annotations - -import sys as sys -import typing as typing - -from demo.pure_python.functions_3_8_plus import args_mix -from demo.pure_python.functions_3_9_plus import generic_alias_annotation - -__all__: list[str] = [ - "accept_frozenset", - "args_mix", - "builtin_function_as_default_arg", - "function_as_default_arg", - "generic_alias_annotation", - "lambda_as_default_arg", - "search", - "static_method_as_default_arg", - "sys", - "typing", -] - -class _Dummy: - @staticmethod - def foo(): ... - -def accept_frozenset(arg: frozenset[int | float]) -> int | None: ... -def builtin_function_as_default_arg(func: type(len) = len): ... -def function_as_default_arg(func: type(search) = search): ... -def lambda_as_default_arg(callback=...): ... -def search(a: int, b: list[int]) -> int: ... -def static_method_as_default_arg(callback=_Dummy.foo): ... diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi deleted file mode 100644 index c55aed1d..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import typing as typing - -__all__: list[str] = [ - "Expression", - "Token", - "args_mix", - "nested_current_module_annotations", - "typing", -] - -class Token: - pass - -class Expression: - pass - -def args_mix( - a: int, - b: float = 0.5, - c: str = "", - *args: int, - x: int = 1, - y=int, - **kwargs: dict[int, str], -): ... -def nested_current_module_annotations( - tokens: list[Token], expr: Expression | None = None -) -> dict[str, Expression]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi deleted file mode 100644 index 59b9b80c..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["generic_alias_annotation"] - -def generic_alias_annotation(a: list[tuple[int]], b: dict[int, str]) -> list[float]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi deleted file mode 100644 index 07913391..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["callables_dict"] -callables_dict: dict = {"len": len, "int": int} diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/__init__.pyi deleted file mode 100644 index 9f27c427..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/__init__.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -from . import _bindings, core, pure_python - -__all__: list[str] = [ - "aliases", - "classes", - "core", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "pure_python", - "stl", - "stl_bind", - "typing", - "values", - "version", -] -version: str = "0.0.0" diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/__init__.pyi deleted file mode 100644 index 9ea9beb4..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/__init__.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from . import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/__init__.pyi deleted file mode 100644 index d8c8d8ac..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/__init__.pyi +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.enum -import numpy -from demo._bindings.aliases.foreign_method_arg import Bar2 as foreign_type_alias -from demo._bindings.aliases.foreign_return import get_foo as foreign_class_alias -from numpy import random - -from . import ( - foreign_arg, - foreign_attr, - foreign_class_member, - foreign_method_arg, - foreign_method_return, - foreign_return, - missing_self_arg, -) - -__all__: list[str] = [ - "Color", - "Dummy", - "foreign_arg", - "foreign_attr", - "foreign_class_alias", - "foreign_class_member", - "foreign_enum_default", - "foreign_method_arg", - "foreign_method_return", - "foreign_return", - "foreign_type_alias", - "func", - "local_func_alias", - "local_type_alias", - "missing_self_arg", - "random", -] - -class Dummy: - linalg = numpy.linalg - -class Color: - pass - -def foreign_enum_default( - color: typing.Any = demo._bindings.enum.ConsoleForegroundColor.Blue, -) -> None: ... -def func(arg0: int) -> int: ... - -local_type_alias = Color -local_func_alias = func diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_arg.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_arg.pyi deleted file mode 100644 index de75e6fb..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_arg.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["set_foo"] - -def set_foo(arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_attr.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_attr.pyi deleted file mode 100644 index 10c4a712..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_attr.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["value"] -value: demo._bindings.classes.Foo # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_class_member.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_class_member.pyi deleted file mode 100644 index 497a4e2a..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_class_member.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.classes - -__all__: list[str] = ["Bar1"] - -class Bar1: - foo: typing.ClassVar[ - demo._bindings.classes.Foo - ] # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_arg.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_arg.pyi deleted file mode 100644 index 8d4ed4ac..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar2"] - -class Bar2: - def set_foo(self, arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_return.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_return.pyi deleted file mode 100644 index 95d218e1..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_return.pyi +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar3"] - -class Bar3: - @staticmethod - def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_return.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_return.pyi deleted file mode 100644 index 7b5b428f..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/foreign_return.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["get_foo"] - -def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/missing_self_arg.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/missing_self_arg.pyi deleted file mode 100644 index ee1a09ee..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/aliases/missing_self_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar4"] - -class Bar4: - def set_foo(self: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/classes.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/classes.pyi deleted file mode 100644 index 73c6e950..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/classes.pyi +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "CppException", - "Derived", - "Foo", - "MyBase", - "Outer", - "ParIter", - "ParIterBase", - "ParticleContainer", -] - -class Outer: - class Inner: - class NestedEnum: - """ - Members: - - ONE - - TWO - """ - - ONE: typing.ClassVar[Outer.Inner.NestedEnum] # value = - TWO: typing.ClassVar[Outer.Inner.NestedEnum] # value = - __members__: typing.ClassVar[ - dict[str, Outer.Inner.NestedEnum] - ] # value = {'ONE': , 'TWO': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - - value: Outer.Inner.NestedEnum - - inner: Outer.Inner - -class MyBase: - class Inner: - pass - - name: str - -class Derived(MyBase): - count: int - -class Foo: - class FooChild: - def __init__(self) -> None: ... - def g(self) -> None: ... - - def __init__(self) -> None: ... - def f(self) -> None: ... - -class ParIterBase: - level: int - -class ParIter(ParIterBase): - def __init__(self, particle_container: ParticleContainer, level: int) -> None: ... - -class ParticleContainer: - name: str - Iterator = ParIter - def process(self, arg0: ParIter) -> None: ... - -class CppException(Exception): - pass diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/enum.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/enum.pyi deleted file mode 100644 index 57aba5b9..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/enum.pyi +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Blue", - "ConsoleForegroundColor", - "Green", - "Magenta", - "None_", - "Yellow", - "accept_defaulted_enum", -] - -class ConsoleForegroundColor: - """ - Members: - - Green - - Yellow - - Blue - - Magenta - - None_ - """ - - Blue: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Green: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Magenta: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - None_: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Yellow: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - __members__: typing.ClassVar[ - dict[str, ConsoleForegroundColor] - ] # value = {'Green': , 'Yellow': , 'Blue': , 'Magenta': , 'None_': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - -def accept_defaulted_enum( - color: ConsoleForegroundColor = ConsoleForegroundColor.None_, -) -> None: ... - -Blue: ConsoleForegroundColor # value = -Green: ConsoleForegroundColor # value = -Magenta: ConsoleForegroundColor # value = -None_: ConsoleForegroundColor # value = -Yellow: ConsoleForegroundColor # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/flawed_bindings.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/flawed_bindings.pyi deleted file mode 100644 index 96774842..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/flawed_bindings.pyi +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = [ - "Enum", - "Unbound", - "accept_unbound_enum", - "accept_unbound_enum_defaulted", - "accept_unbound_type", - "accept_unbound_type_defaulted", - "get_unbound_type", -] - -class Unbound: - pass - -class Enum: - pass - -def accept_unbound_enum(arg0: ...) -> int: ... -def accept_unbound_enum_defaulted(x: Enum = ...) -> int: ... -def accept_unbound_type(arg0: tuple[..., int]) -> int: ... -def accept_unbound_type_defaulted(x: Unbound = ...) -> int: ... -def get_unbound_type() -> ...: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/functions.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/functions.pyi deleted file mode 100644 index bb839212..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/functions.pyi +++ /dev/null @@ -1,76 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Foo", - "accept_annotated_callable", - "accept_callable", - "accept_frozenset", - "accept_py_handle", - "accept_py_object", - "accept_set", - "add", - "default_custom_arg", - "default_int_arg", - "default_list_arg", - "default_optional_arg", - "func_w_anon_args", - "func_w_named_pos_args", - "generic", - "mul", - "nested_types", - "pass_callback", - "passthrough1", - "passthrough2", - "passthrough3", - "passthrough_backwards", - "pos_kw_only_mix", - "pos_kw_only_variadic_mix", -] - -class Foo: - def __init__(self, arg0: int) -> None: ... - -def accept_annotated_callable(arg0: typing.Callable[[int, int], int]) -> typing.Any: ... -def accept_callable(arg0: typing.Callable) -> typing.Any: ... -def accept_frozenset(arg0: frozenset) -> None: ... -def accept_py_handle(arg0: typing.Any) -> str: ... -def accept_py_object(arg0: typing.Any) -> str: ... -def accept_set(arg0: set) -> None: ... -def add(arg0: int, arg1: int) -> int: ... -def default_custom_arg(foo: Foo = Foo(5)) -> None: ... -def default_int_arg(n: int = 5) -> None: ... -def default_list_arg(l: list = [1, 2, 6, 18]) -> None: ... -def default_optional_arg(n: int | None = None) -> None: ... -def func_w_anon_args(arg0: int, arg1: int, arg2: int) -> None: ... -def func_w_named_pos_args(x: int, y: int, z: int) -> None: ... -def generic(*args, **kwargs) -> None: ... -@typing.overload -def mul(x: int, y: int) -> int: - """ - Multiply x and y (int) - """ - -@typing.overload -def mul(p: float, q: float) -> float: - """ - Multiply p and q (double) - """ - -def nested_types(arg0: list[Foo] | Foo) -> list[Foo] | Foo: ... -def pass_callback(arg0: typing.Callable[[Foo], Foo]) -> Foo: ... -def passthrough1[T](obj: T) -> T: ... -@typing.overload -def passthrough2() -> None: ... -@typing.overload -def passthrough2[T](obj: T) -> T: ... -@typing.overload -def passthrough3() -> tuple[None, None]: ... -@typing.overload -def passthrough3[T](obj: T) -> tuple[T, None]: ... -@typing.overload -def passthrough3[T1, T2](obj1: T1, obj2: T2) -> tuple[T1, T2]: ... -def passthrough_backwards[T](obj: T) -> T: ... -def pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple: ... -def pos_kw_only_variadic_mix(i: int, /, j: int, *args, k: int, **kwargs) -> tuple: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/hidden_builtins.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/hidden_builtins.pyi deleted file mode 100644 index 52c09281..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/hidden_builtins.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import types - -__all__: list[str] = ["mapping_proxy", "none"] -mapping_proxy: types.MappingProxyType # value = mappingproxy({}) -none = None diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/issues.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/issues.pyi deleted file mode 100644 index a537a958..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/issues.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "backslashes_should_be_escaped", - "issue_51_catastrophic_regex", - "issue_73_utf8_doc_chars", - "issue_274_docstring_leading_newline", -] - -def backslashes_should_be_escaped() -> None: - """ - \\brief A brief description of this function. - - A detailed description of this function. - - Here's some reStructuredText: :math:`x = [x, y, \\theta]^T` - """ - -def issue_274_docstring_leading_newline() -> None: - """ - This is a docstring - """ - -def issue_51_catastrophic_regex(arg0: int, arg1: int) -> None: - """ - Use-case: - issue_51(os.get_handle_inheritable, os.set_handle_inheritable) - """ - -def issue_73_utf8_doc_chars() -> None: - """ - Construct a Ramsete unicycle controller. - - Tuning parameter (b > 0 rad²/m²) for which larger values make - - convergence more aggressive like a proportional term. - Tuning parameter (0 rad⁻¹ < zeta < 1 rad⁻¹) for which larger - values provide more damping in response. - """ - -_cleanup: typing.Any # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/methods.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/methods.pyi deleted file mode 100644 index 6c115cd1..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/methods.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["Dummy"] - -class Dummy: - @staticmethod - def static_method(arg0: int) -> int: ... - def regular_method(self, arg0: int) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/properties.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/properties.pyi deleted file mode 100644 index e47254e8..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/properties.pyi +++ /dev/null @@ -1,117 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "WithGetterSetterDoc", - "WithPropAndGetterSetterDoc", - "WithPropDoc", - "WithoutDoc", -] - -class WithoutDoc: - """ - No user docstring provided - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - def_property: int - def_readwrite: int - @property - def def_property_readonly(self) -> int: ... - @property - def def_readonly(self) -> int: ... - -class WithPropDoc: - """ - User docstring provided only to `def_` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - """ - prop doc token - """ - def_property_static: typing.ClassVar[int] = 0 - """ - prop doc token - """ - @property - def def_property(self) -> int: - """ - prop doc token - """ - @def_property.setter - def def_property(self, arg1: int) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ - @property - def def_readonly(self) -> int: - """ - prop doc token - """ - @property - def def_readwrite(self) -> int: - """ - prop doc token - """ - @def_readwrite.setter - def def_readwrite(self, arg0: int) -> None: ... - -class WithGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters, but NOT to `def_*(..., doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - """ - getter doc token - """ - def_property_static: typing.ClassVar[int] = 0 - """ - getter doc token - """ - @property - def def_property(self) -> int: - """ - getter doc token - """ - @def_property.setter - def def_property(self, arg1: int) -> None: - """ - setter doc token - """ - @property - def def_property_readonly(self) -> int: - """ - getter doc token - """ - -class WithPropAndGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters and to `def_*(, doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - """ - prop doc token - """ - def_property_static: typing.ClassVar[int] = 0 - """ - prop doc token - """ - @property - def def_property(self) -> int: - """ - prop doc token - """ - @def_property.setter - def def_property(self, arg1: int) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/stl.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/stl.pyi deleted file mode 100644 index e0faec55..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/stl.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -import typing - -import pybind11_stubgen.typing_ext - -__all__: list[str] = [ - "std_array", - "std_map", - "std_optional", - "std_variant", - "std_vector", -] - -def std_array( - arg0: typing.Annotated[list[int], pybind11_stubgen.typing_ext.FixedSize(3)], -) -> typing.Annotated[list[int], pybind11_stubgen.typing_ext.FixedSize(3)]: ... -def std_map() -> dict[int, complex]: ... -def std_optional(arg0: int | None) -> None: ... -def std_variant(arg0: int | float | tuple[int, int]) -> None: ... -def std_vector() -> list[tuple[int, float]]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/stl_bind.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/stl_bind.pyi deleted file mode 100644 index 2cac91aa..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/stl_bind.pyi +++ /dev/null @@ -1,124 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "MapStringComplex", - "VectorPairStringDouble", - "get_complex_map", - "get_vector_of_pairs", -] - -class VectorPairStringDouble: - __hash__: typing.ClassVar[None] = None - def __bool__(self) -> bool: - """ - Check whether the list is nonempty - """ - def __contains__(self, x: tuple[str, float]) -> bool: - """ - Return true the container contains ``x`` - """ - @typing.overload - def __delitem__(self, arg0: int) -> None: - """ - Delete the list elements at index ``i`` - """ - @typing.overload - def __delitem__(self, arg0: slice) -> None: - """ - Delete list elements using a slice object - """ - def __eq__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __getitem__(self, s: slice) -> VectorPairStringDouble: - """ - Retrieve list elements using a slice object - """ - @typing.overload - def __getitem__(self, arg0: int) -> tuple[str, float]: ... - @typing.overload - def __init__(self) -> None: ... - @typing.overload - def __init__(self, arg0: VectorPairStringDouble) -> None: - """ - Copy constructor - """ - @typing.overload - def __init__(self, arg0: typing.Iterable) -> None: ... - def __iter__(self) -> typing.Iterator[tuple[str, float]]: ... - def __len__(self) -> int: ... - def __ne__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __setitem__(self, arg0: int, arg1: tuple[str, float]) -> None: ... - @typing.overload - def __setitem__(self, arg0: slice, arg1: VectorPairStringDouble) -> None: - """ - Assign list elements using a slice object - """ - def append(self, x: tuple[str, float]) -> None: - """ - Add an item to the end of the list - """ - def clear(self) -> None: - """ - Clear the contents - """ - def count(self, x: tuple[str, float]) -> int: - """ - Return the number of times ``x`` appears in the list - """ - @typing.overload - def extend(self, L: VectorPairStringDouble) -> None: - """ - Extend the list by appending all the items in the given list - """ - @typing.overload - def extend(self, L: typing.Iterable) -> None: - """ - Extend the list by appending all the items in the given list - """ - def insert(self, i: int, x: tuple[str, float]) -> None: - """ - Insert an item at a given position. - """ - @typing.overload - def pop(self) -> tuple[str, float]: - """ - Remove and return the last item - """ - @typing.overload - def pop(self, i: int) -> tuple[str, float]: - """ - Remove and return the item at index ``i`` - """ - def remove(self, x: tuple[str, float]) -> None: - """ - Remove the first item from the list whose value is x. It is an error if there is no such item. - """ - -class MapStringComplex: - def __bool__(self) -> bool: - """ - Check whether the map is nonempty - """ - @typing.overload - def __contains__(self, arg0: str) -> bool: ... - @typing.overload - def __contains__(self, arg0: typing.Any) -> bool: ... - def __delitem__(self, arg0: str) -> None: ... - def __getitem__(self, arg0: str) -> complex: ... - def __init__(self) -> None: ... - def __iter__(self) -> typing.Iterator[str]: ... - def __len__(self) -> int: ... - def __repr__(self) -> str: - """ - Return the canonical string representation of this map. - """ - def __setitem__(self, arg0: str, arg1: complex) -> None: ... - def items(self) -> typing.ItemsView: ... - def keys(self) -> typing.KeysView: ... - def values(self) -> typing.ValuesView: ... - -def get_complex_map() -> MapStringComplex: ... -def get_vector_of_pairs() -> VectorPairStringDouble: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/typing.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/typing.pyi deleted file mode 100644 index f2c4ab95..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/typing.pyi +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import annotations - -import typing - -import typing_extensions - -__all__: list[str] = ["get_buffer", "get_sequence"] - -def get_buffer(arg0: typing_extensions.Buffer) -> typing_extensions.Buffer: ... -def get_sequence(arg0: typing.Sequence) -> typing.Sequence: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/values.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/values.pyi deleted file mode 100644 index 7c36d3fd..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/values.pyi +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -import datetime - -import numpy -from numpy import random - -__all__: list[str] = [ - "Dummy", - "Foo", - "add_day", - "foolist", - "foovar", - "list_with_none", - "none", - "random", - "t_10ms", - "t_20ns", - "t_30s", -] - -class Dummy: - linalg = numpy.linalg - -class Foo: - pass - -def add_day(arg0: datetime.datetime) -> datetime.datetime: ... - -foolist: list # value = [, ] -foovar: Foo # value = -list_with_none: list = [None, 2, {}] -none = None -t_10ms: datetime.timedelta # value = datetime.timedelta(microseconds=10000) -t_20ns: datetime.timedelta # value = datetime.timedelta(0) -t_30s: datetime.timedelta # value = datetime.timedelta(seconds=30) diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/core.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/core.pyi deleted file mode 100644 index 95cb8455..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/core.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/__init__.pyi deleted file mode 100644 index 10d343e0..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/__init__.pyi +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from . import classes, functions, functions_3_8_plus, functions_3_9_plus, values - -__all__: list[str] = [ - "classes", - "functions", - "functions_3_8_plus", - "functions_3_9_plus", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/classes.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/classes.pyi deleted file mode 100644 index 32aa068b..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/classes.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["A", "B", "C", "X"] - -class A: - """ - A - """ - -class B(A): - """ - B - """ - -class C(B): - """ - C - """ - -class X: - pass diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions.pyi deleted file mode 100644 index 5ed9ef3c..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions.pyi +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import annotations - -import sys as sys -import typing as typing - -from demo.pure_python.functions_3_8_plus import args_mix -from demo.pure_python.functions_3_9_plus import generic_alias_annotation - -__all__: list[str] = [ - "accept_frozenset", - "args_mix", - "builtin_function_as_default_arg", - "function_as_default_arg", - "generic_alias_annotation", - "lambda_as_default_arg", - "search", - "static_method_as_default_arg", - "sys", - "typing", -] - -class _Dummy: - @staticmethod - def foo(): ... - -def accept_frozenset(arg: frozenset[int | float]) -> int | None: ... -def builtin_function_as_default_arg(func: type(len) = len): ... -def function_as_default_arg(func: type(search) = search): ... -def lambda_as_default_arg(callback=...): ... -def search(a: int, b: list[int]) -> int: ... -def static_method_as_default_arg(callback=_Dummy.foo): ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions_3_8_plus.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions_3_8_plus.pyi deleted file mode 100644 index c55aed1d..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions_3_8_plus.pyi +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import typing as typing - -__all__: list[str] = [ - "Expression", - "Token", - "args_mix", - "nested_current_module_annotations", - "typing", -] - -class Token: - pass - -class Expression: - pass - -def args_mix( - a: int, - b: float = 0.5, - c: str = "", - *args: int, - x: int = 1, - y=int, - **kwargs: dict[int, str], -): ... -def nested_current_module_annotations( - tokens: list[Token], expr: Expression | None = None -) -> dict[str, Expression]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions_3_9_plus.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions_3_9_plus.pyi deleted file mode 100644 index 59b9b80c..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/functions_3_9_plus.pyi +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["generic_alias_annotation"] - -def generic_alias_annotation(a: list[tuple[int]], b: dict[int, str]) -> list[float]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/values.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/values.pyi deleted file mode 100644 index 07913391..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/pure_python/values.pyi +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["callables_dict"] -callables_dict: dict = {"len": len, "int": int} diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/__init__.pyi deleted file mode 100644 index 9f27c427..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/__init__.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -from . import _bindings, core, pure_python - -__all__: list[str] = [ - "aliases", - "classes", - "core", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "pure_python", - "stl", - "stl_bind", - "typing", - "values", - "version", -] -version: str = "0.0.0" diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi deleted file mode 100644 index 9ea9beb4..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from . import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi deleted file mode 100644 index d8c8d8ac..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.enum -import numpy -from demo._bindings.aliases.foreign_method_arg import Bar2 as foreign_type_alias -from demo._bindings.aliases.foreign_return import get_foo as foreign_class_alias -from numpy import random - -from . import ( - foreign_arg, - foreign_attr, - foreign_class_member, - foreign_method_arg, - foreign_method_return, - foreign_return, - missing_self_arg, -) - -__all__: list[str] = [ - "Color", - "Dummy", - "foreign_arg", - "foreign_attr", - "foreign_class_alias", - "foreign_class_member", - "foreign_enum_default", - "foreign_method_arg", - "foreign_method_return", - "foreign_return", - "foreign_type_alias", - "func", - "local_func_alias", - "local_type_alias", - "missing_self_arg", - "random", -] - -class Dummy: - linalg = numpy.linalg - -class Color: - pass - -def foreign_enum_default( - color: typing.Any = demo._bindings.enum.ConsoleForegroundColor.Blue, -) -> None: ... -def func(arg0: int) -> int: ... - -local_type_alias = Color -local_func_alias = func diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi deleted file mode 100644 index de75e6fb..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["set_foo"] - -def set_foo(arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi deleted file mode 100644 index 10c4a712..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["value"] -value: demo._bindings.classes.Foo # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi deleted file mode 100644 index 497a4e2a..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.classes - -__all__: list[str] = ["Bar1"] - -class Bar1: - foo: typing.ClassVar[ - demo._bindings.classes.Foo - ] # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi deleted file mode 100644 index 8d4ed4ac..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar2"] - -class Bar2: - def set_foo(self, arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi deleted file mode 100644 index 95d218e1..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar3"] - -class Bar3: - @staticmethod - def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi deleted file mode 100644 index 7b5b428f..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["get_foo"] - -def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi deleted file mode 100644 index ee1a09ee..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar4"] - -class Bar4: - def set_foo(self: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi deleted file mode 100644 index 73c6e950..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "CppException", - "Derived", - "Foo", - "MyBase", - "Outer", - "ParIter", - "ParIterBase", - "ParticleContainer", -] - -class Outer: - class Inner: - class NestedEnum: - """ - Members: - - ONE - - TWO - """ - - ONE: typing.ClassVar[Outer.Inner.NestedEnum] # value = - TWO: typing.ClassVar[Outer.Inner.NestedEnum] # value = - __members__: typing.ClassVar[ - dict[str, Outer.Inner.NestedEnum] - ] # value = {'ONE': , 'TWO': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - - value: Outer.Inner.NestedEnum - - inner: Outer.Inner - -class MyBase: - class Inner: - pass - - name: str - -class Derived(MyBase): - count: int - -class Foo: - class FooChild: - def __init__(self) -> None: ... - def g(self) -> None: ... - - def __init__(self) -> None: ... - def f(self) -> None: ... - -class ParIterBase: - level: int - -class ParIter(ParIterBase): - def __init__(self, particle_container: ParticleContainer, level: int) -> None: ... - -class ParticleContainer: - name: str - Iterator = ParIter - def process(self, arg0: ParIter) -> None: ... - -class CppException(Exception): - pass diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi deleted file mode 100644 index 795a711b..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi +++ /dev/null @@ -1,101 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy -import scipy.sparse - -import pybind11_stubgen.typing_ext - -__all__: list[str] = [ - "accept_matrix_int", - "accept_vector_float64", - "dense_matrix_c", - "dense_matrix_r", - "fixed_mutator_a", - "fixed_mutator_c", - "fixed_mutator_r", - "four_col_matrix_r", - "four_row_matrix_r", - "get_matrix_int", - "get_vector_float64", - "sparse_matrix_c", - "sparse_matrix_r", -] - -def accept_matrix_int( - arg0: typing.Annotated[ - numpy.ndarray, numpy.int32, pybind11_stubgen.typing_ext.FixedSize(3, 3) - ], -) -> None: ... -def accept_vector_float64( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float64, pybind11_stubgen.typing_ext.FixedSize(3, 1) - ], -) -> None: ... -def dense_matrix_c( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") -]: ... -def dense_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") -]: ... -def fixed_mutator_a( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - ], -) -> None: ... -def fixed_mutator_c( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - numpy.ndarray.flags.f_contiguous, - ], -) -> None: ... -def fixed_mutator_r( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - numpy.ndarray.flags.c_contiguous, - ], -) -> None: ... -def four_col_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", 4) - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", 4) -]: ... -def four_row_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize(4, "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize(4, "n") -]: ... -def get_matrix_int() -> typing.Annotated[ - numpy.ndarray, numpy.int32, pybind11_stubgen.typing_ext.FixedSize(3, 3) -]: ... -def get_vector_float64() -> typing.Annotated[ - numpy.ndarray, numpy.float64, pybind11_stubgen.typing_ext.FixedSize(3, 1) -]: ... -def sparse_matrix_c( - arg0: typing.Annotated[scipy.sparse.csc_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csc_matrix, numpy.float32]: ... -def sparse_matrix_r( - arg0: typing.Annotated[scipy.sparse.csr_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csr_matrix, numpy.float32]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi deleted file mode 100644 index 57aba5b9..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Blue", - "ConsoleForegroundColor", - "Green", - "Magenta", - "None_", - "Yellow", - "accept_defaulted_enum", -] - -class ConsoleForegroundColor: - """ - Members: - - Green - - Yellow - - Blue - - Magenta - - None_ - """ - - Blue: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Green: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Magenta: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - None_: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Yellow: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - __members__: typing.ClassVar[ - dict[str, ConsoleForegroundColor] - ] # value = {'Green': , 'Yellow': , 'Blue': , 'Magenta': , 'None_': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - -def accept_defaulted_enum( - color: ConsoleForegroundColor = ConsoleForegroundColor.None_, -) -> None: ... - -Blue: ConsoleForegroundColor # value = -Green: ConsoleForegroundColor # value = -Magenta: ConsoleForegroundColor # value = -None_: ConsoleForegroundColor # value = -Yellow: ConsoleForegroundColor # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi deleted file mode 100644 index 96774842..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = [ - "Enum", - "Unbound", - "accept_unbound_enum", - "accept_unbound_enum_defaulted", - "accept_unbound_type", - "accept_unbound_type_defaulted", - "get_unbound_type", -] - -class Unbound: - pass - -class Enum: - pass - -def accept_unbound_enum(arg0: ...) -> int: ... -def accept_unbound_enum_defaulted(x: Enum = ...) -> int: ... -def accept_unbound_type(arg0: tuple[..., int]) -> int: ... -def accept_unbound_type_defaulted(x: Unbound = ...) -> int: ... -def get_unbound_type() -> ...: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi deleted file mode 100644 index bb839212..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi +++ /dev/null @@ -1,76 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Foo", - "accept_annotated_callable", - "accept_callable", - "accept_frozenset", - "accept_py_handle", - "accept_py_object", - "accept_set", - "add", - "default_custom_arg", - "default_int_arg", - "default_list_arg", - "default_optional_arg", - "func_w_anon_args", - "func_w_named_pos_args", - "generic", - "mul", - "nested_types", - "pass_callback", - "passthrough1", - "passthrough2", - "passthrough3", - "passthrough_backwards", - "pos_kw_only_mix", - "pos_kw_only_variadic_mix", -] - -class Foo: - def __init__(self, arg0: int) -> None: ... - -def accept_annotated_callable(arg0: typing.Callable[[int, int], int]) -> typing.Any: ... -def accept_callable(arg0: typing.Callable) -> typing.Any: ... -def accept_frozenset(arg0: frozenset) -> None: ... -def accept_py_handle(arg0: typing.Any) -> str: ... -def accept_py_object(arg0: typing.Any) -> str: ... -def accept_set(arg0: set) -> None: ... -def add(arg0: int, arg1: int) -> int: ... -def default_custom_arg(foo: Foo = Foo(5)) -> None: ... -def default_int_arg(n: int = 5) -> None: ... -def default_list_arg(l: list = [1, 2, 6, 18]) -> None: ... -def default_optional_arg(n: int | None = None) -> None: ... -def func_w_anon_args(arg0: int, arg1: int, arg2: int) -> None: ... -def func_w_named_pos_args(x: int, y: int, z: int) -> None: ... -def generic(*args, **kwargs) -> None: ... -@typing.overload -def mul(x: int, y: int) -> int: - """ - Multiply x and y (int) - """ - -@typing.overload -def mul(p: float, q: float) -> float: - """ - Multiply p and q (double) - """ - -def nested_types(arg0: list[Foo] | Foo) -> list[Foo] | Foo: ... -def pass_callback(arg0: typing.Callable[[Foo], Foo]) -> Foo: ... -def passthrough1[T](obj: T) -> T: ... -@typing.overload -def passthrough2() -> None: ... -@typing.overload -def passthrough2[T](obj: T) -> T: ... -@typing.overload -def passthrough3() -> tuple[None, None]: ... -@typing.overload -def passthrough3[T](obj: T) -> tuple[T, None]: ... -@typing.overload -def passthrough3[T1, T2](obj1: T1, obj2: T2) -> tuple[T1, T2]: ... -def passthrough_backwards[T](obj: T) -> T: ... -def pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple: ... -def pos_kw_only_variadic_mix(i: int, /, j: int, *args, k: int, **kwargs) -> tuple: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi deleted file mode 100644 index 52c09281..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import types - -__all__: list[str] = ["mapping_proxy", "none"] -mapping_proxy: types.MappingProxyType # value = mappingproxy({}) -none = None diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi deleted file mode 100644 index a537a958..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "backslashes_should_be_escaped", - "issue_51_catastrophic_regex", - "issue_73_utf8_doc_chars", - "issue_274_docstring_leading_newline", -] - -def backslashes_should_be_escaped() -> None: - """ - \\brief A brief description of this function. - - A detailed description of this function. - - Here's some reStructuredText: :math:`x = [x, y, \\theta]^T` - """ - -def issue_274_docstring_leading_newline() -> None: - """ - This is a docstring - """ - -def issue_51_catastrophic_regex(arg0: int, arg1: int) -> None: - """ - Use-case: - issue_51(os.get_handle_inheritable, os.set_handle_inheritable) - """ - -def issue_73_utf8_doc_chars() -> None: - """ - Construct a Ramsete unicycle controller. - - Tuning parameter (b > 0 rad²/m²) for which larger values make - - convergence more aggressive like a proportional term. - Tuning parameter (0 rad⁻¹ < zeta < 1 rad⁻¹) for which larger - values provide more damping in response. - """ - -_cleanup: typing.Any # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi deleted file mode 100644 index 6c115cd1..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["Dummy"] - -class Dummy: - @staticmethod - def static_method(arg0: int) -> int: ... - def regular_method(self, arg0: int) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi deleted file mode 100644 index 34943048..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy - -__all__: list[str] = [ - "accept_ndarray_float64", - "accept_ndarray_int", - "get_ndarray_float64", - "get_ndarray_int", - "return_dtype", -] - -def accept_ndarray_float64( - arg0: typing.Annotated[numpy.ndarray, numpy.float64], -) -> None: ... -def accept_ndarray_int(arg0: typing.Annotated[numpy.ndarray, numpy.int32]) -> None: ... -def get_ndarray_float64() -> typing.Annotated[numpy.ndarray, numpy.float64]: ... -def get_ndarray_int() -> typing.Annotated[numpy.ndarray, numpy.int32]: ... -def return_dtype() -> numpy.dtype[typing.Any]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi deleted file mode 100644 index e47254e8..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi +++ /dev/null @@ -1,117 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "WithGetterSetterDoc", - "WithPropAndGetterSetterDoc", - "WithPropDoc", - "WithoutDoc", -] - -class WithoutDoc: - """ - No user docstring provided - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - def_property: int - def_readwrite: int - @property - def def_property_readonly(self) -> int: ... - @property - def def_readonly(self) -> int: ... - -class WithPropDoc: - """ - User docstring provided only to `def_` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - """ - prop doc token - """ - def_property_static: typing.ClassVar[int] = 0 - """ - prop doc token - """ - @property - def def_property(self) -> int: - """ - prop doc token - """ - @def_property.setter - def def_property(self, arg1: int) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ - @property - def def_readonly(self) -> int: - """ - prop doc token - """ - @property - def def_readwrite(self) -> int: - """ - prop doc token - """ - @def_readwrite.setter - def def_readwrite(self, arg0: int) -> None: ... - -class WithGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters, but NOT to `def_*(..., doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - """ - getter doc token - """ - def_property_static: typing.ClassVar[int] = 0 - """ - getter doc token - """ - @property - def def_property(self) -> int: - """ - getter doc token - """ - @def_property.setter - def def_property(self, arg1: int) -> None: - """ - setter doc token - """ - @property - def def_property_readonly(self) -> int: - """ - getter doc token - """ - -class WithPropAndGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters and to `def_*(, doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - """ - prop doc token - """ - def_property_static: typing.ClassVar[int] = 0 - """ - prop doc token - """ - @property - def def_property(self) -> int: - """ - prop doc token - """ - @def_property.setter - def def_property(self, arg1: int) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi deleted file mode 100644 index e0faec55..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -import typing - -import pybind11_stubgen.typing_ext - -__all__: list[str] = [ - "std_array", - "std_map", - "std_optional", - "std_variant", - "std_vector", -] - -def std_array( - arg0: typing.Annotated[list[int], pybind11_stubgen.typing_ext.FixedSize(3)], -) -> typing.Annotated[list[int], pybind11_stubgen.typing_ext.FixedSize(3)]: ... -def std_map() -> dict[int, complex]: ... -def std_optional(arg0: int | None) -> None: ... -def std_variant(arg0: int | float | tuple[int, int]) -> None: ... -def std_vector() -> list[tuple[int, float]]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi deleted file mode 100644 index 2cac91aa..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi +++ /dev/null @@ -1,124 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "MapStringComplex", - "VectorPairStringDouble", - "get_complex_map", - "get_vector_of_pairs", -] - -class VectorPairStringDouble: - __hash__: typing.ClassVar[None] = None - def __bool__(self) -> bool: - """ - Check whether the list is nonempty - """ - def __contains__(self, x: tuple[str, float]) -> bool: - """ - Return true the container contains ``x`` - """ - @typing.overload - def __delitem__(self, arg0: int) -> None: - """ - Delete the list elements at index ``i`` - """ - @typing.overload - def __delitem__(self, arg0: slice) -> None: - """ - Delete list elements using a slice object - """ - def __eq__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __getitem__(self, s: slice) -> VectorPairStringDouble: - """ - Retrieve list elements using a slice object - """ - @typing.overload - def __getitem__(self, arg0: int) -> tuple[str, float]: ... - @typing.overload - def __init__(self) -> None: ... - @typing.overload - def __init__(self, arg0: VectorPairStringDouble) -> None: - """ - Copy constructor - """ - @typing.overload - def __init__(self, arg0: typing.Iterable) -> None: ... - def __iter__(self) -> typing.Iterator[tuple[str, float]]: ... - def __len__(self) -> int: ... - def __ne__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __setitem__(self, arg0: int, arg1: tuple[str, float]) -> None: ... - @typing.overload - def __setitem__(self, arg0: slice, arg1: VectorPairStringDouble) -> None: - """ - Assign list elements using a slice object - """ - def append(self, x: tuple[str, float]) -> None: - """ - Add an item to the end of the list - """ - def clear(self) -> None: - """ - Clear the contents - """ - def count(self, x: tuple[str, float]) -> int: - """ - Return the number of times ``x`` appears in the list - """ - @typing.overload - def extend(self, L: VectorPairStringDouble) -> None: - """ - Extend the list by appending all the items in the given list - """ - @typing.overload - def extend(self, L: typing.Iterable) -> None: - """ - Extend the list by appending all the items in the given list - """ - def insert(self, i: int, x: tuple[str, float]) -> None: - """ - Insert an item at a given position. - """ - @typing.overload - def pop(self) -> tuple[str, float]: - """ - Remove and return the last item - """ - @typing.overload - def pop(self, i: int) -> tuple[str, float]: - """ - Remove and return the item at index ``i`` - """ - def remove(self, x: tuple[str, float]) -> None: - """ - Remove the first item from the list whose value is x. It is an error if there is no such item. - """ - -class MapStringComplex: - def __bool__(self) -> bool: - """ - Check whether the map is nonempty - """ - @typing.overload - def __contains__(self, arg0: str) -> bool: ... - @typing.overload - def __contains__(self, arg0: typing.Any) -> bool: ... - def __delitem__(self, arg0: str) -> None: ... - def __getitem__(self, arg0: str) -> complex: ... - def __init__(self) -> None: ... - def __iter__(self) -> typing.Iterator[str]: ... - def __len__(self) -> int: ... - def __repr__(self) -> str: - """ - Return the canonical string representation of this map. - """ - def __setitem__(self, arg0: str, arg1: complex) -> None: ... - def items(self) -> typing.ItemsView: ... - def keys(self) -> typing.KeysView: ... - def values(self) -> typing.ValuesView: ... - -def get_complex_map() -> MapStringComplex: ... -def get_vector_of_pairs() -> VectorPairStringDouble: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi deleted file mode 100644 index f2c4ab95..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import annotations - -import typing - -import typing_extensions - -__all__: list[str] = ["get_buffer", "get_sequence"] - -def get_buffer(arg0: typing_extensions.Buffer) -> typing_extensions.Buffer: ... -def get_sequence(arg0: typing.Sequence) -> typing.Sequence: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi deleted file mode 100644 index 7c36d3fd..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -import datetime - -import numpy -from numpy import random - -__all__: list[str] = [ - "Dummy", - "Foo", - "add_day", - "foolist", - "foovar", - "list_with_none", - "none", - "random", - "t_10ms", - "t_20ns", - "t_30s", -] - -class Dummy: - linalg = numpy.linalg - -class Foo: - pass - -def add_day(arg0: datetime.datetime) -> datetime.datetime: ... - -foolist: list # value = [, ] -foovar: Foo # value = -list_with_none: list = [None, 2, {}] -none = None -t_10ms: datetime.timedelta # value = datetime.timedelta(microseconds=10000) -t_20ns: datetime.timedelta # value = datetime.timedelta(0) -t_30s: datetime.timedelta # value = datetime.timedelta(seconds=30) diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/core.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/core.pyi deleted file mode 100644 index 95cb8455..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/core.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi deleted file mode 100644 index 10d343e0..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from . import classes, functions, functions_3_8_plus, functions_3_9_plus, values - -__all__: list[str] = [ - "classes", - "functions", - "functions_3_8_plus", - "functions_3_9_plus", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi deleted file mode 100644 index 32aa068b..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["A", "B", "C", "X"] - -class A: - """ - A - """ - -class B(A): - """ - B - """ - -class C(B): - """ - C - """ - -class X: - pass diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi deleted file mode 100644 index 5ed9ef3c..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import annotations - -import sys as sys -import typing as typing - -from demo.pure_python.functions_3_8_plus import args_mix -from demo.pure_python.functions_3_9_plus import generic_alias_annotation - -__all__: list[str] = [ - "accept_frozenset", - "args_mix", - "builtin_function_as_default_arg", - "function_as_default_arg", - "generic_alias_annotation", - "lambda_as_default_arg", - "search", - "static_method_as_default_arg", - "sys", - "typing", -] - -class _Dummy: - @staticmethod - def foo(): ... - -def accept_frozenset(arg: frozenset[int | float]) -> int | None: ... -def builtin_function_as_default_arg(func: type(len) = len): ... -def function_as_default_arg(func: type(search) = search): ... -def lambda_as_default_arg(callback=...): ... -def search(a: int, b: list[int]) -> int: ... -def static_method_as_default_arg(callback=_Dummy.foo): ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi deleted file mode 100644 index c55aed1d..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import typing as typing - -__all__: list[str] = [ - "Expression", - "Token", - "args_mix", - "nested_current_module_annotations", - "typing", -] - -class Token: - pass - -class Expression: - pass - -def args_mix( - a: int, - b: float = 0.5, - c: str = "", - *args: int, - x: int = 1, - y=int, - **kwargs: dict[int, str], -): ... -def nested_current_module_annotations( - tokens: list[Token], expr: Expression | None = None -) -> dict[str, Expression]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi deleted file mode 100644 index 59b9b80c..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["generic_alias_annotation"] - -def generic_alias_annotation(a: list[tuple[int]], b: dict[int, str]) -> list[float]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi deleted file mode 100644 index 07913391..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["callables_dict"] -callables_dict: dict = {"len": len, "int": int} diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/__init__.pyi deleted file mode 100644 index 9f27c427..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/__init__.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -from . import _bindings, core, pure_python - -__all__: list[str] = [ - "aliases", - "classes", - "core", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "pure_python", - "stl", - "stl_bind", - "typing", - "values", - "version", -] -version: str = "0.0.0" diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi deleted file mode 100644 index 9ea9beb4..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from . import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi deleted file mode 100644 index d8c8d8ac..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.enum -import numpy -from demo._bindings.aliases.foreign_method_arg import Bar2 as foreign_type_alias -from demo._bindings.aliases.foreign_return import get_foo as foreign_class_alias -from numpy import random - -from . import ( - foreign_arg, - foreign_attr, - foreign_class_member, - foreign_method_arg, - foreign_method_return, - foreign_return, - missing_self_arg, -) - -__all__: list[str] = [ - "Color", - "Dummy", - "foreign_arg", - "foreign_attr", - "foreign_class_alias", - "foreign_class_member", - "foreign_enum_default", - "foreign_method_arg", - "foreign_method_return", - "foreign_return", - "foreign_type_alias", - "func", - "local_func_alias", - "local_type_alias", - "missing_self_arg", - "random", -] - -class Dummy: - linalg = numpy.linalg - -class Color: - pass - -def foreign_enum_default( - color: typing.Any = demo._bindings.enum.ConsoleForegroundColor.Blue, -) -> None: ... -def func(arg0: int) -> int: ... - -local_type_alias = Color -local_func_alias = func diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi deleted file mode 100644 index de75e6fb..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["set_foo"] - -def set_foo(arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi deleted file mode 100644 index 10c4a712..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["value"] -value: demo._bindings.classes.Foo # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi deleted file mode 100644 index 497a4e2a..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.classes - -__all__: list[str] = ["Bar1"] - -class Bar1: - foo: typing.ClassVar[ - demo._bindings.classes.Foo - ] # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi deleted file mode 100644 index 8d4ed4ac..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar2"] - -class Bar2: - def set_foo(self, arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi deleted file mode 100644 index 95d218e1..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar3"] - -class Bar3: - @staticmethod - def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi deleted file mode 100644 index 7b5b428f..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["get_foo"] - -def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi deleted file mode 100644 index ee1a09ee..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar4"] - -class Bar4: - def set_foo(self: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi deleted file mode 100644 index 73c6e950..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "CppException", - "Derived", - "Foo", - "MyBase", - "Outer", - "ParIter", - "ParIterBase", - "ParticleContainer", -] - -class Outer: - class Inner: - class NestedEnum: - """ - Members: - - ONE - - TWO - """ - - ONE: typing.ClassVar[Outer.Inner.NestedEnum] # value = - TWO: typing.ClassVar[Outer.Inner.NestedEnum] # value = - __members__: typing.ClassVar[ - dict[str, Outer.Inner.NestedEnum] - ] # value = {'ONE': , 'TWO': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - - value: Outer.Inner.NestedEnum - - inner: Outer.Inner - -class MyBase: - class Inner: - pass - - name: str - -class Derived(MyBase): - count: int - -class Foo: - class FooChild: - def __init__(self) -> None: ... - def g(self) -> None: ... - - def __init__(self) -> None: ... - def f(self) -> None: ... - -class ParIterBase: - level: int - -class ParIter(ParIterBase): - def __init__(self, particle_container: ParticleContainer, level: int) -> None: ... - -class ParticleContainer: - name: str - Iterator = ParIter - def process(self, arg0: ParIter) -> None: ... - -class CppException(Exception): - pass diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi deleted file mode 100644 index 795a711b..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi +++ /dev/null @@ -1,101 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy -import scipy.sparse - -import pybind11_stubgen.typing_ext - -__all__: list[str] = [ - "accept_matrix_int", - "accept_vector_float64", - "dense_matrix_c", - "dense_matrix_r", - "fixed_mutator_a", - "fixed_mutator_c", - "fixed_mutator_r", - "four_col_matrix_r", - "four_row_matrix_r", - "get_matrix_int", - "get_vector_float64", - "sparse_matrix_c", - "sparse_matrix_r", -] - -def accept_matrix_int( - arg0: typing.Annotated[ - numpy.ndarray, numpy.int32, pybind11_stubgen.typing_ext.FixedSize(3, 3) - ], -) -> None: ... -def accept_vector_float64( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float64, pybind11_stubgen.typing_ext.FixedSize(3, 1) - ], -) -> None: ... -def dense_matrix_c( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") -]: ... -def dense_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", "n") -]: ... -def fixed_mutator_a( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - ], -) -> None: ... -def fixed_mutator_c( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - numpy.ndarray.flags.f_contiguous, - ], -) -> None: ... -def fixed_mutator_r( - arg0: typing.Annotated[ - numpy.ndarray, - numpy.float32, - pybind11_stubgen.typing_ext.FixedSize(5, 6), - numpy.ndarray.flags.writeable, - numpy.ndarray.flags.c_contiguous, - ], -) -> None: ... -def four_col_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", 4) - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize("m", 4) -]: ... -def four_row_matrix_r( - arg0: typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize(4, "n") - ], -) -> typing.Annotated[ - numpy.ndarray, numpy.float32, pybind11_stubgen.typing_ext.DynamicSize(4, "n") -]: ... -def get_matrix_int() -> typing.Annotated[ - numpy.ndarray, numpy.int32, pybind11_stubgen.typing_ext.FixedSize(3, 3) -]: ... -def get_vector_float64() -> typing.Annotated[ - numpy.ndarray, numpy.float64, pybind11_stubgen.typing_ext.FixedSize(3, 1) -]: ... -def sparse_matrix_c( - arg0: typing.Annotated[scipy.sparse.csc_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csc_matrix, numpy.float32]: ... -def sparse_matrix_r( - arg0: typing.Annotated[scipy.sparse.csr_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csr_matrix, numpy.float32]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi deleted file mode 100644 index 57aba5b9..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Blue", - "ConsoleForegroundColor", - "Green", - "Magenta", - "None_", - "Yellow", - "accept_defaulted_enum", -] - -class ConsoleForegroundColor: - """ - Members: - - Green - - Yellow - - Blue - - Magenta - - None_ - """ - - Blue: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Green: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Magenta: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - None_: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Yellow: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - __members__: typing.ClassVar[ - dict[str, ConsoleForegroundColor] - ] # value = {'Green': , 'Yellow': , 'Blue': , 'Magenta': , 'None_': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: int) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__(self, state: int) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - -def accept_defaulted_enum( - color: ConsoleForegroundColor = ConsoleForegroundColor.None_, -) -> None: ... - -Blue: ConsoleForegroundColor # value = -Green: ConsoleForegroundColor # value = -Magenta: ConsoleForegroundColor # value = -None_: ConsoleForegroundColor # value = -Yellow: ConsoleForegroundColor # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi deleted file mode 100644 index 96774842..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = [ - "Enum", - "Unbound", - "accept_unbound_enum", - "accept_unbound_enum_defaulted", - "accept_unbound_type", - "accept_unbound_type_defaulted", - "get_unbound_type", -] - -class Unbound: - pass - -class Enum: - pass - -def accept_unbound_enum(arg0: ...) -> int: ... -def accept_unbound_enum_defaulted(x: Enum = ...) -> int: ... -def accept_unbound_type(arg0: tuple[..., int]) -> int: ... -def accept_unbound_type_defaulted(x: Unbound = ...) -> int: ... -def get_unbound_type() -> ...: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi deleted file mode 100644 index 52c09281..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import types - -__all__: list[str] = ["mapping_proxy", "none"] -mapping_proxy: types.MappingProxyType # value = mappingproxy({}) -none = None diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi deleted file mode 100644 index a537a958..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "backslashes_should_be_escaped", - "issue_51_catastrophic_regex", - "issue_73_utf8_doc_chars", - "issue_274_docstring_leading_newline", -] - -def backslashes_should_be_escaped() -> None: - """ - \\brief A brief description of this function. - - A detailed description of this function. - - Here's some reStructuredText: :math:`x = [x, y, \\theta]^T` - """ - -def issue_274_docstring_leading_newline() -> None: - """ - This is a docstring - """ - -def issue_51_catastrophic_regex(arg0: int, arg1: int) -> None: - """ - Use-case: - issue_51(os.get_handle_inheritable, os.set_handle_inheritable) - """ - -def issue_73_utf8_doc_chars() -> None: - """ - Construct a Ramsete unicycle controller. - - Tuning parameter (b > 0 rad²/m²) for which larger values make - - convergence more aggressive like a proportional term. - Tuning parameter (0 rad⁻¹ < zeta < 1 rad⁻¹) for which larger - values provide more damping in response. - """ - -_cleanup: typing.Any # value = diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi deleted file mode 100644 index 6c115cd1..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["Dummy"] - -class Dummy: - @staticmethod - def static_method(arg0: int) -> int: ... - def regular_method(self, arg0: int) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi deleted file mode 100644 index 34943048..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy - -__all__: list[str] = [ - "accept_ndarray_float64", - "accept_ndarray_int", - "get_ndarray_float64", - "get_ndarray_int", - "return_dtype", -] - -def accept_ndarray_float64( - arg0: typing.Annotated[numpy.ndarray, numpy.float64], -) -> None: ... -def accept_ndarray_int(arg0: typing.Annotated[numpy.ndarray, numpy.int32]) -> None: ... -def get_ndarray_float64() -> typing.Annotated[numpy.ndarray, numpy.float64]: ... -def get_ndarray_int() -> typing.Annotated[numpy.ndarray, numpy.int32]: ... -def return_dtype() -> numpy.dtype[typing.Any]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi deleted file mode 100644 index f19453b4..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi +++ /dev/null @@ -1,99 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "WithGetterSetterDoc", - "WithPropAndGetterSetterDoc", - "WithPropDoc", - "WithoutDoc", -] - -class WithoutDoc: - """ - No user docstring provided - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - def_property: int - def_readwrite: int - @property - def def_property_readonly(self) -> int: ... - @property - def def_readonly(self) -> int: ... - -class WithPropDoc: - """ - User docstring provided only to `def_` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - prop doc token - """ - @def_property.setter - def def_property(self, arg1: int) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ - @property - def def_readonly(self) -> int: - """ - prop doc token - """ - @property - def def_readwrite(self) -> int: - """ - prop doc token - """ - @def_readwrite.setter - def def_readwrite(self, arg0: int) -> None: ... - -class WithGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters, but NOT to `def_*(..., doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - getter doc token - """ - @def_property.setter - def def_property(self, arg1: int) -> None: - """ - setter doc token - """ - @property - def def_property_readonly(self) -> int: - """ - getter doc token - """ - -class WithPropAndGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters and to `def_*(, doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: - """ - prop doc token - """ - @def_property.setter - def def_property(self, arg1: int) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi deleted file mode 100644 index f2c4ab95..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import annotations - -import typing - -import typing_extensions - -__all__: list[str] = ["get_buffer", "get_sequence"] - -def get_buffer(arg0: typing_extensions.Buffer) -> typing_extensions.Buffer: ... -def get_sequence(arg0: typing.Sequence) -> typing.Sequence: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi deleted file mode 100644 index 7c36d3fd..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -import datetime - -import numpy -from numpy import random - -__all__: list[str] = [ - "Dummy", - "Foo", - "add_day", - "foolist", - "foovar", - "list_with_none", - "none", - "random", - "t_10ms", - "t_20ns", - "t_30s", -] - -class Dummy: - linalg = numpy.linalg - -class Foo: - pass - -def add_day(arg0: datetime.datetime) -> datetime.datetime: ... - -foolist: list # value = [, ] -foovar: Foo # value = -list_with_none: list = [None, 2, {}] -none = None -t_10ms: datetime.timedelta # value = datetime.timedelta(microseconds=10000) -t_20ns: datetime.timedelta # value = datetime.timedelta(0) -t_30s: datetime.timedelta # value = datetime.timedelta(seconds=30) diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/core.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/core.pyi deleted file mode 100644 index 95cb8455..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/core.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi deleted file mode 100644 index 10d343e0..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from . import classes, functions, functions_3_8_plus, functions_3_9_plus, values - -__all__: list[str] = [ - "classes", - "functions", - "functions_3_8_plus", - "functions_3_9_plus", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi deleted file mode 100644 index 32aa068b..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["A", "B", "C", "X"] - -class A: - """ - A - """ - -class B(A): - """ - B - """ - -class C(B): - """ - C - """ - -class X: - pass diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi deleted file mode 100644 index 5ed9ef3c..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import annotations - -import sys as sys -import typing as typing - -from demo.pure_python.functions_3_8_plus import args_mix -from demo.pure_python.functions_3_9_plus import generic_alias_annotation - -__all__: list[str] = [ - "accept_frozenset", - "args_mix", - "builtin_function_as_default_arg", - "function_as_default_arg", - "generic_alias_annotation", - "lambda_as_default_arg", - "search", - "static_method_as_default_arg", - "sys", - "typing", -] - -class _Dummy: - @staticmethod - def foo(): ... - -def accept_frozenset(arg: frozenset[int | float]) -> int | None: ... -def builtin_function_as_default_arg(func: type(len) = len): ... -def function_as_default_arg(func: type(search) = search): ... -def lambda_as_default_arg(callback=...): ... -def search(a: int, b: list[int]) -> int: ... -def static_method_as_default_arg(callback=_Dummy.foo): ... diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi deleted file mode 100644 index c55aed1d..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import typing as typing - -__all__: list[str] = [ - "Expression", - "Token", - "args_mix", - "nested_current_module_annotations", - "typing", -] - -class Token: - pass - -class Expression: - pass - -def args_mix( - a: int, - b: float = 0.5, - c: str = "", - *args: int, - x: int = 1, - y=int, - **kwargs: dict[int, str], -): ... -def nested_current_module_annotations( - tokens: list[Token], expr: Expression | None = None -) -> dict[str, Expression]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi deleted file mode 100644 index 59b9b80c..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["generic_alias_annotation"] - -def generic_alias_annotation(a: list[tuple[int]], b: dict[int, str]) -> list[float]: ... diff --git a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi b/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi deleted file mode 100644 index 07913391..00000000 --- a/tests/stubs/python-3.12/pybind11-v2.9/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["callables_dict"] -callables_dict: dict = {"len": len, "int": int} diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/__init__.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/__init__.pyi deleted file mode 100644 index 9f27c427..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/__init__.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -from . import _bindings, core, pure_python - -__all__: list[str] = [ - "aliases", - "classes", - "core", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "pure_python", - "stl", - "stl_bind", - "typing", - "values", - "version", -] -version: str = "0.0.0" diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/__init__.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/__init__.pyi deleted file mode 100644 index 9ea9beb4..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/__init__.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from . import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/__init__.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/__init__.pyi deleted file mode 100644 index c696ef48..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/__init__.pyi +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.enum -import numpy -from demo._bindings.aliases.foreign_method_arg import Bar2 as foreign_type_alias -from demo._bindings.aliases.foreign_return import get_foo as foreign_class_alias -from numpy import random - -from . import ( - foreign_arg, - foreign_attr, - foreign_class_member, - foreign_method_arg, - foreign_method_return, - foreign_return, - missing_self_arg, -) - -__all__: list[str] = [ - "Color", - "Dummy", - "foreign_arg", - "foreign_attr", - "foreign_class_alias", - "foreign_class_member", - "foreign_enum_default", - "foreign_method_arg", - "foreign_method_return", - "foreign_return", - "foreign_type_alias", - "func", - "local_func_alias", - "local_type_alias", - "missing_self_arg", - "random", -] - -class Dummy: - linalg = numpy.linalg - -class Color: - pass - -def foreign_enum_default( - color: typing.Any = demo._bindings.enum.ConsoleForegroundColor.Blue, -) -> None: ... -def func(arg0: typing.SupportsInt | typing.SupportsIndex) -> int: ... - -local_type_alias = Color -local_func_alias = func diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_arg.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_arg.pyi deleted file mode 100644 index de75e6fb..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_arg.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["set_foo"] - -def set_foo(arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_attr.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_attr.pyi deleted file mode 100644 index 10c4a712..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_attr.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["value"] -value: demo._bindings.classes.Foo # value = diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_class_member.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_class_member.pyi deleted file mode 100644 index 497a4e2a..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_class_member.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.classes - -__all__: list[str] = ["Bar1"] - -class Bar1: - foo: typing.ClassVar[ - demo._bindings.classes.Foo - ] # value = diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_arg.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_arg.pyi deleted file mode 100644 index 8d4ed4ac..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar2"] - -class Bar2: - def set_foo(self, arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_return.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_return.pyi deleted file mode 100644 index 95d218e1..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_method_return.pyi +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar3"] - -class Bar3: - @staticmethod - def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_return.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_return.pyi deleted file mode 100644 index 7b5b428f..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/foreign_return.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["get_foo"] - -def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/missing_self_arg.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/missing_self_arg.pyi deleted file mode 100644 index ee1a09ee..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/aliases/missing_self_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar4"] - -class Bar4: - def set_foo(self: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/classes.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/classes.pyi deleted file mode 100644 index d85d9249..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/classes.pyi +++ /dev/null @@ -1,94 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "CppException", - "Derived", - "Foo", - "MyBase", - "Outer", - "ParIter", - "ParIterBase", - "ParticleContainer", -] - -class Outer: - class Inner: - class NestedEnum: - """ - Members: - - ONE - - TWO - """ - - ONE: typing.ClassVar[Outer.Inner.NestedEnum] # value = - TWO: typing.ClassVar[Outer.Inner.NestedEnum] # value = - __members__: typing.ClassVar[ - dict[str, Outer.Inner.NestedEnum] - ] # value = {'ONE': , 'TWO': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__( - self, value: typing.SupportsInt | typing.SupportsIndex - ) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__( - self, state: typing.SupportsInt | typing.SupportsIndex - ) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - - value: Outer.Inner.NestedEnum - - inner: Outer.Inner - -class MyBase: - class Inner: - pass - - name: str - -class Derived(MyBase): - @property - def count(self) -> int: ... - @count.setter - def count(self, arg0: typing.SupportsInt | typing.SupportsIndex) -> None: ... - -class Foo: - class FooChild: - def __init__(self) -> None: ... - def g(self) -> None: ... - - def __init__(self) -> None: ... - def f(self) -> None: ... - -class ParIterBase: - @property - def level(self) -> int: ... - @level.setter - def level(self, arg0: typing.SupportsInt | typing.SupportsIndex) -> None: ... - -class ParIter(ParIterBase): - def __init__( - self, - particle_container: ParticleContainer, - level: typing.SupportsInt | typing.SupportsIndex, - ) -> None: ... - -class ParticleContainer: - name: str - Iterator = ParIter - def process(self, arg0: ParIter) -> None: ... - -class CppException(Exception): - pass diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/flawed_bindings.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/flawed_bindings.pyi deleted file mode 100644 index f8c22db8..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/flawed_bindings.pyi +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Enum", - "Unbound", - "accept_unbound_enum", - "accept_unbound_enum_defaulted", - "accept_unbound_type", - "accept_unbound_type_defaulted", - "get_unbound_type", -] - -class Unbound: - pass - -class Enum: - pass - -def accept_unbound_enum(arg0: ...) -> int: ... -def accept_unbound_enum_defaulted(x: Enum = ...) -> int: ... -def accept_unbound_type( - arg0: tuple[..., typing.SupportsInt | typing.SupportsIndex], -) -> int: ... -def accept_unbound_type_defaulted(x: Unbound = ...) -> int: ... -def get_unbound_type() -> ...: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/hidden_builtins.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/hidden_builtins.pyi deleted file mode 100644 index 52c09281..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/hidden_builtins.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import types - -__all__: list[str] = ["mapping_proxy", "none"] -mapping_proxy: types.MappingProxyType # value = mappingproxy({}) -none = None diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/issues.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/issues.pyi deleted file mode 100644 index e8bd056c..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/issues.pyi +++ /dev/null @@ -1,46 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "backslashes_should_be_escaped", - "issue_51_catastrophic_regex", - "issue_73_utf8_doc_chars", - "issue_274_docstring_leading_newline", -] - -def backslashes_should_be_escaped() -> None: - """ - \\brief A brief description of this function. - - A detailed description of this function. - - Here's some reStructuredText: :math:`x = [x, y, \\theta]^T` - """ - -def issue_274_docstring_leading_newline() -> None: - """ - This is a docstring - """ - -def issue_51_catastrophic_regex( - arg0: typing.SupportsInt | typing.SupportsIndex, - arg1: typing.SupportsInt | typing.SupportsIndex, -) -> None: - """ - Use-case: - issue_51(os.get_handle_inheritable, os.set_handle_inheritable) - """ - -def issue_73_utf8_doc_chars() -> None: - """ - Construct a Ramsete unicycle controller. - - Tuning parameter (b > 0 rad²/m²) for which larger values make - - convergence more aggressive like a proportional term. - Tuning parameter (0 rad⁻¹ < zeta < 1 rad⁻¹) for which larger - values provide more damping in response. - """ - -_cleanup: typing.Any # value = diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/methods.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/methods.pyi deleted file mode 100644 index 0ac0df06..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/methods.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = ["Dummy"] - -class Dummy: - @staticmethod - def static_method(arg0: typing.SupportsInt | typing.SupportsIndex) -> int: ... - def regular_method( - self, arg0: typing.SupportsInt | typing.SupportsIndex - ) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/numpy.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/numpy.pyi deleted file mode 100644 index ca0a64b9..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/numpy.pyi +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy -import numpy.typing - -__all__: list[str] = [ - "accept_ndarray_float64", - "accept_ndarray_int", - "get_ndarray_float64", - "get_ndarray_int", - "return_dtype", -] - -def accept_ndarray_float64( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float64], -) -> None: ... -def accept_ndarray_int( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.int32], -) -> None: ... -def get_ndarray_float64() -> numpy.typing.NDArray[numpy.float64]: ... -def get_ndarray_int() -> numpy.typing.NDArray[numpy.int32]: ... -def return_dtype() -> numpy.dtype[typing.Any]: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/stl.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/stl.pyi deleted file mode 100644 index 41ef06d3..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/stl.pyi +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import annotations - -import collections.abc -import typing - -__all__: list[str] = [ - "std_array", - "std_map", - "std_optional", - "std_variant", - "std_vector", -] - -def std_array( - arg0: typing.Annotated[ - collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex], - "FixedSize(3)", - ], -) -> typing.Annotated[list[int], "FixedSize(3)"]: ... -def std_map() -> dict[int, complex]: ... -def std_optional(arg0: typing.SupportsInt | typing.SupportsIndex | None) -> None: ... -def std_variant( - arg0: typing.SupportsInt - | typing.SupportsIndex - | typing.SupportsFloat - | typing.SupportsIndex - | tuple[ - typing.SupportsInt | typing.SupportsIndex, - typing.SupportsInt | typing.SupportsIndex, - ], -) -> None: ... -def std_vector() -> list[tuple[int, float]]: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/stl_bind.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/stl_bind.pyi deleted file mode 100644 index b1ae812b..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/stl_bind.pyi +++ /dev/null @@ -1,145 +0,0 @@ -from __future__ import annotations - -import collections.abc -import typing - -__all__: list[str] = [ - "MapStringComplex", - "VectorPairStringDouble", - "get_complex_map", - "get_vector_of_pairs", -] - -class VectorPairStringDouble: - __hash__: typing.ClassVar[None] = None - def __bool__(self) -> bool: - """ - Check whether the list is nonempty - """ - def __contains__( - self, x: tuple[str, typing.SupportsFloat | typing.SupportsIndex] - ) -> bool: - """ - Return true the container contains ``x`` - """ - @typing.overload - def __delitem__(self, arg0: typing.SupportsInt | typing.SupportsIndex) -> None: - """ - Delete the list elements at index ``i`` - """ - @typing.overload - def __delitem__(self, arg0: slice) -> None: - """ - Delete list elements using a slice object - """ - def __eq__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __getitem__(self, s: slice) -> VectorPairStringDouble: - """ - Retrieve list elements using a slice object - """ - @typing.overload - def __getitem__( - self, arg0: typing.SupportsInt | typing.SupportsIndex - ) -> tuple[str, float]: ... - @typing.overload - def __init__(self) -> None: ... - @typing.overload - def __init__(self, arg0: VectorPairStringDouble) -> None: - """ - Copy constructor - """ - @typing.overload - def __init__(self, arg0: collections.abc.Iterable) -> None: ... - def __iter__(self) -> collections.abc.Iterator[tuple[str, float]]: ... - def __len__(self) -> int: ... - def __ne__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __setitem__( - self, - arg0: typing.SupportsInt | typing.SupportsIndex, - arg1: tuple[str, typing.SupportsFloat | typing.SupportsIndex], - ) -> None: ... - @typing.overload - def __setitem__(self, arg0: slice, arg1: VectorPairStringDouble) -> None: - """ - Assign list elements using a slice object - """ - def append( - self, x: tuple[str, typing.SupportsFloat | typing.SupportsIndex] - ) -> None: - """ - Add an item to the end of the list - """ - def clear(self) -> None: - """ - Clear the contents - """ - def count(self, x: tuple[str, typing.SupportsFloat | typing.SupportsIndex]) -> int: - """ - Return the number of times ``x`` appears in the list - """ - @typing.overload - def extend(self, L: VectorPairStringDouble) -> None: - """ - Extend the list by appending all the items in the given list - """ - @typing.overload - def extend(self, L: collections.abc.Iterable) -> None: - """ - Extend the list by appending all the items in the given list - """ - def insert( - self, - i: typing.SupportsInt | typing.SupportsIndex, - x: tuple[str, typing.SupportsFloat | typing.SupportsIndex], - ) -> None: - """ - Insert an item at a given position. - """ - @typing.overload - def pop(self) -> tuple[str, float]: - """ - Remove and return the last item - """ - @typing.overload - def pop(self, i: typing.SupportsInt | typing.SupportsIndex) -> tuple[str, float]: - """ - Remove and return the item at index ``i`` - """ - def remove( - self, x: tuple[str, typing.SupportsFloat | typing.SupportsIndex] - ) -> None: - """ - Remove the first item from the list whose value is x. It is an error if there is no such item. - """ - -class MapStringComplex: - def __bool__(self) -> bool: - """ - Check whether the map is nonempty - """ - @typing.overload - def __contains__(self, arg0: str) -> bool: ... - @typing.overload - def __contains__(self, arg0: typing.Any) -> bool: ... - def __delitem__(self, arg0: str) -> None: ... - def __getitem__(self, arg0: str) -> complex: ... - def __init__(self) -> None: ... - def __iter__(self) -> collections.abc.Iterator[str]: ... - def __len__(self) -> int: ... - def __repr__(self) -> str: - """ - Return the canonical string representation of this map. - """ - def __setitem__( - self, - arg0: str, - arg1: typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex, - ) -> None: ... - def items(self) -> typing.ItemsView: ... - def keys(self) -> typing.KeysView: ... - def values(self) -> typing.ValuesView: ... - -def get_complex_map() -> MapStringComplex: ... -def get_vector_of_pairs() -> VectorPairStringDouble: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/values.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/values.pyi deleted file mode 100644 index 7c36d3fd..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/values.pyi +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -import datetime - -import numpy -from numpy import random - -__all__: list[str] = [ - "Dummy", - "Foo", - "add_day", - "foolist", - "foovar", - "list_with_none", - "none", - "random", - "t_10ms", - "t_20ns", - "t_30s", -] - -class Dummy: - linalg = numpy.linalg - -class Foo: - pass - -def add_day(arg0: datetime.datetime) -> datetime.datetime: ... - -foolist: list # value = [, ] -foovar: Foo # value = -list_with_none: list = [None, 2, {}] -none = None -t_10ms: datetime.timedelta # value = datetime.timedelta(microseconds=10000) -t_20ns: datetime.timedelta # value = datetime.timedelta(0) -t_30s: datetime.timedelta # value = datetime.timedelta(seconds=30) diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/core.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/core.pyi deleted file mode 100644 index 95cb8455..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/core.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/__init__.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/__init__.pyi deleted file mode 100644 index 10d343e0..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/__init__.pyi +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from . import classes, functions, functions_3_8_plus, functions_3_9_plus, values - -__all__: list[str] = [ - "classes", - "functions", - "functions_3_8_plus", - "functions_3_9_plus", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/classes.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/classes.pyi deleted file mode 100644 index 32aa068b..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/classes.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["A", "B", "C", "X"] - -class A: - """ - A - """ - -class B(A): - """ - B - """ - -class C(B): - """ - C - """ - -class X: - pass diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions.pyi deleted file mode 100644 index 5ed9ef3c..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions.pyi +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import annotations - -import sys as sys -import typing as typing - -from demo.pure_python.functions_3_8_plus import args_mix -from demo.pure_python.functions_3_9_plus import generic_alias_annotation - -__all__: list[str] = [ - "accept_frozenset", - "args_mix", - "builtin_function_as_default_arg", - "function_as_default_arg", - "generic_alias_annotation", - "lambda_as_default_arg", - "search", - "static_method_as_default_arg", - "sys", - "typing", -] - -class _Dummy: - @staticmethod - def foo(): ... - -def accept_frozenset(arg: frozenset[int | float]) -> int | None: ... -def builtin_function_as_default_arg(func: type(len) = len): ... -def function_as_default_arg(func: type(search) = search): ... -def lambda_as_default_arg(callback=...): ... -def search(a: int, b: list[int]) -> int: ... -def static_method_as_default_arg(callback=_Dummy.foo): ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions_3_8_plus.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions_3_8_plus.pyi deleted file mode 100644 index c55aed1d..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions_3_8_plus.pyi +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import typing as typing - -__all__: list[str] = [ - "Expression", - "Token", - "args_mix", - "nested_current_module_annotations", - "typing", -] - -class Token: - pass - -class Expression: - pass - -def args_mix( - a: int, - b: float = 0.5, - c: str = "", - *args: int, - x: int = 1, - y=int, - **kwargs: dict[int, str], -): ... -def nested_current_module_annotations( - tokens: list[Token], expr: Expression | None = None -) -> dict[str, Expression]: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions_3_9_plus.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions_3_9_plus.pyi deleted file mode 100644 index 59b9b80c..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/functions_3_9_plus.pyi +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["generic_alias_annotation"] - -def generic_alias_annotation(a: list[tuple[int]], b: dict[int, str]) -> list[float]: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/values.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/values.pyi deleted file mode 100644 index 07913391..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/pure_python/values.pyi +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["callables_dict"] -callables_dict: dict = {"len": len, "int": int} diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/__init__.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/__init__.pyi deleted file mode 100644 index 9f27c427..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/__init__.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -from . import _bindings, core, pure_python - -__all__: list[str] = [ - "aliases", - "classes", - "core", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "pure_python", - "stl", - "stl_bind", - "typing", - "values", - "version", -] -version: str = "0.0.0" diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi deleted file mode 100644 index 9ea9beb4..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/__init__.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from . import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi deleted file mode 100644 index c696ef48..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/__init__.pyi +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.enum -import numpy -from demo._bindings.aliases.foreign_method_arg import Bar2 as foreign_type_alias -from demo._bindings.aliases.foreign_return import get_foo as foreign_class_alias -from numpy import random - -from . import ( - foreign_arg, - foreign_attr, - foreign_class_member, - foreign_method_arg, - foreign_method_return, - foreign_return, - missing_self_arg, -) - -__all__: list[str] = [ - "Color", - "Dummy", - "foreign_arg", - "foreign_attr", - "foreign_class_alias", - "foreign_class_member", - "foreign_enum_default", - "foreign_method_arg", - "foreign_method_return", - "foreign_return", - "foreign_type_alias", - "func", - "local_func_alias", - "local_type_alias", - "missing_self_arg", - "random", -] - -class Dummy: - linalg = numpy.linalg - -class Color: - pass - -def foreign_enum_default( - color: typing.Any = demo._bindings.enum.ConsoleForegroundColor.Blue, -) -> None: ... -def func(arg0: typing.SupportsInt | typing.SupportsIndex) -> int: ... - -local_type_alias = Color -local_func_alias = func diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi deleted file mode 100644 index de75e6fb..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_arg.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["set_foo"] - -def set_foo(arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi deleted file mode 100644 index 10c4a712..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_attr.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["value"] -value: demo._bindings.classes.Foo # value = diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi deleted file mode 100644 index 497a4e2a..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_class_member.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -import demo._bindings.classes - -__all__: list[str] = ["Bar1"] - -class Bar1: - foo: typing.ClassVar[ - demo._bindings.classes.Foo - ] # value = diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi deleted file mode 100644 index 8d4ed4ac..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar2"] - -class Bar2: - def set_foo(self, arg0: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi deleted file mode 100644 index 95d218e1..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_method_return.pyi +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar3"] - -class Bar3: - @staticmethod - def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi deleted file mode 100644 index 7b5b428f..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/foreign_return.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["get_foo"] - -def get_foo() -> demo._bindings.classes.Foo: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi deleted file mode 100644 index ee1a09ee..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/aliases/missing_self_arg.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import demo._bindings.classes - -__all__: list[str] = ["Bar4"] - -class Bar4: - def set_foo(self: demo._bindings.classes.Foo) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi deleted file mode 100644 index d85d9249..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/classes.pyi +++ /dev/null @@ -1,94 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "CppException", - "Derived", - "Foo", - "MyBase", - "Outer", - "ParIter", - "ParIterBase", - "ParticleContainer", -] - -class Outer: - class Inner: - class NestedEnum: - """ - Members: - - ONE - - TWO - """ - - ONE: typing.ClassVar[Outer.Inner.NestedEnum] # value = - TWO: typing.ClassVar[Outer.Inner.NestedEnum] # value = - __members__: typing.ClassVar[ - dict[str, Outer.Inner.NestedEnum] - ] # value = {'ONE': , 'TWO': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__( - self, value: typing.SupportsInt | typing.SupportsIndex - ) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__( - self, state: typing.SupportsInt | typing.SupportsIndex - ) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - - value: Outer.Inner.NestedEnum - - inner: Outer.Inner - -class MyBase: - class Inner: - pass - - name: str - -class Derived(MyBase): - @property - def count(self) -> int: ... - @count.setter - def count(self, arg0: typing.SupportsInt | typing.SupportsIndex) -> None: ... - -class Foo: - class FooChild: - def __init__(self) -> None: ... - def g(self) -> None: ... - - def __init__(self) -> None: ... - def f(self) -> None: ... - -class ParIterBase: - @property - def level(self) -> int: ... - @level.setter - def level(self, arg0: typing.SupportsInt | typing.SupportsIndex) -> None: ... - -class ParIter(ParIterBase): - def __init__( - self, - particle_container: ParticleContainer, - level: typing.SupportsInt | typing.SupportsIndex, - ) -> None: ... - -class ParticleContainer: - name: str - Iterator = ParIter - def process(self, arg0: ParIter) -> None: ... - -class CppException(Exception): - pass diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi deleted file mode 100644 index 1bf5cd79..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/eigen.pyi +++ /dev/null @@ -1,75 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy -import numpy.typing -import scipy.sparse - -__all__: list[str] = [ - "accept_matrix_int", - "accept_vector_float64", - "dense_matrix_c", - "dense_matrix_r", - "fixed_mutator_a", - "fixed_mutator_c", - "fixed_mutator_r", - "four_col_matrix_r", - "four_row_matrix_r", - "get_matrix_int", - "get_vector_float64", - "sparse_matrix_c", - "sparse_matrix_r", -] - -def accept_matrix_int( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.int32, "[3, 3]"], -) -> None: ... -def accept_vector_float64( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float64, "[3, 1]"], -) -> None: ... -def dense_matrix_c( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float32, "[m, n]"], -) -> typing.Annotated[numpy.typing.NDArray[numpy.float32], "[m, n]"]: ... -def dense_matrix_r( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float32, "[m, n]"], -) -> typing.Annotated[numpy.typing.NDArray[numpy.float32], "[m, n]"]: ... -def fixed_mutator_a( - arg0: typing.Annotated[ - numpy.typing.NDArray[numpy.float32], "[5, 6]", "flags.writeable" - ], -) -> None: ... -def fixed_mutator_c( - arg0: typing.Annotated[ - numpy.typing.NDArray[numpy.float32], - "[5, 6]", - "flags.writeable", - "flags.f_contiguous", - ], -) -> None: ... -def fixed_mutator_r( - arg0: typing.Annotated[ - numpy.typing.NDArray[numpy.float32], - "[5, 6]", - "flags.writeable", - "flags.c_contiguous", - ], -) -> None: ... -def four_col_matrix_r( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float32, "[m, 4]"], -) -> typing.Annotated[numpy.typing.NDArray[numpy.float32], "[m, 4]"]: ... -def four_row_matrix_r( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float32, "[4, n]"], -) -> typing.Annotated[numpy.typing.NDArray[numpy.float32], "[4, n]"]: ... -def get_matrix_int() -> typing.Annotated[ - numpy.typing.NDArray[numpy.int32], "[3, 3]" -]: ... -def get_vector_float64() -> typing.Annotated[ - numpy.typing.NDArray[numpy.float64], "[3, 1]" -]: ... -def sparse_matrix_c( - arg0: typing.Annotated[scipy.sparse.csc_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csc_matrix, numpy.float32]: ... -def sparse_matrix_r( - arg0: typing.Annotated[scipy.sparse.csr_matrix, numpy.float32], -) -> typing.Annotated[scipy.sparse.csr_matrix, numpy.float32]: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi deleted file mode 100644 index 3c587312..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/enum.pyi +++ /dev/null @@ -1,85 +0,0 @@ -from __future__ import annotations - -import enum -import typing - -__all__: list[str] = [ - "Blue", - "ConsoleForegroundColor", - "Green", - "Magenta", - "NativeColor", - "None_", - "Yellow", - "accept_defaulted_enum", -] - -class ConsoleForegroundColor: - """ - Members: - - Green - - Yellow - - Blue - - Magenta - - None_ - """ - - Blue: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Green: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Magenta: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - None_: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - Yellow: typing.ClassVar[ - ConsoleForegroundColor - ] # value = - __members__: typing.ClassVar[ - dict[str, ConsoleForegroundColor] - ] # value = {'Green': , 'Yellow': , 'Blue': , 'Magenta': , 'None_': } - def __eq__(self, other: typing.Any) -> bool: ... - def __getstate__(self) -> int: ... - def __hash__(self) -> int: ... - def __index__(self) -> int: ... - def __init__(self, value: typing.SupportsInt | typing.SupportsIndex) -> None: ... - def __int__(self) -> int: ... - def __ne__(self, other: typing.Any) -> bool: ... - def __repr__(self) -> str: ... - def __setstate__( - self, state: typing.SupportsInt | typing.SupportsIndex - ) -> None: ... - def __str__(self) -> str: ... - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - -class NativeColor(enum.IntEnum): - Blue: typing.ClassVar[NativeColor] # value = - Red: typing.ClassVar[NativeColor] # value = - @classmethod - def __new__(cls, value): ... - def __format__(self, format_spec): - """ - Convert to a string according to format_spec. - """ - -def accept_defaulted_enum( - color: ConsoleForegroundColor = ConsoleForegroundColor.None_, -) -> None: ... - -Blue: ConsoleForegroundColor # value = -Green: ConsoleForegroundColor # value = -Magenta: ConsoleForegroundColor # value = -None_: ConsoleForegroundColor # value = -Yellow: ConsoleForegroundColor # value = diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi deleted file mode 100644 index f8c22db8..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/flawed_bindings.pyi +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "Enum", - "Unbound", - "accept_unbound_enum", - "accept_unbound_enum_defaulted", - "accept_unbound_type", - "accept_unbound_type_defaulted", - "get_unbound_type", -] - -class Unbound: - pass - -class Enum: - pass - -def accept_unbound_enum(arg0: ...) -> int: ... -def accept_unbound_enum_defaulted(x: Enum = ...) -> int: ... -def accept_unbound_type( - arg0: tuple[..., typing.SupportsInt | typing.SupportsIndex], -) -> int: ... -def accept_unbound_type_defaulted(x: Unbound = ...) -> int: ... -def get_unbound_type() -> ...: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi deleted file mode 100644 index 94fbc47d..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/functions.pyi +++ /dev/null @@ -1,117 +0,0 @@ -from __future__ import annotations - -import collections.abc -import typing - -__all__: list[str] = [ - "Foo", - "accept_annotated_callable", - "accept_callable", - "accept_frozenset", - "accept_py_handle", - "accept_py_object", - "accept_set", - "add", - "default_custom_arg", - "default_int_arg", - "default_list_arg", - "default_optional_arg", - "func_w_anon_args", - "func_w_named_pos_args", - "generic", - "mul", - "nested_types", - "pass_callback", - "passthrough1", - "passthrough2", - "passthrough3", - "passthrough_backwards", - "pos_kw_only_mix", - "pos_kw_only_variadic_mix", -] - -class Foo: - def __init__(self, arg0: typing.SupportsInt | typing.SupportsIndex) -> None: ... - -def accept_annotated_callable( - arg0: collections.abc.Callable[ - [ - typing.SupportsInt | typing.SupportsIndex, - typing.SupportsInt | typing.SupportsIndex, - ], - int, - ], -) -> typing.Any: ... -def accept_callable(arg0: collections.abc.Callable) -> typing.Any: ... -def accept_frozenset(arg0: frozenset) -> None: ... -def accept_py_handle(arg0: typing.Any) -> str: ... -def accept_py_object(arg0: typing.Any) -> str: ... -def accept_set(arg0: set) -> None: ... -def add( - arg0: typing.SupportsInt | typing.SupportsIndex, - arg1: typing.SupportsInt | typing.SupportsIndex, -) -> int: ... -def default_custom_arg(foo: Foo = Foo(5)) -> None: ... -def default_int_arg(n: typing.SupportsInt | typing.SupportsIndex = 5) -> None: ... -def default_list_arg(l: list = [1, 2, 6, 18]) -> None: ... -def default_optional_arg( - n: typing.SupportsInt | typing.SupportsIndex | None = None, -) -> None: ... -def func_w_anon_args( - arg0: typing.SupportsInt | typing.SupportsIndex, - arg1: typing.SupportsInt | typing.SupportsIndex, - arg2: typing.SupportsInt | typing.SupportsIndex, -) -> None: ... -def func_w_named_pos_args( - x: typing.SupportsInt | typing.SupportsIndex, - y: typing.SupportsInt | typing.SupportsIndex, - z: typing.SupportsInt | typing.SupportsIndex, -) -> None: ... -def generic(*args, **kwargs) -> None: ... -@typing.overload -def mul( - x: typing.SupportsInt | typing.SupportsIndex, - y: typing.SupportsInt | typing.SupportsIndex, -) -> int: - """ - Multiply x and y (int) - """ - -@typing.overload -def mul( - p: typing.SupportsFloat | typing.SupportsIndex, - q: typing.SupportsFloat | typing.SupportsIndex, -) -> float: - """ - Multiply p and q (double) - """ - -def nested_types(arg0: collections.abc.Sequence[Foo] | Foo) -> list[Foo] | Foo: ... -def pass_callback(arg0: collections.abc.Callable[[Foo], Foo]) -> Foo: ... -def passthrough1[T](obj: T) -> T: ... -@typing.overload -def passthrough2() -> None: ... -@typing.overload -def passthrough2[T](obj: T) -> T: ... -@typing.overload -def passthrough3() -> tuple[None, None]: ... -@typing.overload -def passthrough3[T](obj: T) -> tuple[T, None]: ... -@typing.overload -def passthrough3[T1, T2](obj1: T1, obj2: T2) -> tuple[T1, T2]: ... -def passthrough_backwards[T](obj: T) -> T: ... -def pos_kw_only_mix( - i: typing.SupportsInt | typing.SupportsIndex, - /, - j: typing.SupportsInt | typing.SupportsIndex, - *, - k: typing.SupportsInt | typing.SupportsIndex, -) -> tuple[int, int, int]: ... -def pos_kw_only_variadic_mix( - i: typing.SupportsInt | typing.SupportsIndex, - /, - j: typing.SupportsInt | typing.SupportsIndex, - *args, - k: typing.SupportsInt | typing.SupportsIndex, - **kwargs, -) -> tuple[int, int, int]: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi deleted file mode 100644 index 52c09281..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/hidden_builtins.pyi +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -import types - -__all__: list[str] = ["mapping_proxy", "none"] -mapping_proxy: types.MappingProxyType # value = mappingproxy({}) -none = None diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi deleted file mode 100644 index e8bd056c..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/issues.pyi +++ /dev/null @@ -1,46 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "backslashes_should_be_escaped", - "issue_51_catastrophic_regex", - "issue_73_utf8_doc_chars", - "issue_274_docstring_leading_newline", -] - -def backslashes_should_be_escaped() -> None: - """ - \\brief A brief description of this function. - - A detailed description of this function. - - Here's some reStructuredText: :math:`x = [x, y, \\theta]^T` - """ - -def issue_274_docstring_leading_newline() -> None: - """ - This is a docstring - """ - -def issue_51_catastrophic_regex( - arg0: typing.SupportsInt | typing.SupportsIndex, - arg1: typing.SupportsInt | typing.SupportsIndex, -) -> None: - """ - Use-case: - issue_51(os.get_handle_inheritable, os.set_handle_inheritable) - """ - -def issue_73_utf8_doc_chars() -> None: - """ - Construct a Ramsete unicycle controller. - - Tuning parameter (b > 0 rad²/m²) for which larger values make - - convergence more aggressive like a proportional term. - Tuning parameter (0 rad⁻¹ < zeta < 1 rad⁻¹) for which larger - values provide more damping in response. - """ - -_cleanup: typing.Any # value = diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi deleted file mode 100644 index 0ac0df06..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/methods.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = ["Dummy"] - -class Dummy: - @staticmethod - def static_method(arg0: typing.SupportsInt | typing.SupportsIndex) -> int: ... - def regular_method( - self, arg0: typing.SupportsInt | typing.SupportsIndex - ) -> int: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi deleted file mode 100644 index ca0a64b9..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/numpy.pyi +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import annotations - -import typing - -import numpy -import numpy.typing - -__all__: list[str] = [ - "accept_ndarray_float64", - "accept_ndarray_int", - "get_ndarray_float64", - "get_ndarray_int", - "return_dtype", -] - -def accept_ndarray_float64( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float64], -) -> None: ... -def accept_ndarray_int( - arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.int32], -) -> None: ... -def get_ndarray_float64() -> numpy.typing.NDArray[numpy.float64]: ... -def get_ndarray_int() -> numpy.typing.NDArray[numpy.int32]: ... -def return_dtype() -> numpy.dtype[typing.Any]: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi deleted file mode 100644 index eb99e55f..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi +++ /dev/null @@ -1,127 +0,0 @@ -from __future__ import annotations - -import typing - -__all__: list[str] = [ - "WithGetterSetterDoc", - "WithPropAndGetterSetterDoc", - "WithPropDoc", - "WithoutDoc", -] - -class WithoutDoc: - """ - No user docstring provided - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - def_property_static: typing.ClassVar[int] = 0 - @property - def def_property(self) -> int: ... - @def_property.setter - def def_property(self, arg1: typing.SupportsInt | typing.SupportsIndex) -> None: ... - @property - def def_property_readonly(self) -> int: ... - @property - def def_readonly(self) -> int: ... - @property - def def_readwrite(self) -> int: ... - @def_readwrite.setter - def def_readwrite( - self, arg0: typing.SupportsInt | typing.SupportsIndex - ) -> None: ... - -class WithPropDoc: - """ - User docstring provided only to `def_` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - """ - prop doc token - """ - def_property_static: typing.ClassVar[int] = 0 - """ - prop doc token - """ - @property - def def_property(self) -> int: - """ - prop doc token - """ - @def_property.setter - def def_property(self, arg1: typing.SupportsInt | typing.SupportsIndex) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ - @property - def def_readonly(self) -> int: - """ - prop doc token - """ - @property - def def_readwrite(self) -> int: - """ - prop doc token - """ - @def_readwrite.setter - def def_readwrite( - self, arg0: typing.SupportsInt | typing.SupportsIndex - ) -> None: ... - -class WithGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters, but NOT to `def_*(..., doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - """ - getter doc token - """ - def_property_static: typing.ClassVar[int] = 0 - """ - getter doc token - """ - @property - def def_property(self) -> int: - """ - getter doc token - """ - @def_property.setter - def def_property(self, arg1: typing.SupportsInt | typing.SupportsIndex) -> None: - """ - setter doc token - """ - @property - def def_property_readonly(self) -> int: - """ - getter doc token - """ - -class WithPropAndGetterSetterDoc: - """ - User docstring provided via pybind11::cpp_function(..., doc) to getters/setters and to `def_*(, doc)` calls - """ - - def_property_readonly_static: typing.ClassVar[int] = 0 - """ - prop doc token - """ - def_property_static: typing.ClassVar[int] = 0 - """ - prop doc token - """ - @property - def def_property(self) -> int: - """ - prop doc token - """ - @def_property.setter - def def_property(self, arg1: typing.SupportsInt | typing.SupportsIndex) -> None: ... - @property - def def_property_readonly(self) -> int: - """ - prop doc token - """ diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi deleted file mode 100644 index 41ef06d3..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl.pyi +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import annotations - -import collections.abc -import typing - -__all__: list[str] = [ - "std_array", - "std_map", - "std_optional", - "std_variant", - "std_vector", -] - -def std_array( - arg0: typing.Annotated[ - collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex], - "FixedSize(3)", - ], -) -> typing.Annotated[list[int], "FixedSize(3)"]: ... -def std_map() -> dict[int, complex]: ... -def std_optional(arg0: typing.SupportsInt | typing.SupportsIndex | None) -> None: ... -def std_variant( - arg0: typing.SupportsInt - | typing.SupportsIndex - | typing.SupportsFloat - | typing.SupportsIndex - | tuple[ - typing.SupportsInt | typing.SupportsIndex, - typing.SupportsInt | typing.SupportsIndex, - ], -) -> None: ... -def std_vector() -> list[tuple[int, float]]: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi deleted file mode 100644 index b1ae812b..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/stl_bind.pyi +++ /dev/null @@ -1,145 +0,0 @@ -from __future__ import annotations - -import collections.abc -import typing - -__all__: list[str] = [ - "MapStringComplex", - "VectorPairStringDouble", - "get_complex_map", - "get_vector_of_pairs", -] - -class VectorPairStringDouble: - __hash__: typing.ClassVar[None] = None - def __bool__(self) -> bool: - """ - Check whether the list is nonempty - """ - def __contains__( - self, x: tuple[str, typing.SupportsFloat | typing.SupportsIndex] - ) -> bool: - """ - Return true the container contains ``x`` - """ - @typing.overload - def __delitem__(self, arg0: typing.SupportsInt | typing.SupportsIndex) -> None: - """ - Delete the list elements at index ``i`` - """ - @typing.overload - def __delitem__(self, arg0: slice) -> None: - """ - Delete list elements using a slice object - """ - def __eq__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __getitem__(self, s: slice) -> VectorPairStringDouble: - """ - Retrieve list elements using a slice object - """ - @typing.overload - def __getitem__( - self, arg0: typing.SupportsInt | typing.SupportsIndex - ) -> tuple[str, float]: ... - @typing.overload - def __init__(self) -> None: ... - @typing.overload - def __init__(self, arg0: VectorPairStringDouble) -> None: - """ - Copy constructor - """ - @typing.overload - def __init__(self, arg0: collections.abc.Iterable) -> None: ... - def __iter__(self) -> collections.abc.Iterator[tuple[str, float]]: ... - def __len__(self) -> int: ... - def __ne__(self, arg0: VectorPairStringDouble) -> bool: ... - @typing.overload - def __setitem__( - self, - arg0: typing.SupportsInt | typing.SupportsIndex, - arg1: tuple[str, typing.SupportsFloat | typing.SupportsIndex], - ) -> None: ... - @typing.overload - def __setitem__(self, arg0: slice, arg1: VectorPairStringDouble) -> None: - """ - Assign list elements using a slice object - """ - def append( - self, x: tuple[str, typing.SupportsFloat | typing.SupportsIndex] - ) -> None: - """ - Add an item to the end of the list - """ - def clear(self) -> None: - """ - Clear the contents - """ - def count(self, x: tuple[str, typing.SupportsFloat | typing.SupportsIndex]) -> int: - """ - Return the number of times ``x`` appears in the list - """ - @typing.overload - def extend(self, L: VectorPairStringDouble) -> None: - """ - Extend the list by appending all the items in the given list - """ - @typing.overload - def extend(self, L: collections.abc.Iterable) -> None: - """ - Extend the list by appending all the items in the given list - """ - def insert( - self, - i: typing.SupportsInt | typing.SupportsIndex, - x: tuple[str, typing.SupportsFloat | typing.SupportsIndex], - ) -> None: - """ - Insert an item at a given position. - """ - @typing.overload - def pop(self) -> tuple[str, float]: - """ - Remove and return the last item - """ - @typing.overload - def pop(self, i: typing.SupportsInt | typing.SupportsIndex) -> tuple[str, float]: - """ - Remove and return the item at index ``i`` - """ - def remove( - self, x: tuple[str, typing.SupportsFloat | typing.SupportsIndex] - ) -> None: - """ - Remove the first item from the list whose value is x. It is an error if there is no such item. - """ - -class MapStringComplex: - def __bool__(self) -> bool: - """ - Check whether the map is nonempty - """ - @typing.overload - def __contains__(self, arg0: str) -> bool: ... - @typing.overload - def __contains__(self, arg0: typing.Any) -> bool: ... - def __delitem__(self, arg0: str) -> None: ... - def __getitem__(self, arg0: str) -> complex: ... - def __init__(self) -> None: ... - def __iter__(self) -> collections.abc.Iterator[str]: ... - def __len__(self) -> int: ... - def __repr__(self) -> str: - """ - Return the canonical string representation of this map. - """ - def __setitem__( - self, - arg0: str, - arg1: typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex, - ) -> None: ... - def items(self) -> typing.ItemsView: ... - def keys(self) -> typing.KeysView: ... - def values(self) -> typing.ValuesView: ... - -def get_complex_map() -> MapStringComplex: ... -def get_vector_of_pairs() -> VectorPairStringDouble: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi deleted file mode 100644 index 98fd047b..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/typing.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -import collections.abc - -__all__: list[str] = ["get_buffer", "get_sequence"] - -def get_buffer(arg0: collections.abc.Buffer) -> collections.abc.Buffer: ... -def get_sequence(arg0: collections.abc.Sequence) -> collections.abc.Sequence: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi deleted file mode 100644 index 7c36d3fd..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/values.pyi +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -import datetime - -import numpy -from numpy import random - -__all__: list[str] = [ - "Dummy", - "Foo", - "add_day", - "foolist", - "foovar", - "list_with_none", - "none", - "random", - "t_10ms", - "t_20ns", - "t_30s", -] - -class Dummy: - linalg = numpy.linalg - -class Foo: - pass - -def add_day(arg0: datetime.datetime) -> datetime.datetime: ... - -foolist: list # value = [, ] -foovar: Foo # value = -list_with_none: list = [None, 2, {}] -none = None -t_10ms: datetime.timedelta # value = datetime.timedelta(microseconds=10000) -t_20ns: datetime.timedelta # value = datetime.timedelta(0) -t_30s: datetime.timedelta # value = datetime.timedelta(seconds=30) diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/core.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/core.pyi deleted file mode 100644 index 95cb8455..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/core.pyi +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from demo._bindings import ( - aliases, - classes, - eigen, - enum, - flawed_bindings, - functions, - hidden_builtins, - issues, - methods, - numpy, - properties, - stl, - stl_bind, - typing, - values, -) - -__all__: list[str] = [ - "aliases", - "classes", - "eigen", - "enum", - "flawed_bindings", - "functions", - "hidden_builtins", - "issues", - "methods", - "numpy", - "properties", - "stl", - "stl_bind", - "typing", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi deleted file mode 100644 index 10d343e0..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/__init__.pyi +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from . import classes, functions, functions_3_8_plus, functions_3_9_plus, values - -__all__: list[str] = [ - "classes", - "functions", - "functions_3_8_plus", - "functions_3_9_plus", - "values", -] diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi deleted file mode 100644 index 32aa068b..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/classes.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["A", "B", "C", "X"] - -class A: - """ - A - """ - -class B(A): - """ - B - """ - -class C(B): - """ - C - """ - -class X: - pass diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi deleted file mode 100644 index 5ed9ef3c..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions.pyi +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import annotations - -import sys as sys -import typing as typing - -from demo.pure_python.functions_3_8_plus import args_mix -from demo.pure_python.functions_3_9_plus import generic_alias_annotation - -__all__: list[str] = [ - "accept_frozenset", - "args_mix", - "builtin_function_as_default_arg", - "function_as_default_arg", - "generic_alias_annotation", - "lambda_as_default_arg", - "search", - "static_method_as_default_arg", - "sys", - "typing", -] - -class _Dummy: - @staticmethod - def foo(): ... - -def accept_frozenset(arg: frozenset[int | float]) -> int | None: ... -def builtin_function_as_default_arg(func: type(len) = len): ... -def function_as_default_arg(func: type(search) = search): ... -def lambda_as_default_arg(callback=...): ... -def search(a: int, b: list[int]) -> int: ... -def static_method_as_default_arg(callback=_Dummy.foo): ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi deleted file mode 100644 index c55aed1d..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_8_plus.pyi +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import typing as typing - -__all__: list[str] = [ - "Expression", - "Token", - "args_mix", - "nested_current_module_annotations", - "typing", -] - -class Token: - pass - -class Expression: - pass - -def args_mix( - a: int, - b: float = 0.5, - c: str = "", - *args: int, - x: int = 1, - y=int, - **kwargs: dict[int, str], -): ... -def nested_current_module_annotations( - tokens: list[Token], expr: Expression | None = None -) -> dict[str, Expression]: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi deleted file mode 100644 index 59b9b80c..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/functions_3_9_plus.pyi +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["generic_alias_annotation"] - -def generic_alias_annotation(a: list[tuple[int]], b: dict[int, str]) -> list[float]: ... diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi deleted file mode 100644 index 07913391..00000000 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/pure_python/values.pyi +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -__all__: list[str] = ["callables_dict"] -callables_dict: dict = {"len": len, "int": int} diff --git a/tests/stubs/python-3.13 b/tests/stubs/python-3.13 deleted file mode 120000 index 0a8325ae..00000000 --- a/tests/stubs/python-3.13 +++ /dev/null @@ -1 +0,0 @@ -python-3.12 \ No newline at end of file diff --git a/tests/test_demo_errors.py b/tests/test_demo_errors.py index 4aa05b20..77573972 100644 --- a/tests/test_demo_errors.py +++ b/tests/test_demo_errors.py @@ -1,7 +1,7 @@ import sys +from snapshot_catalog import check_case, load_catalog from snapshot_helpers import ( - check_snapshot, diagnostics, run_command, validate_error_run, @@ -12,16 +12,17 @@ def test_demo_errors( demo_case, tmp_path, update_snapshots, artifacts_dir, report_update ): case = demo_case - expected = case.errors_root / case.error_profile + expected = case.catalog_path output = tmp_path / "output" filename = "demo.errors.stderr.txt" with diagnostics( tmp_path, artifacts=artifacts_dir, - reference_roots=(case.stubs_root, case.errors_root), + reference_roots=(expected, case.stubs_root, case.errors_root), case_id=case.id, check_name="errors", - expected=expected / filename, + expected=expected, + reference_details=load_catalog(case.repo).context(case.id, "errors"), ): result = run_command( [ @@ -40,14 +41,13 @@ def test_demo_errors( ) stderr = validate_error_run(result, output) (tmp_path / "normalized.stderr").write_bytes(stderr) - changed = check_snapshot( - case.errors_root, - case.error_profile, + message = check_case( + case.repo, + case.id, + "errors", {filename: stderr}, update=update_snapshots, only=frozenset({filename}), ) if update_snapshots: - report_update( - f"{case.id}: {expected} -> {expected.resolve()}; changed: {changed}" - ) + report_update(message) diff --git a/tests/test_demo_stubs.py b/tests/test_demo_stubs.py index 5503bbf2..cf9b5f98 100644 --- a/tests/test_demo_stubs.py +++ b/tests/test_demo_stubs.py @@ -1,8 +1,8 @@ import sys +from snapshot_catalog import check_case, load_catalog from snapshot_helpers import ( HarnessError, - check_snapshot, diagnostics, format_stubs, read_tree, @@ -14,15 +14,16 @@ def test_demo_stubs( demo_case, tmp_path, update_snapshots, artifacts_dir, report_update ): case = demo_case - expected = case.stubs_root / case.stub_profile + expected = case.catalog_path output = tmp_path / "output" with diagnostics( tmp_path, artifacts=artifacts_dir, - reference_roots=(case.stubs_root, case.errors_root), + reference_roots=(expected, case.stubs_root, case.errors_root), case_id=case.id, check_name="stubs", expected=expected, + reference_details=load_catalog(case.repo).context(case.id, "stubs"), ): run_command( [ @@ -52,10 +53,8 @@ def test_demo_stubs( actual = read_tree(output) if "demo/__init__.pyi" not in actual: raise HarnessError("Normalized output lost demo/__init__.pyi") - changed = check_snapshot( - case.stubs_root, case.stub_profile, actual, update=update_snapshots + message = check_case( + case.repo, case.id, "stubs", actual, update=update_snapshots ) if update_snapshots: - report_update( - f"{case.id}: {expected} -> {expected.resolve()}; changed: {changed}" - ) + report_update(message) diff --git a/tests/test_snapshot_helpers.py b/tests/test_snapshot_helpers.py index 50f09f36..450040a1 100644 --- a/tests/test_snapshot_helpers.py +++ b/tests/test_snapshot_helpers.py @@ -8,6 +8,30 @@ import pytest import snapshot_helpers as h +from snapshot_catalog import load_catalog +from snapshot_test_support import tree_state, write_catalog + + +def seed_catalog(case): + write_catalog( + case.repo, + { + case.id: { + "stubs": {"demo/__init__.pyi": "seed/init.pyi"}, + "errors": {"demo.errors.stderr.txt": "seed/stderr.txt"}, + }, + "unrelated": { + "stubs": {"demo/__init__.pyi": "seed/init.pyi"}, + "errors": {"demo.errors.stderr.txt": "seed/stderr.txt"}, + }, + }, + { + "stubs": {"seed/init.pyi": b"original"}, + "errors": {"seed/stderr.txt": b"original"}, + }, + ) + (case.repo / ".git").mkdir() + (case.repo / ".git/index").write_bytes(b"index sentinel") def write_files(root: Path, files: dict[str, bytes]) -> None: @@ -728,12 +752,12 @@ def test_case_requires_existing_profiles_and_uses_runtime_python(tmp_path): mode = "numpy-array-wrap-with-annotated" with pytest.raises(h.HarnessError): h.make_case(tmp_path, (3, 13), "v3.0", mode) - write_files(tmp_path / "tests/stubs/python-3.13/pybind11-v3.0" / mode, {}) - write_files(tmp_path / "tests/errors/pybind11-v3.0", {}) + seed_catalog(h.DemoCase(tmp_path, (3, 13), "v3.0", mode)) case = h.make_case(tmp_path, (3, 13), "v3.0", mode) - assert case.stub_profile == Path("python-3.13/pybind11-v3.0") / mode - assert case.error_profile == Path("pybind11-v3.0") - assert "python-3.13" in case.id + assert case.catalog_path == tmp_path / "tests/snapshot_cases.toml" + assert case.id == "python-3.13-pybind11-v3.0-numpy-array-wrap-with-annotated" + with pytest.raises(h.HarnessError, match="python-3.12"): + h.make_case(tmp_path, (3, 12), "v3.0", mode) @pytest.mark.parametrize("failure_stage", ["generator", "formatter"]) @@ -744,8 +768,8 @@ def test_success_update_never_accepts_failed_generation_or_formatting( repo = tmp_path / "repo" case = h.DemoCase(repo, (3, 13), "v3.0", "numpy-array-wrap-with-annotated") - reference = case.stubs_root / case.stub_profile - write_files(reference, {"demo/__init__.pyi": b"original"}) + seed_catalog(case) + before = tree_state(case.repo) messages = [] def generate(argv, **kwargs): @@ -765,7 +789,11 @@ def format_failure(*args): integration.test_demo_stubs( case, tmp_path / "work", True, None, messages.append ) - assert h.read_tree(reference) == {"demo/__init__.pyi": b"original"} + assert tree_state(case.repo) == before + context = (tmp_path / "work/failure.txt").read_text() + assert str(case.catalog_path) in context + assert "demo/__init__.pyi" in context + assert str(case.stubs_root / "seed/init.pyi") in context assert messages == [] @@ -773,8 +801,8 @@ def test_error_update_rejects_traceback_even_with_exit_one(tmp_path, monkeypatch import test_demo_errors as integration case = h.DemoCase(tmp_path / "repo", (3, 13), "v3.0", "numpy-array-use-type-var") - reference = case.errors_root / case.error_profile - write_files(reference, {"demo.errors.stderr.txt": b"original"}) + seed_catalog(case) + before = tree_state(case.repo) def traceback_result(argv, **kwargs): assert argv[:4] == [sys.executable, "-I", "-m", "pybind11_stubgen"] @@ -786,7 +814,10 @@ def traceback_result(argv, **kwargs): monkeypatch.setattr(integration, "run_command", traceback_result) with pytest.raises(h.HarnessError, match="traceback"): integration.test_demo_errors(case, tmp_path / "work", True, None, print) - assert h.read_tree(reference) == {"demo.errors.stderr.txt": b"original"} + assert tree_state(case.repo) == before + context = (tmp_path / "work/failure.txt").read_text() + assert str(case.catalog_path) in context + assert str(case.errors_root / "seed/stderr.txt") in context @pytest.mark.parametrize("kind", ["stubs", "errors"]) @@ -804,18 +835,15 @@ def test_integration_check_and_update_paths_with_synthetic_output( run_test = ( integration.test_demo_stubs if kind == "stubs" else integration.test_demo_errors ) - reference = ( - case.stubs_root / case.stub_profile - if kind == "stubs" - else case.errors_root / case.error_profile - ) + seed_catalog(case) + before = tree_state(case.repo) + original = load_catalog(case.repo) filename = "demo/__init__.pyi" if kind == "stubs" else "demo.errors.stderr.txt" new_content = ( b"new stubs\n" if kind == "stubs" else b"object 0x1234abcd5678\nTerminating due to previous errors\n" ) - write_files(reference, {filename: b"original"}) messages = [] def generate(argv, **kwargs): @@ -831,12 +859,22 @@ def generate(argv, **kwargs): monkeypatch.setattr(integration, "format_stubs", lambda *args: None) if update: run_test(case, tmp_path / "work", True, None, messages.append) - assert h.read_tree(reference) == {filename: new_content} - assert messages and str(reference.resolve()) in messages[0] + fresh = load_catalog(case.repo) + assert fresh.snapshot(case.id, kind) == {filename: new_content} + other_kind = "errors" if kind == "stubs" else "stubs" + assert fresh.snapshot(case.id, other_kind) == original.snapshot( + case.id, other_kind + ) + for other_kind in ("stubs", "errors"): + assert fresh.snapshot("unrelated", other_kind) == original.snapshot( + "unrelated", other_kind + ) + assert (case.repo / ".git/index").read_bytes() == b"index sentinel" + assert messages and str(case.catalog_path) in messages[0] else: with pytest.raises(h.HarnessError, match="Changed:"): run_test(case, tmp_path / "work", False, None, messages.append) - assert h.read_tree(reference) == {filename: b"original"} + assert tree_state(case.repo) == before assert messages == [] @@ -847,8 +885,8 @@ def test_empty_output_cannot_erase_references(tmp_path, monkeypatch, empty_at): case = h.DemoCase( tmp_path / "repo", (3, 13), "v3.0", "numpy-array-wrap-with-annotated" ) - reference = case.stubs_root / case.stub_profile - write_files(reference, {"demo/__init__.pyi": b"original"}) + seed_catalog(case) + before = tree_state(case.repo) def generate(argv, **kwargs): if empty_at == "formatter": @@ -860,9 +898,14 @@ def erase_output(output, repo, workspace, python_version): monkeypatch.setattr(integration, "run_command", generate) monkeypatch.setattr(integration, "format_stubs", erase_output) - with pytest.raises(h.HarnessError, match="demo/__init__.pyi"): + expected_diagnostic = ( + "Successful demo generation did not produce demo/__init__.pyi" + if empty_at == "generator" + else "Normalized output lost demo/__init__.pyi" + ) + with pytest.raises(h.HarnessError, match=expected_diagnostic): integration.test_demo_stubs(case, tmp_path / "work", True, None, print) - assert h.read_tree(reference) == {"demo/__init__.pyi": b"original"} + assert tree_state(case.repo) == before def test_entry_points_use_pytest_without_legacy_mutating_checks(): diff --git a/tests/unit/test_snapshot_catalog.py b/tests/unit/test_snapshot_catalog.py index 439856f4..cdc2d4da 100644 --- a/tests/unit/test_snapshot_catalog.py +++ b/tests/unit/test_snapshot_catalog.py @@ -23,6 +23,49 @@ from snapshot_test_support import tree_state, write_catalog +def test_shipped_catalog_has_complete_literal_coverage_and_shared_payloads(): + catalog = load_catalog(Path(__file__).resolve().parents[2]) + annotated = "numpy-array-wrap-with-annotated" + type_var = "numpy-array-use-type-var" + cases = [ + ((3, 10), "v3.0", annotated), + ((3, 11), "v3.0", annotated), + ((3, 12), "v3.0", annotated), + ((3, 13), "v3.0", annotated), + ((3, 13), "v3.0", type_var), + ((3, 10), "v2.13", annotated), + ((3, 11), "v2.13", annotated), + ((3, 12), "v2.13", annotated), + ((3, 13), "v2.13", annotated), + ((3, 13), "v2.13", type_var), + ((3, 13), "v2.9", annotated), + ((3, 13), "v2.11", annotated), + ((3, 13), "v2.12", annotated), + ] + assert set(catalog.cases) == { + f"python-{major}.{minor}-pybind11-{branch}-{mode}" + for (major, minor), branch, mode in cases + } + for kind, required in ( + ("stubs", "demo/__init__.pyi"), + ("errors", "demo.errors.stderr.txt"), + ): + referenced = set() + variants = {} + for case in catalog.cases: + mapping = catalog.mapping(case, kind) + assert required in mapping + for name, reference in mapping.items(): + referenced.add(reference) + key = (name, catalog.pools[kind][reference]) + assert variants.setdefault(key, reference) == reference + assert referenced == set(catalog.pools[kind]) + assert all( + entry[0] in ("file", "dir") + for entry in tree_state(catalog.repo / "tests" / kind).values() + ) + + def sample(tmp_path): cases = { "a": {"stubs": {"demo/a.pyi": "shared/a.pyi"}, "errors": {}}, diff --git a/tests/unit/test_snapshot_native_glue.py b/tests/unit/test_snapshot_native_glue.py new file mode 100644 index 00000000..fd2f6d25 --- /dev/null +++ b/tests/unit/test_snapshot_native_glue.py @@ -0,0 +1,143 @@ +import subprocess + +import pytest + +from snapshot_catalog import load_catalog +from snapshot_helpers import DemoCase, HarnessError, diagnostics, make_case +from snapshot_test_support import tree_state, write_catalog + + +def native_repo(tmp_path): + cases = [ + DemoCase(tmp_path / "repo", (3, 13), "v3.0", mode) + for mode in ("numpy-array-wrap-with-annotated", "numpy-array-use-type-var") + ] + write_catalog( + cases[0].repo, + { + case.id: { + "stubs": {"demo/__init__.pyi": "seed/init.pyi"}, + "errors": {"demo.errors.stderr.txt": "seed/stderr.txt"}, + } + for case in cases + }, + { + "stubs": {"seed/init.pyi": b"old"}, + "errors": {"seed/stderr.txt": b"errors"}, + }, + ) + return cases + + +@pytest.mark.parametrize("fault", ["missing", "unknown", "symlink"]) +def test_selection_preflight_names_case_catalog_and_keeps_cause(tmp_path, fault): + case, _ = native_repo(tmp_path) + path = case.repo / "tests/snapshot_cases.toml" + if fault == "missing": + path.unlink() + elif fault == "unknown": + case = DemoCase(case.repo, (3, 12), case.branch, case.numpy_format) + else: + saved = path.with_suffix(".saved") + path.rename(saved) + path.symlink_to(saved) + before = tree_state(case.repo) + with pytest.raises(HarnessError) as exc: + make_case(case.repo, case.python_version, case.branch, case.numpy_format) + assert case.id in str(exc.value) + assert str(path) in str(exc.value) + assert exc.value.__cause__ is not None + assert tree_state(case.repo) == before + + +@pytest.mark.parametrize("location", ["catalog", "parent", "artifact-symlink"]) +def test_catalog_is_protected_before_native_reference_writes( + tmp_path, monkeypatch, location +): + import test_demo_stubs as integration + + case, _ = native_repo(tmp_path) + catalog = case.repo / "tests/snapshot_cases.toml" + workspace = tmp_path / "work" + artifacts = None + if location == "catalog": + workspace = catalog + elif location == "parent": + workspace = catalog.parent + else: + alias = tmp_path / "catalog-alias" + alias.symlink_to(catalog) + artifacts = alias / "nested" + before = tree_state(case.repo) + + def unexpected_process(*args, **kwargs): + pytest.fail("process started before reference protection") + + monkeypatch.setattr(integration, "run_command", unexpected_process) + with pytest.raises(HarnessError, match="reference tree"): + integration.test_demo_stubs(case, workspace, True, artifacts, print) + assert tree_state(case.repo) == before + + +def test_diagnostic_details_survive_secondary_write_failure(tmp_path, monkeypatch): + import snapshot_helpers as helpers + + case, _ = native_repo(tmp_path) + catalog = load_catalog(case.repo) + details = catalog.context(case.id, "stubs") + original = helpers._write_diagnostic + + def fail_summary(path, content): + if path.name == "failure.txt": + raise OSError("secondary diagnostic failure") + original(path, content) + + monkeypatch.setattr(helpers, "_write_diagnostic", fail_summary) + primary = HarnessError("generator failed") + with pytest.raises(HarnessError) as exc: + with diagnostics( + tmp_path / "work", + artifacts=None, + reference_roots=(catalog.path, case.stubs_root, case.errors_root), + case_id=case.id, + check_name="stubs", + expected=catalog.path, + reference_details=details, + ): + raise primary + assert exc.value.__cause__ is primary + assert details in str(exc.value) + assert "secondary diagnostic failure" in str(exc.value) + assert details in (tmp_path / "work/context.txt").read_text() + + +def test_two_native_modes_update_serially_with_fresh_mappings(tmp_path, monkeypatch): + import test_demo_stubs as integration + + first, second = native_repo(tmp_path) + before = load_catalog(first.repo) + + def generate(argv, **kwargs): + target = kwargs["cwd"] / "output/demo/__init__.pyi" + target.parent.mkdir(parents=True) + target.write_bytes(b"new") + return subprocess.CompletedProcess(argv, 0, b"", b"") + + monkeypatch.setattr(integration, "run_command", generate) + monkeypatch.setattr(integration, "format_stubs", lambda *args: None) + messages = [] + integration.test_demo_stubs(first, tmp_path / "first", True, None, messages.append) + intermediate = load_catalog(first.repo) + assert intermediate.snapshot(second.id, "stubs") == {"demo/__init__.pyi": b"old"} + integration.test_demo_stubs( + second, tmp_path / "second", True, None, messages.append + ) + after = load_catalog(first.repo) + for case in (first, second): + assert after.snapshot(case.id, "stubs") == {"demo/__init__.pyi": b"new"} + assert after.mapping(case.id, "stubs") == intermediate.mapping( + first.id, "stubs" + ) + assert after.snapshot(case.id, "errors") == before.snapshot(case.id, "errors") + assert len(after.pools["stubs"]) == 1 + assert len(messages) == 2 From 6f27dfc3c3de9147f05f59806f1f3d18294665e1 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Sat, 26 Sep 2026 01:18:37 +0000 Subject: [PATCH 39/40] docs: document shared snapshots and copy-on-write updates --- tests/README.md | 123 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 95 insertions(+), 28 deletions(-) diff --git a/tests/README.md b/tests/README.md index 74ee7992..d5b5c97b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -3,9 +3,10 @@ The native demo models a C++ library (`demo-lib`), its bindings (`py-demo/bindings`), and a mixed Python/native package (`py-demo`). -Checks generate output in temporary directories and compare it with `stubs/` -and `errors/`. They never modify references or stage files. Updating references -is a separate, explicit operation. +Checks generate output in temporary directories and compare logical filenames +and bytes using `snapshot_cases.toml`, with shared payloads in `stubs/` and +`errors/`. Normal checks and artifact retention never modify these references +or stage files. Updating references is a separate, explicit operation. ## Prerequisites @@ -31,9 +32,9 @@ python -m pytest tests/test_snapshot_helpers.py Or let uv supply test dependencies, without syncing the project's native dependencies: ```sh -uv run --no-project --isolated --with 'pytest>=8,<9' \ - --with 'tomli>=2,<3; python_version < "3.11"' --with 'tomlkit>=0.13,<1' \ - python -m pytest tests/test_snapshot_helpers.py +env -u VIRTUAL_ENV uv run --no-project --isolated --python 3.10 \ + --with 'pytest>=8,<9' --with 'tomli>=2,<3; python_version < "3.11"' \ + --with 'tomlkit>=0.13,<1' python -m pytest tests/test_snapshot_helpers.py ``` ## Compiler-free production tests @@ -45,9 +46,20 @@ is needed. The following command tests source edits directly without syncing the development dependency group: ```sh -uv run --no-project --isolated --with 'pytest>=8,<9' \ - --with 'tomli>=2,<3; python_version < "3.11"' --with 'tomlkit>=0.13,<1' \ - python -m pytest tests/unit tests/test_snapshot_helpers.py +env -u VIRTUAL_ENV uv run --no-project --isolated --python 3.10 \ + --with 'pytest>=8,<9' --with 'tomli>=2,<3; python_version < "3.11"' \ + --with 'tomlkit>=0.13,<1' python -m pytest tests/unit tests/test_snapshot_helpers.py +``` + +The unit suite also covers catalog reading and copy-on-write editing. Reads +use stdlib `tomllib` on Python 3.11+ and `tomli` on 3.10; only real catalog edits +import `tomlkit`. Read-only checks and no-op updates do not import the editor +library. Focused production tests that do not exercise catalog editing remain +possible with pytest alone, for example: + +```sh +env -u VIRTUAL_ENV uv run --no-project --isolated --python 3.10 \ + --with 'pytest>=8,<9' python -m pytest tests/unit/test_writer.py ``` To test a non-editable installation on each supported matrix interpreter: @@ -108,36 +120,89 @@ Each profile owns its build directories and diagnostics. Checks can use bounded parallel tox execution after the isolation acceptance test; they do not require pytest workers. Reference updates remain explicit and serial. +## Catalog and shared payloads + +`snapshot_cases.toml` contains `format = 1` and an explicit table for every +complete case ID. Each case has exactly two tables, `stubs` and `errors`, mapping +quoted logical filenames to literal, case-sensitive payload paths relative to +the corresponding root. There are no patterns, fallback versions, overlays, +aliases, or implicit common entries. Missing cases and missing mapped files are +errors; extra unreferenced regular payloads are inert. Symlinks are rejected. + +For example, a stub entry can map `"demo/_bindings/numpy.pyi"` to +`"demo/_bindings/numpy/py310-pb213-annotated.pyi"`. Labels such as `shared` or +`py310-pb213-annotated` describe payload origins, not selectors: the catalog alone +selects the bytes, and another case may reuse that payload without renaming it. +Sharing is within one kind and logical filename, not between unrelated outputs. + ## Update references deliberately ```sh tox -e py313-pb30 -- --numpy-format numpy-array-wrap-with-annotated --update-snapshots -git diff -- tests/stubs tests/errors +git diff -- tests/snapshot_cases.toml tests/stubs tests/errors ``` -Do not update shared references concurrently. Existing aliases and per-test, -nontransactional update semantics are unchanged. +Run updates serially, including across tox processes. Use `-k demo_stubs` or +`-k demo_errors` with the update option to select one kind of expectation. +Generation, exit-status, traceback, fatal-run, no-output, required +`demo/__init__.pyi`, and formatting assertions still apply before mutation. +A broken command cannot become a new expectation. + +Reading may share payloads; updating is case-scoped copy-on-write. Only the +selected case/kind changes; other cases and the other kind keep their effective +bytes. Unchanged entries keep their mappings. Changed files reuse an already +referenced variant with identical bytes for that kind/logical filename (the +lexicographically first path if several match). Otherwise new payloads go under +`variants///` in the relevant root, using the first unused positive +number and the logical filenames. Existing payloads are never overwritten. + +Full stub updates match the generated file set. Scoped error updates affect only +the selected logical filenames, including deletions. After publishing the new +catalog, cleanup removes only displaced payloads that no case still references; +it never sweeps unrelated leftovers. No-op updates preserve every catalog and +payload byte, without reserialization, renaming, deduplication, or cleanup. +Repeated serial updates can reuse variants published by earlier updates. + +Reports identify the catalog, case/kind, logical changes, old/new physical +references, and created/reused/removed payloads. Review all three reference +locations above. Nothing is automatically staged or committed. + +New payloads are written before atomic catalog replacement. A failure before +publication leaves the old mappings and previously referenced payloads intact, +but new unreferenced payloads or a temporary catalog may remain; diagnostics +identify their locations. A detected intervening catalog edit is preserved, +not rolled back. A cleanup failure after publication explicitly reports that +the new mapping is already published. Inspect the reported paths and catalog +before any manual recovery; there is no rollback promise or bulk cleanup command. + +Updates are per test, not transactional across files or a run, and do not promise +power-loss durability or concurrent-writer support. If a later check fails, +earlier successful checks may already have updated their references. Always +review the diff; do not accept unexplained changes from a bulk regeneration. -Use `-k demo_stubs` or `-k demo_errors` with the update option to select one kind -of expectation. Generation, exit-status, no-output, and formatting assertions -still apply in update mode. A broken command cannot become a new expectation. +To bootstrap a deliberately added supported case, declare both kind tables +explicitly (empty tables are valid expectations), then run validated updates: -Updates report their canonical destination and changed paths. Some directories -are aliases (for example, Python 3.13 uses the Python 3.12 reference directory). -An update to shared expectations affects every profile using that directory; -review and recheck those profiles. Aliases are preserved, and unrelated profiles -are not rewritten. Nothing is automatically staged or committed. +```toml +[cases."python-3.13-pybind11-v3.0-numpy-array-wrap-with-annotated".stubs] -Updates are per test, not transactional across a run. If a later check fails, -earlier successful checks may already have updated their references. Always -review the diff; do not accept unexplained changes from a bulk regeneration. +[cases."python-3.13-pybind11-v3.0-numpy-array-wrap-with-annotated".errors] +``` + +Use the new case's complete ID, not the already populated example ID above. +Unknown cases are never created implicitly, and invalid/missing mapped payloads +must be repaired rather than blessed by an update. Adding a catalog case does +not schedule it in tox: execution coverage remains a separate, deliberate tox +configuration change. ## Failures and artifacts -Failures show the configuration, resolved reference location, file-set changes, -content diffs, and process diagnostics. Each check uses a fresh temporary output -directory. Optional `--artifacts-dir PATH` retains failure output outside the -reference trees. Pytest diagnostics remain in the environment's +Failures show the configuration, case ID, kind, catalog/TOML entry, logical +filename and canonical payload path (or an explicit unmapped output), file-set +changes, content diffs, and process diagnostics. Each check uses a fresh temporary +output directory. Optional `--artifacts-dir PATH` retains failure output outside the +reference trees and catalog. Neither diagnostic workspaces nor artifact paths +may overlap those locations. Pytest diagnostics remain in the environment's `tmp/pytest-artifacts/`, with distinct case/check/run directories. Build/install logs and fixture provenance are under @@ -147,7 +212,9 @@ upload includes these scoped logs and reports, not entire virtual environments. ## Add a regression -For harness behavior, add a small synthetic test to `test_snapshot_helpers.py`. +For generic harness behavior, add a small synthetic test to +`test_snapshot_helpers.py`; catalog/editor contracts belong in +`tests/unit/test_snapshot_catalog.py` and `tests/unit/test_snapshot_updates.py`. For compiler-free generator behavior, prefer a focused test in `tests/unit/` with explicit expected models or text. Neither route requires a compiler. From ced7244133d1e9f388450402bdd16b23d85349fa Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Sat, 26 Sep 2026 01:36:18 +0000 Subject: [PATCH 40/40] test: support empty snapshot catalog fixtures --- tests/snapshot_test_support.py | 2 ++ tests/unit/test_snapshot_catalog.py | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/tests/snapshot_test_support.py b/tests/snapshot_test_support.py index c646622d..4a1c8f28 100644 --- a/tests/snapshot_test_support.py +++ b/tests/snapshot_test_support.py @@ -38,6 +38,8 @@ def fail(error): def write_catalog(repo: Path, cases: Cases, payloads: dict[str, Snapshot]) -> Path: lines = ["format = 1", ""] + if not cases: + lines.extend(["[cases]", ""]) for kind in ("stubs", "errors"): root = repo / "tests" / kind root.mkdir(parents=True, exist_ok=True) diff --git a/tests/unit/test_snapshot_catalog.py b/tests/unit/test_snapshot_catalog.py index cdc2d4da..637ddae8 100644 --- a/tests/unit/test_snapshot_catalog.py +++ b/tests/unit/test_snapshot_catalog.py @@ -81,6 +81,17 @@ def sample(tmp_path): return repo +def test_empty_catalog_fixture_round_trips(tmp_path): + repo = write_catalog(tmp_path / "repo", {}, {}) + + catalog = load_catalog(repo) + + assert catalog.cases == {} + assert catalog.pools == {"stubs": {}, "errors": {}} + assert (repo / "tests/stubs").is_dir() + assert (repo / "tests/errors").is_dir() + + def test_literal_selection_and_read_only_failure(tmp_path): repo = sample(tmp_path) before = tree_state(repo)