Skip to content

Commit cf96408

Browse files
committed
Fix alarm tests
1 parent e7477f8 commit cf96408

15 files changed

+168
-88
lines changed

src/sage/coding/linear_code.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -789,10 +789,8 @@ def canonical_representative(self, equivalence='semilinear'):
789789
(see :issue:`21651`)::
790790
791791
sage: C = LinearCode(random_matrix(GF(47), 25, 35))
792-
sage: alarm(0.5); C.canonical_representative() # needs sage.libs.gap
793-
Traceback (most recent call last):
794-
...
795-
AlarmInterrupt
792+
sage: from sage.doctest.util import ensure_interruptible_after
793+
sage: with ensure_interruptible_after(0.5): C.canonical_representative() # needs sage.libs.gap
796794
"""
797795
aut_group_can_label = self._canonize(equivalence)
798796
return aut_group_can_label.get_canonical_form(), \

src/sage/doctest/util.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
from time import time as walltime
2727
from os import sysconf, times
28+
from contextlib import contextmanager
29+
from cysignals.alarm import alarm, cancel_alarm, AlarmInterrupt
2830

2931

3032
def count_noun(number, noun, plural=None, pad_number=False, pad_noun=False):
@@ -749,3 +751,119 @@ def __ne__(self, other):
749751
True
750752
"""
751753
return not (self == other)
754+
755+
756+
@contextmanager
757+
def ensure_interruptible_after(seconds: float, max_wait_after_interrupt: float = 0.2, inaccuracy_tolerance: float = 0.1):
758+
"""
759+
Helper function for doctesting to ensure that the code is interruptible after a certain amount of time.
760+
This should only be used for internal doctesting purposes.
761+
762+
EXAMPLES::
763+
764+
sage: from sage.doctest.util import ensure_interruptible_after
765+
sage: with ensure_interruptible_after(1) as data: sleep(3)
766+
767+
``as data`` is optional, but if it is used, it will contain a few useful values::
768+
769+
sage: data # abs tol 0.2
770+
{'alarm_raised': True, 'elapsed': 1.0}
771+
772+
``max_wait_after_interrupt`` can be passed if the function may take longer than usual to be interrupted::
773+
774+
sage: cython('''
775+
....: from libc.time cimport clock_t, clock, CLOCKS_PER_SEC
776+
....: from cysignals.signals cimport sig_check
777+
....: cpdef void uninterruptible_sleep(double seconds):
778+
....: cdef clock_t target = clock() + <clock_t>(CLOCKS_PER_SEC * seconds)
779+
....: while clock() < target:
780+
....: pass
781+
....: cpdef void check_interrupt_only_occasionally():
782+
....: for i in range(10):
783+
....: uninterruptible_sleep(0.8)
784+
....: sig_check()
785+
....: ''', compiler_directives={'preliminary_late_includes_cy28': True})
786+
sage: with ensure_interruptible_after(1) as data: # not passing max_wait_after_interrupt will raise an error
787+
....: check_interrupt_only_occasionally()
788+
Traceback (most recent call last):
789+
...
790+
RuntimeError: Function is not interruptible within 1.0000 seconds, only after 1... seconds
791+
sage: with ensure_interruptible_after(1, max_wait_after_interrupt=0.7):
792+
....: check_interrupt_only_occasionally()
793+
794+
TESTS::
795+
796+
sage: data['elapsed'] # abs tol 0.2 # 1.6 = 0.8 * 2
797+
1.6
798+
799+
This test ensures the ``# cython: ...`` header comment doesn't work
800+
and ``cysignals`` requires ``preliminary_late_includes_cy28=True``
801+
(when it is finally fixed then this test and the one above can be modified
802+
to remove the flag)::
803+
804+
sage: # needs sage.misc.cython
805+
sage: cython('''
806+
....: # cython: preliminary_late_includes_cy28=True
807+
....: # ^ required by cysignals
808+
....: from cysignals.signals cimport sig_check
809+
....: ''')
810+
Traceback (most recent call last):
811+
...
812+
RuntimeError: ...
813+
814+
::
815+
816+
sage: with ensure_interruptible_after(2) as data: sleep(1)
817+
Traceback (most recent call last):
818+
...
819+
RuntimeError: Function terminates early after 1... < 2.0000 seconds
820+
sage: data # abs tol 0.2
821+
{'alarm_raised': False, 'elapsed': 1.0}
822+
sage: with ensure_interruptible_after(1) as data: raise ValueError
823+
Traceback (most recent call last):
824+
...
825+
ValueError
826+
sage: data # abs tol 0.2
827+
{'alarm_raised': False, 'elapsed': 0.0}
828+
829+
::
830+
831+
sage: # needs sage.misc.cython
832+
sage: with ensure_interruptible_after(1) as data: uninterruptible_sleep(2)
833+
Traceback (most recent call last):
834+
...
835+
RuntimeError: Function is not interruptible within 1.0000 seconds, only after 2... seconds
836+
sage: data # abs tol 0.2
837+
{'alarm_raised': True, 'elapsed': 2.0}
838+
sage: with ensure_interruptible_after(1): uninterruptible_sleep(2); raise RuntimeError
839+
Traceback (most recent call last):
840+
...
841+
RuntimeError: Function is not interruptible within 1.0000 seconds, only after 2... seconds
842+
sage: data # abs tol 0.2
843+
{'alarm_raised': True, 'elapsed': 2.0}
844+
"""
845+
data = {}
846+
start_time = walltime()
847+
alarm(seconds)
848+
alarm_raised = False
849+
850+
try:
851+
yield data
852+
except AlarmInterrupt:
853+
alarm_raised = True
854+
finally:
855+
cancel_alarm()
856+
elapsed = walltime() - start_time
857+
data["elapsed"] = elapsed
858+
data["alarm_raised"] = alarm_raised
859+
860+
if elapsed > seconds + max_wait_after_interrupt:
861+
raise RuntimeError(
862+
f"Function is not interruptible within {seconds:.4f} seconds, only after {elapsed:.4f} seconds"
863+
+ ("" if alarm_raised else " (__exit__ called before interrupt check)"))
864+
865+
if alarm_raised:
866+
if elapsed < seconds - inaccuracy_tolerance:
867+
raise RuntimeError(f"Interrupted too early: {elapsed:.4f} < {seconds:.4f}, this should not happen")
868+
else:
869+
raise RuntimeError(f"Function terminates early after {elapsed:.4f} < {seconds:.4f} seconds")

