Skip to content

Commit bbc9743

Browse files
committed
Add sentinel modules and mypyc infrastructure tests
- Add sentinel modules to verify import hook redirection - Add 22 tests covering detection, activation, hook behavior - Sentinel includes benchmark functions (fibonacci, sum_squares)
1 parent edbcdc1 commit bbc9743

4 files changed

Lines changed: 345 additions & 0 deletions

File tree

src/graphql/_sentinel.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Sentinel module for mypyc compilation testing (interpreted version).
2+
3+
This is the interpreted fallback version. When graphql_mypyc is installed
4+
and the import hook is active, this module is replaced with the compiled version.
5+
"""
6+
7+
from __future__ import annotations
8+
9+
__all__ = ["SENTINEL_VALUE", "add_numbers", "fibonacci", "is_compiled", "sum_squares"]
10+
11+
SENTINEL_VALUE: str = "interpreted"
12+
13+
14+
def is_compiled() -> bool:
15+
"""Return True if this module was compiled with mypyc."""
16+
return False
17+
18+
19+
def add_numbers(a: int, b: int) -> int:
20+
"""Simple function to test mypyc compilation."""
21+
return a + b
22+
23+
24+
def fibonacci(n: int) -> int:
25+
"""Compute fibonacci number recursively."""
26+
if n <= 1:
27+
return n
28+
return fibonacci(n - 1) + fibonacci(n - 2)
29+
30+
31+
def sum_squares(n: int) -> int:
32+
"""Sum of squares from 1 to n."""
33+
total = 0
34+
for i in range(1, n + 1):
35+
total += i * i
36+
return total

src/graphql_mypyc/_sentinel.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""Sentinel module for mypyc compilation testing.
2+
3+
This module is compiled with mypyc to verify the compilation infrastructure
4+
works correctly. It will be removed once real modules are compiled.
5+
"""
6+
7+
from __future__ import annotations
8+
9+
__all__ = ["SENTINEL_VALUE", "add_numbers", "fibonacci", "is_compiled", "sum_squares"]
10+
11+
# A constant value that can be checked
12+
SENTINEL_VALUE: str = "mypyc-compiled"
13+
14+
15+
def is_compiled() -> bool:
16+
"""Return True if this module was compiled with mypyc.
17+
18+
Checks if the module is loaded from a .so extension file.
19+
"""
20+
return __file__.endswith(".so")
21+
22+
23+
def add_numbers(a: int, b: int) -> int:
24+
"""Simple function to test mypyc compilation.
25+
26+
This is a simple pure function that benefits from mypyc compilation.
27+
"""
28+
return a + b
29+
30+
31+
def fibonacci(n: int) -> int:
32+
"""Compute fibonacci number recursively.
33+
34+
This is intentionally slow to show mypyc speedup.
35+
"""
36+
if n <= 1:
37+
return n
38+
return fibonacci(n - 1) + fibonacci(n - 2)
39+
40+
41+
def sum_squares(n: int) -> int:
42+
"""Sum of squares from 1 to n.
43+
44+
Loop-based computation that benefits from mypyc.
45+
"""
46+
total = 0
47+
for i in range(1, n + 1):
48+
total += i * i
49+
return total

