Skip to content

Commit 514a615

Browse files
authored
Test optional SymPy loading in fresh interpreters
1 parent 5df29aa commit 514a615

1 file changed

Lines changed: 111 additions & 0 deletions

File tree

‎tests/test_lazy_sympy.py‎

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"""Regression tests for optional SymPy loading in fresh interpreters."""
2+
3+
import subprocess
4+
import sys
5+
import textwrap
6+
7+
import pytest
8+
9+
10+
def run_python(source):
11+
"""Run an isolated check with the same interpreter as pytest."""
12+
subprocess.run([sys.executable, "-c", textwrap.dedent(source)], check=True)
13+
14+
15+
def test_numeric_import_does_not_load_sympy():
16+
run_python(
17+
"""
18+
import sys
19+
import numpy as np
20+
import spatialmath
21+
from spatialmath import base as smb
22+
assert not any(n == 'sympy' or n.startswith('sympy.') for n in sys.modules)
23+
assert smb.isscalar(1.0)
24+
assert smb.isvector([1, 2])
25+
assert smb.isnumberlist([1, 2])
26+
assert smb.norm([3, 4]) == 5
27+
assert np.allclose(smb.rot2(0), np.eye(2))
28+
assert not any(n == 'sympy' or n.startswith('sympy.') for n in sys.modules)
29+
"""
30+
)
31+
32+
33+
@pytest.mark.parametrize("sympy_first", [False, True])
34+
def test_symbolic_operations_in_both_import_orders(sympy_first):
35+
prefix = "import sympy as sp\n" if sympy_first else ""
36+
run_python(
37+
prefix
38+
+ """
39+
import numpy as np
40+
from spatialmath import base as smb
41+
import sympy as sp
42+
x = sp.Symbol('x', real=True)
43+
assert smb.issymbol(x)
44+
assert smb.issymbol([1, x])
45+
assert smb.issymbol((x, 1))
46+
assert not smb.issymbol([1, 2])
47+
assert smb.isscalar(x)
48+
assert not smb.isscalar([x])
49+
assert not smb.isscalar((x,))
50+
assert smb.isvector([x, 1])
51+
assert smb.isnumberlist([x, 1])
52+
assert smb.getvector(x)[0] == x
53+
assert smb.getvector([x, 1])[0] == x
54+
assert smb.sin(x) == sp.sin(x)
55+
assert smb.cos(x) == sp.cos(x)
56+
assert smb.tan(x) == sp.tan(x)
57+
assert smb.sqrt(x) == sp.sqrt(x)
58+
assert smb.norm([x, 1]) == sp.sqrt(x*x + 1)
59+
assert smb.det(np.array([[x, 0], [0, 1]], dtype=object)) == x
60+
T = smb.trot2(x)
61+
assert sp.simplify(smb.tr2xyt(T)[2] - sp.atan2(sp.sin(x), sp.cos(x))) == 0
62+
assert smb.simplify(sp.sin(x)**2 + sp.cos(x)**2) == 1
63+
"""
64+
)
65+
66+
67+
@pytest.mark.parametrize("helper", ["symbol", "zero", "one", "negative_one", "pi"])
68+
def test_explicit_symbolic_helpers_load_sympy(helper):
69+
run_python(
70+
textwrap.dedent(f"helper = {helper!r}\n")
71+
+ textwrap.dedent(
72+
"""
73+
import sys
74+
from spatialmath.base import symbolic as sym
75+
assert 'sympy' not in sys.modules
76+
getattr(sym, helper)(*(['x'] if helper == 'symbol' else []))
77+
assert 'sympy' in sys.modules
78+
x = sym.symbol('x')
79+
import sympy as sp
80+
assert isinstance(x, sp.Symbol)
81+
from spatialmath.base.symbolic import Symbol, symtype
82+
assert Symbol is sp.Symbol
83+
assert symtype == (sp.Expr,)
84+
assert sym.zero() == sp.S.Zero
85+
assert sym.one() == sp.S.One
86+
assert sym.negative_one() == sp.S.NegativeOne
87+
assert sym.pi() == sp.pi
88+
"""
89+
)
90+
)
91+
92+
93+
def test_numeric_operations_without_sympy():
94+
run_python(
95+
"""
96+
import importlib.abc
97+
import sys
98+
class BlockSympy(importlib.abc.MetaPathFinder):
99+
def find_spec(self, fullname, path=None, target=None):
100+
if fullname == 'sympy' or fullname.startswith('sympy.'):
101+
raise ModuleNotFoundError('SymPy blocked for this test')
102+
sys.meta_path.insert(0, BlockSympy())
103+
from spatialmath import base as smb
104+
assert not smb.issymbol(1)
105+
assert smb.norm([3, 4]) == 5
106+
assert smb.sin(0) == 0
107+
assert smb.simplify(2) == 2
108+
assert smb.isscalar(1)
109+
assert smb.isvector([1, 2])
110+
"""
111+
)

0 commit comments

Comments
 (0)