src/sage/geometry/integral_points.pxi

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -531,10 +531,8 @@ cpdef rectangular_box_points(list box_min, list box_max,
531531
....: (0, 0, 0, 0, 0, -1, 2, -1, 0),
532532
....: (0, 0, 0, 0, 0, 0, -1, 2, -1)]
533533
sage: P = Polyhedron(ieqs=ieqs)
534-
sage: alarm(0.5); P.integral_points()
535-
Traceback (most recent call last):
536-
...
537-
AlarmInterrupt
534+
sage: from sage.doctest.util import ensure_interruptible_after
535+
sage: with ensure_interruptible_after(0.5): P.integral_points()
538536
"""
539537
assert len(box_min) == len(box_max)
540538
assert not (count_only and return_saturated)

src/sage/libs/libecm.pyx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,8 @@ def ecmfactor(number, double B1, verbose=False, sigma=0):
143143
Check that ``ecmfactor`` can be interrupted (factoring a large
144144
prime number)::
145145
146-
sage: alarm(0.5); ecmfactor(2^521-1, 1e7)
147-
Traceback (most recent call last):
148-
...
149-
AlarmInterrupt
146+
sage: from sage.doctest.util import ensure_interruptible_after
147+
sage: with ensure_interruptible_after(0.5): ecmfactor(2^521-1, 1e7)
150148
151149
Some special cases::
152150

src/sage/matrix/matrix_integer_dense.pyx

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4385,14 +4385,8 @@ cdef class Matrix_integer_dense(Matrix_dense):
43854385
43864386
sage: A = random_matrix(ZZ, 2000, 2000)
43874387
sage: B = random_matrix(ZZ, 2000, 2000)
4388-
sage: t0 = walltime()
4389-
sage: alarm(2); A._solve_iml(B) # long time
4390-
Traceback (most recent call last):
4391-
...
4392-
AlarmInterrupt
4393-
sage: t = walltime(t0)
4394-
sage: t < 10 or t
4395-
True
4388+
sage: from sage.doctest.util import ensure_interruptible_after
4389+
sage: with ensure_interruptible_after(2, max_wait_after_interrupt=8): A._solve_iml(B)
43964390
43974391
ALGORITHM: Uses IML.
43984392
@@ -4549,14 +4543,8 @@ cdef class Matrix_integer_dense(Matrix_dense):
45494543
45504544
sage: A = random_matrix(ZZ, 2000, 2000)
45514545
sage: B = random_matrix(ZZ, 2000, 2000)
4552-
sage: t0 = walltime()
4553-
sage: alarm(2); A._solve_flint(B) # long time
4554-
Traceback (most recent call last):
4555-
...
4556-
AlarmInterrupt
4557-
sage: t = walltime(t0)
4558-
sage: t < 10 or t
4559-
True
4546+
sage: from sage.doctest.util import ensure_interruptible_after
4547+
sage: with ensure_interruptible_after(2, max_wait_after_interrupt=8): A._solve_flint(B)
45604548
45614549
AUTHORS:
45624550

src/sage/matrix/matrix_mod2_dense.pyx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1975,10 +1975,8 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse
19751975
sage: A = random_matrix(GF(2), n, m)
19761976
sage: x = random_vector(GF(2), m)
19771977
sage: B = A*x
1978-
sage: alarm(0.5); sol = A.solve_right(B)
1979-
Traceback (most recent call last):
1980-
...
1981-
AlarmInterrupt
1978+
sage: from sage.doctest.util import ensure_interruptible_after
1979+
sage: with ensure_interruptible_after(0.5): sol = A.solve_right(B)
19821980
"""
19831981
cdef mzd_t *B_entries = (<Matrix_mod2_dense>B)._entries
19841982