tests/mypyc/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Tests for mypyc compilation infrastructure."""
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
"""Tests for mypyc compilation infrastructure."""
2+
3+
from __future__ import annotations
4+
5+
6+
def describe_mypyc_detection():
7+
"""Tests for mypyc detection utilities."""
8+
9+
def exports_detection_functions():
10+
"""The graphql module exports mypyc detection functions."""
11+
import graphql
12+
13+
assert hasattr(graphql, "is_mypyc_enabled")
14+
assert hasattr(graphql, "get_mypyc_modules")
15+
assert callable(graphql.is_mypyc_enabled)
16+
assert callable(graphql.get_mypyc_modules)
17+
18+
def is_mypyc_enabled_returns_bool():
19+
"""is_mypyc_enabled returns a boolean."""
20+
import graphql
21+
22+
result = graphql.is_mypyc_enabled()
23+
assert isinstance(result, bool)
24+
25+
def get_mypyc_modules_returns_frozenset():
26+
"""get_mypyc_modules returns a frozenset of strings."""
27+
import graphql
28+
29+
result = graphql.get_mypyc_modules()
30+
assert isinstance(result, frozenset)
31+
32+
33+
def describe_mypyc_module():
34+
"""Tests for the graphql_mypyc module."""
35+
36+
def can_import_graphql_mypyc():
37+
"""The graphql_mypyc module can be imported."""
38+
import graphql_mypyc
39+
40+
assert graphql_mypyc is not None
41+
42+
def exports_activate_deactivate():
43+
"""The graphql_mypyc module exports activate/deactivate functions."""
44+
import graphql_mypyc
45+
46+
assert hasattr(graphql_mypyc, "activate")
47+
assert hasattr(graphql_mypyc, "deactivate")
48+
assert hasattr(graphql_mypyc, "is_active")
49+
assert callable(graphql_mypyc.activate)
50+
assert callable(graphql_mypyc.deactivate)
51+
assert callable(graphql_mypyc.is_active)
52+
53+
def exports_compiled_modules_set():
54+
"""The graphql_mypyc module exports COMPILED_MODULES."""
55+
import graphql_mypyc
56+
57+
assert hasattr(graphql_mypyc, "COMPILED_MODULES")
58+
assert isinstance(graphql_mypyc.COMPILED_MODULES, frozenset)
59+
60+
def exports_version():
61+
"""The graphql_mypyc module exports __version__."""
62+
import graphql_mypyc
63+
64+
assert hasattr(graphql_mypyc, "__version__")
65+
assert isinstance(graphql_mypyc.__version__, str)
66+
67+
68+
def describe_mypyc_activation():
69+
"""Tests for mypyc activation/deactivation."""
70+
71+
def activation_is_idempotent():
72+
"""Calling activate multiple times is safe."""
73+
import graphql_mypyc
74+
75+
# Should already be active from auto-activation
76+
result1 = graphql_mypyc.activate()
77+
result2 = graphql_mypyc.activate()
78+
assert result1 is True
79+
assert result2 is True
80+
81+
def deactivation_works():
82+
"""Deactivation disables mypyc mode."""
83+
import graphql
84+
import graphql_mypyc
85+
86+
# Deactivate
87+
graphql_mypyc.deactivate()
88+
assert graphql_mypyc.is_active() is False
89+
assert graphql.is_mypyc_enabled() is False
90+
91+
# Reactivate for other tests
92+
graphql_mypyc.activate()
93+
assert graphql_mypyc.is_active() is True
94+
assert graphql.is_mypyc_enabled() is True
95+
96+
def deactivation_is_idempotent():
97+
"""Calling deactivate multiple times is safe."""
98+
import graphql_mypyc
99+
100+
# Deactivate twice
101+
graphql_mypyc.deactivate()
102+
result1 = graphql_mypyc.deactivate()
103+
result2 = graphql_mypyc.deactivate()
104+
assert result1 is True
105+
assert result2 is True
106+
107+
# Reactivate for other tests
108+
graphql_mypyc.activate()
109+
110+
111+
def describe_mypyc_auto_activation():
112+
"""Tests for automatic mypyc activation on import."""
113+
114+
def auto_activates_when_graphql_mypyc_available():
115+
"""Auto-activates mypyc when graphql_mypyc is installed."""
116+
# Since graphql_mypyc is in our src/, it's always available
117+
# and should be auto-activated when graphql is imported
118+
import graphql
119+
120+
# The module should report as enabled (hook is installed)
121+
# Note: This tests the hook infrastructure, not actual compilation
122+
assert graphql.is_mypyc_enabled() is True
123+
124+
def compiled_modules_reflects_current_state():
125+
"""get_mypyc_modules returns the currently compiled modules."""
126+
import graphql
127+
import graphql_mypyc
128+
129+
# The compiled modules set should match what's registered
130+
modules = graphql.get_mypyc_modules()
131+
assert modules == graphql_mypyc.COMPILED_MODULES
132+
133+
134+
def describe_import_hook():
135+
"""Tests for the import hook mechanism."""
136+
137+
def hook_module_exports():
138+
"""The _hook module exports expected functions."""
139+
from graphql_mypyc._hook import (
140+
COMPILED_MODULES,
141+
install_hook,
142+
is_hook_installed,
143+
uninstall_hook,
144+
)
145+
146+
assert isinstance(COMPILED_MODULES, frozenset)
147+
assert callable(install_hook)
148+
assert callable(uninstall_hook)
149+
assert callable(is_hook_installed)
150+
151+
def hook_is_installed_after_activation():
152+
"""The import hook is installed after activation."""
153+
import graphql_mypyc
154+
from graphql_mypyc._hook import is_hook_installed
155+
156+
graphql_mypyc.activate()
157+
assert is_hook_installed() is True
158+
159+
def hook_is_removed_after_deactivation():
160+
"""The import hook is removed after deactivation."""
161+
import graphql_mypyc
162+
from graphql_mypyc._hook import is_hook_installed
163+
164+
graphql_mypyc.deactivate()
165+
assert is_hook_installed() is False
166+
167+
# Reactivate for other tests
168+
graphql_mypyc.activate()
169+
170+
def compiled_modules_contains_sentinel():
171+
"""COMPILED_MODULES includes the sentinel module."""
172+
from graphql_mypyc._hook import COMPILED_MODULES
173+
174+
assert "graphql._sentinel" in COMPILED_MODULES
175+
176+
177+
def describe_sentinel_module():
178+
"""Tests for the sentinel module that validates the import hook."""
179+
180+
def graphql_sentinel_exists():
181+
"""The graphql._sentinel module can be imported."""
182+
from graphql import _sentinel
183+
184+
assert _sentinel is not None
185+
186+
def graphql_mypyc_sentinel_exists():
187+
"""The graphql_mypyc._sentinel module can be imported."""
188+
from graphql_mypyc import _sentinel
189+
190+
assert _sentinel is not None
191+
192+
def sentinel_has_expected_exports():
193+
"""The sentinel module exports expected functions."""
194+
from graphql import _sentinel
195+
196+
assert hasattr(_sentinel, "is_compiled")
197+
assert hasattr(_sentinel, "add_numbers")
198+
assert hasattr(_sentinel, "SENTINEL_VALUE")
199+
assert callable(_sentinel.is_compiled)
200+
assert callable(_sentinel.add_numbers)
201+
202+
def sentinel_add_numbers_works():
203+
"""The sentinel add_numbers function works correctly."""
204+
from graphql import _sentinel
205+
206+
assert _sentinel.add_numbers(2, 3) == 5
207+
assert _sentinel.add_numbers(-1, 1) == 0
208+
209+
def hook_redirects_to_mypyc_version():
210+
"""With hook active, graphql._sentinel gets graphql_mypyc._sentinel."""
211+
import sys
212+
213+
import graphql_mypyc
214+
215+
# Ensure hook is active
216+
graphql_mypyc.activate()
217+
218+
# Remove any cached import of graphql._sentinel
219+
if "graphql._sentinel" in sys.modules:
220+
del sys.modules["graphql._sentinel"]
221+
222+
# Import with hook active - should get redirected
223+
from graphql import _sentinel
224+
225+
# The SENTINEL_VALUE tells us which version we got
226+
# graphql_mypyc._sentinel has "mypyc-compiled"
227+
# graphql._sentinel (fallback) has "interpreted"
228+
assert _sentinel.SENTINEL_VALUE == "mypyc-compiled"
229+
230+
def without_hook_gets_interpreted_version():
231+
"""With hook inactive, graphql._sentinel is the interpreted version."""
232+
import sys
233+
234+
import graphql
235+
import graphql_mypyc
236+
237+
# Deactivate hook
238+
graphql_mypyc.deactivate()
239+
240+
# Remove any cached import from sys.modules
241+
if "graphql._sentinel" in sys.modules:
242+
del sys.modules["graphql._sentinel"]
243+
244+
# Also remove the cached attribute from the graphql module
245+
# (Python caches submodule imports as attributes on the parent)
246+
if hasattr(graphql, "_sentinel"):
247+
delattr(graphql, "_sentinel")
248+
249+
# Import without hook - should get original
250+
from graphql import _sentinel
251+
252+
assert _sentinel.SENTINEL_VALUE == "interpreted"
253+
254+
# Reactivate for other tests and clear cache
255+
graphql_mypyc.activate()
256+
if "graphql._sentinel" in sys.modules:
257+
del sys.modules["graphql._sentinel"]
258+
if hasattr(graphql, "_sentinel"):
259+
delattr(graphql, "_sentinel")

0 commit comments

Comments
 (0)