blob: 1ac87d0ef77cb4d5cdcd6543bb84fefe03943635 [file]
"""Tests for targeting a Python build other than the running one."""
from __future__ import annotations
import os
import tempfile
import unittest
from mypyc.build import get_mypy_config
from mypyc.codegen.emit import Emitter, EmitterContext
from mypyc.codegen.emitmodule import GroupGenerator
from mypyc.common import EXT_SUFFIX
from mypyc.ir.rtypes import RUnion, list_rprimitive, object_rprimitive
from mypyc.namegen import NameGenerator
from mypyc.options import CompilerOptions, TargetPython, replace_ext_suffix_version
from mypyc.test.testutil import infer_target_python_from_test_name
class TestTargetPython(unittest.TestCase):
def test_parse(self) -> None:
assert TargetPython.parse("3.13") == TargetPython((3, 13), False)
assert TargetPython.parse("3.14t") == TargetPython((3, 14), True)
assert TargetPython.parse("3.10") == TargetPython((3, 10), False)
def test_parse_invalid(self) -> None:
for target in ["3", "3.13x", "313", "2.7", "4.0", "3.9", "3.12t", "t", "3.10t"]:
with self.assertRaises(ValueError, msg=target):
TargetPython.parse(target)
def test_str(self) -> None:
assert str(TargetPython((3, 13), False)) == "3.13"
assert str(TargetPython((3, 14), True)) == "3.14t"
def test_have_immortal(self) -> None:
assert not TargetPython((3, 11)).have_immortal
assert TargetPython((3, 12)).have_immortal
def test_ext_suffix(self) -> None:
assert TargetPython.host().ext_suffix == EXT_SUFFIX
for host, target, expected in [
(".cpython-313-x86_64-linux-gnu.so", "3.14t", ".cpython-314t-x86_64-linux-gnu.so"),
(".cpython-314t-darwin.so", "3.13", ".cpython-313-darwin.so"),
(".cp313-win_amd64.pyd", "3.14t", ".cp314t-win_amd64.pyd"),
("_d.cp313t-win_amd64.pyd", "3.12", "_d.cp312-win_amd64.pyd"),
]:
parsed = TargetPython.parse(target)
result = replace_ext_suffix_version(host, parsed.version, parsed.free_threaded)
assert result == expected
for suffix in [".so", ".abi3.so", ".pypy310-pp73-x86_64-linux-gnu.so"]:
with self.assertRaises(ValueError):
replace_ext_suffix_version(suffix, (3, 14), True)
def test_may_be_immortal(self) -> None:
old, new = TargetPython((3, 11)), TargetPython((3, 12))
assert not object_rprimitive.may_be_immortal(old)
assert object_rprimitive.may_be_immortal(new)
assert not list_rprimitive.may_be_immortal(new)
assert RUnion([list_rprimitive, object_rprimitive]).may_be_immortal(new)
def test_emit_ref_count_ops(self) -> None:
def emit(target: TargetPython) -> str:
emitter = Emitter(EmitterContext(NameGenerator([["mod"]]), True, target_python=target))
emitter.emit_inc_ref("x", list_rprimitive)
emitter.emit_dec_ref("x", list_rprimitive)
return "".join(emitter.fragments)
assert emit(TargetPython((3, 11))) == "CPy_INCREF(x);\nCPy_DECREF(x);\n"
assert emit(TargetPython((3, 12))) == "CPy_INCREF_NO_IMM(x);\nCPy_DECREF_NO_IMM(x);\n"
def test_invalid_target(self) -> None:
for version, free_threaded in [((3, 9), False), ((2, 7), False), ((3, 12), True)]:
with self.assertRaises(ValueError):
TargetPython(version, free_threaded)
def test_infer_target_python_from_test_name(self) -> None:
assert infer_target_python_from_test_name("testFoo") == TargetPython((3, 10))
assert infer_target_python_from_test_name("testFoo_withgil") == TargetPython((3, 10))
assert infer_target_python_from_test_name("testFoo_nogil") == TargetPython((3, 13), True)
assert infer_target_python_from_test_name("testFoo_python3_14_nogil") == TargetPython(
(3, 14), True
)
assert infer_target_python_from_test_name("testFoo_python3_12") == TargetPython((3, 12))
def test_module_def_slots(self) -> None:
def slots(target: TargetPython) -> str:
context = EmitterContext(NameGenerator([["mod"]]), True, target_python=target)
emitter = Emitter(context)
generator = GroupGenerator({}, {}, None, {}, context.names, CompilerOptions())
generator.emit_module_def_slots(emitter, "prefix", "mod")
return "".join(emitter.fragments)
assert "Py_mod_gil" not in slots(TargetPython((3, 12)))
assert "Py_mod_multiple_interpreters" in slots(TargetPython((3, 12)))
assert "Py_mod_gil" in slots(TargetPython((3, 13)))
def test_mypy_python_version_follows_target(self) -> None:
# This is used by mypyc_build(), which is how a target is selected.
with tempfile.TemporaryDirectory() as tmp:
path = os.path.join(tmp, "a.py")
with open(path, "w") as f:
f.write("x = 1\n")
options = CompilerOptions(target_python=TargetPython((3, 14), True))
_, _, mypy_options = get_mypy_config([path], None, options, None)
assert mypy_options.python_version == (3, 14)