src/sage/misc/cython.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def _standard_libs_libdirs_incdirs_aliases():
8080

8181
def cython(filename, verbose=0, compile_message=False,
8282
use_cache=False, create_local_c_file=False, annotate=True, sage_namespace=True,
83-
create_local_so_file=False):
83+
compiler_directives={}, create_local_so_file=False):
8484
r"""
8585
Compile a Cython file. This converts a Cython file to a C (or C++ file),
8686
and then compiles that. The .c file and the .so file are
@@ -113,6 +113,10 @@ def cython(filename, verbose=0, compile_message=False,
113113
- ``sage_namespace`` -- boolean (default: ``True``); if ``True``, import
114114
``sage.all``
115115
116+
- ``compiler_directives`` -- dictionary (default: ``{}``); extra compiler
117+
directives to pass to Cython. Usually this can be provided by ``# cython: ...``
118+
comments in the Cython file, but there are a few exceptions
119+
116120
- ``create_local_so_file`` -- boolean (default: ``False``); if ``True``, save a
117121
copy of the compiled .so file in the current directory
118122
@@ -348,7 +352,8 @@ def cython(filename, verbose=0, compile_message=False,
348352
libraries=standard_libs,
349353
library_dirs=standard_libdirs)
350354

351-
directives = {'language_level': 3, 'cdivision': True}
355+
compiler_directives = {'language_level': 3, 'cdivision': True, **compiler_directives}
356+
# let user-provided compiler directives override the defaults
352357

353358
try:
354359
# Change directories to target_dir so that Cython produces the correct
@@ -360,7 +365,7 @@ def cython(filename, verbose=0, compile_message=False,
360365
ext, = cythonize([ext],
361366
aliases=aliases,
362367
include_path=includes,
363-
compiler_directives=directives,
368+
compiler_directives=compiler_directives,
364369
quiet=(verbose <= 0),
365370
errors_to_stderr=False,
366371
use_listing_file=True)

src/sage/rings/complex_arb.pyx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,13 +1184,10 @@ class ComplexBallField(UniqueRepresentation, sage.rings.abc.ComplexBallField):
11841184
sage: ComplexBallField(100).integral(lambda x, _: sin(x), RBF(0), RBF(1))
11851185
[0.4596976941318602825990633926 +/- ...e-29]
11861186
1187-
sage: from cysignals.alarm import alarm
1188-
sage: alarm(0.1r)
1189-
sage: C = ComplexBallField(1000000)
1190-
sage: C.integral(lambda x, _: x.cos() * x.sin(), 0, 1)
1191-
Traceback (most recent call last):
1192-
...
1193-
AlarmInterrupt
1187+
sage: from sage.doctest.util import ensure_interruptible_after
1188+
sage: with ensure_interruptible_after(0.1):
1189+
....: C = ComplexBallField(1000000)
1190+
....: C.integral(lambda x, _: x.cos() * x.sin(), 0, 1)
11941191
"""
11951192
cdef IntegrationContext ctx = IntegrationContext()
11961193
cdef acb_calc_integrate_opt_t arb_opts

