1+ from contextlib import contextmanager
12import json
23import os
34import re
78from functools import lru_cache
89from glob import glob
910from pathlib import Path
10- from typing import Any , Callable , Dict , List , Optional , Tuple
11+ from typing import Any , Callable , Dict , Iterator , List , Optional , Tuple
1112
1213import nox
14+ import nox .command
1315
1416nox .options .sessions = ["test" , "clippy" , "rustfmt" , "ruff" , "docs" ]
1517
@@ -100,7 +102,7 @@ def _clippy(session: nox.Session, *, env: Dict[str, str] = None) -> bool:
100102 "--deny=warnings" ,
101103 env = env ,
102104 )
103- except Exception :
105+ except nox . command . CommandFailed :
104106 success = False
105107 return success
106108
@@ -552,6 +554,33 @@ def ffi_check(session: nox.Session):
552554 _run_cargo (session , "run" , _FFI_CHECK )
553555
554556
557+ @nox .session (name = "test-version-limits" )
558+ def test_version_limits (session : nox .Session ):
559+ env = os .environ .copy ()
560+ with _config_file () as config_file :
561+ env ["PYO3_CONFIG_FILE" ] = config_file .name
562+
563+ assert "3.6" not in PY_VERSIONS
564+ config_file .set ("CPython" , "3.6" )
565+ _run_cargo (session , "check" , env = env , expect_error = True )
566+
567+ assert "3.13" not in PY_VERSIONS
568+ config_file .set ("CPython" , "3.13" )
569+ _run_cargo (session , "check" , env = env , expect_error = True )
570+
571+ # 3.13 CPython should build with forward compatibility
572+ env ["PYO3_USE_ABI3_FORWARD_COMPATIBILITY" ] = "1"
573+ _run_cargo (session , "check" , env = env )
574+
575+ assert "3.6" not in PYPY_VERSIONS
576+ config_file .set ("PyPy" , "3.6" )
577+ _run_cargo (session , "check" , env = env , expect_error = True )
578+
579+ assert "3.11" not in PYPY_VERSIONS
580+ config_file .set ("PyPy" , "3.11" )
581+ _run_cargo (session , "check" , env = env , expect_error = True )
582+
583+
555584def _build_docs_for_ffi_check (session : nox .Session ) -> None :
556585 # pyo3-ffi-check needs to scrape docs of pyo3-ffi
557586 _run_cargo (session , "doc" , _FFI_CHECK , "-p" , "pyo3-ffi" , "--no-deps" )
@@ -640,7 +669,13 @@ def _run(session: nox.Session, *args: str, **kwargs: Any) -> None:
640669 print ("::endgroup::" , file = sys .stderr )
641670
642671
643- def _run_cargo (session : nox .Session , * args : str , ** kwargs : Any ) -> None :
672+ def _run_cargo (
673+ session : nox .Session , * args : str , expect_error : bool = False , ** kwargs : Any
674+ ) -> None :
675+ if expect_error :
676+ if "success_codes" in kwargs :
677+ raise ValueError ("expect_error overrides success_codes" )
678+ kwargs ["success_codes" ] = [101 ]
644679 _run (session , "cargo" , * args , ** kwargs , external = True )
645680
646681
@@ -688,24 +723,14 @@ def _get_output(*args: str) -> str:
688723def _for_all_version_configs (
689724 session : nox .Session , job : Callable [[Dict [str , str ]], None ]
690725) -> None :
691- with tempfile .NamedTemporaryFile ("r+" ) as config :
692- env = os .environ .copy ()
693- env ["PYO3_CONFIG_FILE" ] = config .name
694-
695- def _job_with_config (implementation , version ) -> bool :
696- config .seek (0 )
697- config .truncate (0 )
698- config .write (
699- f"""\
700- implementation={ implementation }
701- version={ version }
702- suppress_build_script_link_lines=true
703- """
704- )
705- config .flush ()
726+ env = os .environ .copy ()
727+ with _config_file () as config_file :
728+ env ["PYO3_CONFIG_FILE" ] = config_file .name
706729
730+ def _job_with_config (implementation , version ):
707731 session .log (f"{ implementation } { version } " )
708- return job (env )
732+ config_file .set (implementation , version )
733+ job (env )
709734
710735 for version in PY_VERSIONS :
711736 _job_with_config ("CPython" , version )
@@ -714,5 +739,34 @@ def _job_with_config(implementation, version) -> bool:
714739 _job_with_config ("PyPy" , version )
715740
716741
742+ class _ConfigFile :
743+ def __init__ (self , config_file ) -> None :
744+ self ._config_file = config_file
745+
746+ def set (self , implementation : str , version : str ) -> None :
747+ """Set the contents of this config file to the given implementation and version."""
748+ self ._config_file .seek (0 )
749+ self ._config_file .truncate (0 )
750+ self ._config_file .write (
751+ f"""\
752+ implementation={ implementation }
753+ version={ version }
754+ suppress_build_script_link_lines=true
755+ """
756+ )
757+ self ._config_file .flush ()
758+
759+ @property
760+ def name (self ) -> str :
761+ return self ._config_file .name
762+
763+
764+ @contextmanager
765+ def _config_file () -> Iterator [_ConfigFile ]:
766+ """Creates a temporary config file which can be repeatedly set to different values."""
767+ with tempfile .NamedTemporaryFile ("r+" ) as config :
768+ yield _ConfigFile (config )
769+
770+
717771_BENCHES = "--manifest-path=pyo3-benches/Cargo.toml"
718772_FFI_CHECK = "--manifest-path=pyo3-ffi-check/Cargo.toml"
0 commit comments