src/sage/rings/factorint_pari.pyx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,11 @@ def factor_using_pari(n, int_=False, debug_level=0, proof=None):
5050
5151
Check that PARI's debug level is properly reset (:issue:`18792`)::
5252
53-
sage: alarm(0.5); factor(2^1000 - 1, verbose=5)
54-
Traceback (most recent call last):
53+
sage: from sage.doctest.util import ensure_interruptible_after
54+
sage: with ensure_interruptible_after(0.5): factor(2^1000 - 1, verbose=5)
5555
...
56-
AlarmInterrupt
56+
doctest:warning...
57+
RuntimeWarning: cypari2 leaked ... bytes on the PARI stack
5758
sage: pari.get_debug_level()
5859
0
5960
"""

src/sage/rings/integer.pyx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7108,21 +7108,16 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement):
71087108
71097109
Check that it can be interrupted (:issue:`17852`)::
71107110
7111-
sage: alarm(0.5); (2^100).binomial(2^22, algorithm='mpir')
7112-
Traceback (most recent call last):
7113-
...
7114-
AlarmInterrupt
7111+
sage: from sage.doctest.util import ensure_interruptible_after
7112+
sage: with ensure_interruptible_after(0.5): (2^100).binomial(2^22, algorithm='mpir')
71157113
71167114
For PARI, we try 10 interrupts with increasing intervals to
71177115
check for reliable interrupting, see :issue:`18919`::
71187116
71197117
sage: from cysignals import AlarmInterrupt
71207118
sage: for i in [1..10]: # long time (5s) # needs sage.libs.pari
7121-
....: try:
7122-
....: alarm(i/11)
7119+
....: with ensure_interruptible_after(i/11):
71237120
....: (2^100).binomial(2^22, algorithm='pari')
7124-
....: except AlarmInterrupt:
7125-
....: pass
71267121
doctest:...: RuntimeWarning: cypari2 leaked ... bytes on the PARI stack...
71277122
"""
71287123
cdef Integer x

src/sage/rings/polynomial/polynomial_element.pyx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2533,10 +2533,8 @@ cdef class Polynomial(CommutativePolynomial):
25332533
sage: K.<a> = GF(2^8)
25342534
sage: x = polygen(K)
25352535
sage: pol = x^1000000 + x + a
2536-
sage: alarm(0.5); pol.any_root()
2537-
Traceback (most recent call last):
2538-
...
2539-
AlarmInterrupt
2536+
sage: from sage.doctest.util import ensure_interruptible_after
2537+
sage: with ensure_interruptible_after(0.5): pol.any_root()
25402538
25412539
Check root computation over large finite fields::
25422540

src/sage/rings/qqbar.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7106,14 +7106,13 @@ def exactify(self):
71067106
sage: x = polygen(AA)
71077107
sage: p = AA(2)^(1/100) * x + AA(3)^(1/100)
71087108
sage: cp = AA.common_polynomial(p)
7109-
sage: alarm(0.5); cp.generator()
7110-
Traceback (most recent call last):
7111-
...
7112-
AlarmInterrupt
7113-
sage: alarm(0.5); cp.generator()
7114-
Traceback (most recent call last):
7115-
...
7116-
AlarmInterrupt
7109+
sage: from sage.doctest.util import ensure_interruptible_after
7110+
sage: with ensure_interruptible_after(0.5): cp.generator()
7111+
doctest:warning...
7112+
RuntimeWarning: cypari2 leaked ... bytes on the PARI stack
7113+
sage: with ensure_interruptible_after(0.5): cp.generator()
7114+
doctest:warning...
7115+
RuntimeWarning: cypari2 leaked ... bytes on the PARI stack
71177116
"""
71187117
if self._exact:
71197118
return

src/sage/schemes/elliptic_curves/descent_two_isogeny.pyx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,10 +1208,8 @@ def two_descent_by_two_isogeny(E,
12081208
Elliptic Curve defined by y^2 = x^3 - x^2 - 900*x - 10098 over Rational Field
12091209
sage: E.sha().an()
12101210
4
1211-
sage: alarm(0.5); two_descent_by_two_isogeny(E, global_limit_large=10^8)
1212-
Traceback (most recent call last):
1213-
...
1214-
AlarmInterrupt
1211+
sage: from sage.doctest.util import ensure_interruptible_after
1212+
sage: with ensure_interruptible_after(0.5): two_descent_by_two_isogeny(E, global_limit_large=10^8)
12151213
"""
12161214
cdef Integer a1, a2, a3, a4, a6, s2, s4, s6
12171215
cdef Integer c, d, x0

0 commit comments

Comments
 (0)