Skip to content

Commit

Permalink
Fix alarm tests
Browse files Browse the repository at this point in the history
  • Loading branch information
user202729 committed Dec 16, 2024
1 parent e7477f8 commit cf96408
Show file tree
Hide file tree
Showing 15 changed files with 168 additions and 88 deletions.
6 changes: 2 additions & 4 deletions src/sage/coding/linear_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,10 +789,8 @@ def canonical_representative(self, equivalence='semilinear'):
(see :issue:`21651`)::
sage: C = LinearCode(random_matrix(GF(47), 25, 35))
sage: alarm(0.5); C.canonical_representative() # needs sage.libs.gap
Traceback (most recent call last):
...
AlarmInterrupt
sage: from sage.doctest.util import ensure_interruptible_after
sage: with ensure_interruptible_after(0.5): C.canonical_representative() # needs sage.libs.gap
"""
aut_group_can_label = self._canonize(equivalence)
return aut_group_can_label.get_canonical_form(), \
Expand Down
118 changes: 118 additions & 0 deletions src/sage/doctest/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

from time import time as walltime
from os import sysconf, times
from contextlib import contextmanager
from cysignals.alarm import alarm, cancel_alarm, AlarmInterrupt


def count_noun(number, noun, plural=None, pad_number=False, pad_noun=False):
Expand Down Expand Up @@ -749,3 +751,119 @@ def __ne__(self, other):
True
"""
return not (self == other)


@contextmanager
def ensure_interruptible_after(seconds: float, max_wait_after_interrupt: float = 0.2, inaccuracy_tolerance: float = 0.1):
"""
Helper function for doctesting to ensure that the code is interruptible after a certain amount of time.
This should only be used for internal doctesting purposes.
EXAMPLES::
sage: from sage.doctest.util import ensure_interruptible_after
sage: with ensure_interruptible_after(1) as data: sleep(3)
``as data`` is optional, but if it is used, it will contain a few useful values::
sage: data # abs tol 0.2
{'alarm_raised': True, 'elapsed': 1.0}
``max_wait_after_interrupt`` can be passed if the function may take longer than usual to be interrupted::
sage: cython('''
....: from libc.time cimport clock_t, clock, CLOCKS_PER_SEC
....: from cysignals.signals cimport sig_check
....: cpdef void uninterruptible_sleep(double seconds):
....: cdef clock_t target = clock() + <clock_t>(CLOCKS_PER_SEC * seconds)
....: while clock() < target:
....: pass
....: cpdef void check_interrupt_only_occasionally():
....: for i in range(10):
....: uninterruptible_sleep(0.8)
....: sig_check()
....: ''', compiler_directives={'preliminary_late_includes_cy28': True})
sage: with ensure_interruptible_after(1) as data: # not passing max_wait_after_interrupt will raise an error
....: check_interrupt_only_occasionally()
Traceback (most recent call last):
...
RuntimeError: Function is not interruptible within 1.0000 seconds, only after 1... seconds
sage: with ensure_interruptible_after(1, max_wait_after_interrupt=0.7):

Check failure on line 791 in src/sage/doctest/util.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Exception raised: Traceback (most recent call last): File "/sage/src/sage/doctest/util.py", line 851, in ensure_interruptible_after yield data File "<doctest sage.doctest.util.inaccuracy_tolerance[5]>", line 2, in <module> check_interrupt_only_occasionally() File "_tmp_tmpma59ghcx_tmp_k8xuvx4p_pyx_0.pyx", line 8, in _tmp_tmpma59ghcx_tmp_k8xuvx4p_pyx_0.check_interrupt_only_occasionally cpdef void check_interrupt_only_occasionally(): File "_tmp_tmpma59ghcx_tmp_k8xuvx4p_pyx_0.pyx", line 11, in _tmp_tmpma59ghcx_tmp_k8xuvx4p_pyx_0.check_interrupt_only_occasionally sig_check() cysignals.signals.AlarmInterrupt During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/sage/src/sage/doctest/forker.py", line 716, in _run self.compile_and_execute(example, compiler, test.globs) File "/sage/src/sage/doctest/forker.py", line 1137, in compile_and_execute exec(compiled, globs) File "<doctest sage.doctest.util.inaccuracy_tolerance[5]>", line 1, in <module> with ensure_interruptible_after(Integer(1), max_wait_after_interrupt=RealNumber('0.7')): File "/usr/lib/python3.10/contextlib.py", line 153, in __exit__ self.gen.throw(typ, value, traceback) File "/sage/src/sage/doctest/util.py", line 861, in ensure_interruptible_after raise RuntimeError( RuntimeError: Function is not interruptible within 1.0000 seconds, only after 1.7602 seconds
....: check_interrupt_only_occasionally()
TESTS::
sage: data['elapsed'] # abs tol 0.2 # 1.6 = 0.8 * 2
1.6
This test ensures the ``# cython: ...`` header comment doesn't work
and ``cysignals`` requires ``preliminary_late_includes_cy28=True``
(when it is finally fixed then this test and the one above can be modified
to remove the flag)::
sage: # needs sage.misc.cython
sage: cython('''

Check failure on line 805 in src/sage/doctest/util.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Got:
....: # cython: preliminary_late_includes_cy28=True
....: # ^ required by cysignals
....: from cysignals.signals cimport sig_check
....: ''')
Traceback (most recent call last):
...
RuntimeError: ...
::
sage: with ensure_interruptible_after(2) as data: sleep(1)
Traceback (most recent call last):
...
RuntimeError: Function terminates early after 1... < 2.0000 seconds
sage: data # abs tol 0.2
{'alarm_raised': False, 'elapsed': 1.0}
sage: with ensure_interruptible_after(1) as data: raise ValueError
Traceback (most recent call last):
...
ValueError
sage: data # abs tol 0.2
{'alarm_raised': False, 'elapsed': 0.0}
::
sage: # needs sage.misc.cython
sage: with ensure_interruptible_after(1) as data: uninterruptible_sleep(2)
Traceback (most recent call last):
...
RuntimeError: Function is not interruptible within 1.0000 seconds, only after 2... seconds
sage: data # abs tol 0.2
{'alarm_raised': True, 'elapsed': 2.0}
sage: with ensure_interruptible_after(1): uninterruptible_sleep(2); raise RuntimeError
Traceback (most recent call last):
...
RuntimeError: Function is not interruptible within 1.0000 seconds, only after 2... seconds
sage: data # abs tol 0.2
{'alarm_raised': True, 'elapsed': 2.0}
"""
data = {}
start_time = walltime()
alarm(seconds)
alarm_raised = False

try:
yield data
except AlarmInterrupt:
alarm_raised = True
finally:
cancel_alarm()
elapsed = walltime() - start_time
data["elapsed"] = elapsed
data["alarm_raised"] = alarm_raised

if elapsed > seconds + max_wait_after_interrupt:
raise RuntimeError(
f"Function is not interruptible within {seconds:.4f} seconds, only after {elapsed:.4f} seconds"
+ ("" if alarm_raised else " (__exit__ called before interrupt check)"))

if alarm_raised:
if elapsed < seconds - inaccuracy_tolerance:
raise RuntimeError(f"Interrupted too early: {elapsed:.4f} < {seconds:.4f}, this should not happen")
else:
raise RuntimeError(f"Function terminates early after {elapsed:.4f} < {seconds:.4f} seconds")
6 changes: 2 additions & 4 deletions src/sage/geometry/integral_points.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -531,10 +531,8 @@ cpdef rectangular_box_points(list box_min, list box_max,
....: (0, 0, 0, 0, 0, -1, 2, -1, 0),
....: (0, 0, 0, 0, 0, 0, -1, 2, -1)]
sage: P = Polyhedron(ieqs=ieqs)
sage: alarm(0.5); P.integral_points()
Traceback (most recent call last):
...
AlarmInterrupt
sage: from sage.doctest.util import ensure_interruptible_after
sage: with ensure_interruptible_after(0.5): P.integral_points()
"""
assert len(box_min) == len(box_max)
assert not (count_only and return_saturated)
Expand Down
6 changes: 2 additions & 4 deletions src/sage/libs/libecm.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,8 @@ def ecmfactor(number, double B1, verbose=False, sigma=0):
Check that ``ecmfactor`` can be interrupted (factoring a large
prime number)::
sage: alarm(0.5); ecmfactor(2^521-1, 1e7)
Traceback (most recent call last):
...
AlarmInterrupt
sage: from sage.doctest.util import ensure_interruptible_after
sage: with ensure_interruptible_after(0.5): ecmfactor(2^521-1, 1e7)
Some special cases::
Expand Down
20 changes: 4 additions & 16 deletions src/sage/matrix/matrix_integer_dense.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -4385,14 +4385,8 @@ cdef class Matrix_integer_dense(Matrix_dense):
sage: A = random_matrix(ZZ, 2000, 2000)
sage: B = random_matrix(ZZ, 2000, 2000)
sage: t0 = walltime()
sage: alarm(2); A._solve_iml(B) # long time
Traceback (most recent call last):
...
AlarmInterrupt
sage: t = walltime(t0)
sage: t < 10 or t
True
sage: from sage.doctest.util import ensure_interruptible_after
sage: with ensure_interruptible_after(2, max_wait_after_interrupt=8): A._solve_iml(B)
ALGORITHM: Uses IML.
Expand Down Expand Up @@ -4549,14 +4543,8 @@ cdef class Matrix_integer_dense(Matrix_dense):
sage: A = random_matrix(ZZ, 2000, 2000)
sage: B = random_matrix(ZZ, 2000, 2000)
sage: t0 = walltime()
sage: alarm(2); A._solve_flint(B) # long time
Traceback (most recent call last):
...
AlarmInterrupt
sage: t = walltime(t0)
sage: t < 10 or t
True
sage: from sage.doctest.util import ensure_interruptible_after
sage: with ensure_interruptible_after(2, max_wait_after_interrupt=8): A._solve_flint(B)
AUTHORS:
Expand Down
6 changes: 2 additions & 4 deletions src/sage/matrix/matrix_mod2_dense.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1975,10 +1975,8 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse
sage: A = random_matrix(GF(2), n, m)
sage: x = random_vector(GF(2), m)
sage: B = A*x
sage: alarm(0.5); sol = A.solve_right(B)
Traceback (most recent call last):
...
AlarmInterrupt
sage: from sage.doctest.util import ensure_interruptible_after
sage: with ensure_interruptible_after(0.5): sol = A.solve_right(B)
"""
cdef mzd_t *B_entries = (<Matrix_mod2_dense>B)._entries

Expand Down
11 changes: 8 additions & 3 deletions src/sage/misc/cython.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def _standard_libs_libdirs_incdirs_aliases():

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

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

try:
# Change directories to target_dir so that Cython produces the correct
Expand All @@ -360,7 +365,7 @@ def cython(filename, verbose=0, compile_message=False,
ext, = cythonize([ext],
aliases=aliases,
include_path=includes,
compiler_directives=directives,
compiler_directives=compiler_directives,
quiet=(verbose <= 0),
errors_to_stderr=False,
use_listing_file=True)
Expand Down
11 changes: 4 additions & 7 deletions src/sage/rings/complex_arb.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1184,13 +1184,10 @@ class ComplexBallField(UniqueRepresentation, sage.rings.abc.ComplexBallField):
sage: ComplexBallField(100).integral(lambda x, _: sin(x), RBF(0), RBF(1))
[0.4596976941318602825990633926 +/- ...e-29]
sage: from cysignals.alarm import alarm
sage: alarm(0.1r)
sage: C = ComplexBallField(1000000)
sage: C.integral(lambda x, _: x.cos() * x.sin(), 0, 1)
Traceback (most recent call last):
...
AlarmInterrupt
sage: from sage.doctest.util import ensure_interruptible_after
sage: with ensure_interruptible_after(0.1):
....: C = ComplexBallField(1000000)
....: C.integral(lambda x, _: x.cos() * x.sin(), 0, 1)
"""
cdef IntegrationContext ctx = IntegrationContext()
cdef acb_calc_integrate_opt_t arb_opts
Expand Down
7 changes: 4 additions & 3 deletions src/sage/rings/factorint_pari.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ def factor_using_pari(n, int_=False, debug_level=0, proof=None):
Check that PARI's debug level is properly reset (:issue:`18792`)::
sage: alarm(0.5); factor(2^1000 - 1, verbose=5)
Traceback (most recent call last):
sage: from sage.doctest.util import ensure_interruptible_after
sage: with ensure_interruptible_after(0.5): factor(2^1000 - 1, verbose=5)
...
AlarmInterrupt
doctest:warning...
RuntimeWarning: cypari2 leaked ... bytes on the PARI stack
sage: pari.get_debug_level()
0
"""
Expand Down
11 changes: 3 additions & 8 deletions src/sage/rings/integer.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -7108,21 +7108,16 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement):
Check that it can be interrupted (:issue:`17852`)::
sage: alarm(0.5); (2^100).binomial(2^22, algorithm='mpir')
Traceback (most recent call last):
...
AlarmInterrupt
sage: from sage.doctest.util import ensure_interruptible_after
sage: with ensure_interruptible_after(0.5): (2^100).binomial(2^22, algorithm='mpir')
For PARI, we try 10 interrupts with increasing intervals to
check for reliable interrupting, see :issue:`18919`::
sage: from cysignals import AlarmInterrupt
sage: for i in [1..10]: # long time (5s) # needs sage.libs.pari
....: try:
....: alarm(i/11)
....: with ensure_interruptible_after(i/11):
....: (2^100).binomial(2^22, algorithm='pari')
....: except AlarmInterrupt:
....: pass
doctest:...: RuntimeWarning: cypari2 leaked ... bytes on the PARI stack...
"""
cdef Integer x
Expand Down
6 changes: 2 additions & 4 deletions src/sage/rings/polynomial/polynomial_element.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -2533,10 +2533,8 @@ cdef class Polynomial(CommutativePolynomial):
sage: K.<a> = GF(2^8)
sage: x = polygen(K)
sage: pol = x^1000000 + x + a
sage: alarm(0.5); pol.any_root()
Traceback (most recent call last):
...
AlarmInterrupt
sage: from sage.doctest.util import ensure_interruptible_after
sage: with ensure_interruptible_after(0.5): pol.any_root()
Check root computation over large finite fields::
Expand Down
15 changes: 7 additions & 8 deletions src/sage/rings/qqbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -7106,14 +7106,13 @@ def exactify(self):
sage: x = polygen(AA)
sage: p = AA(2)^(1/100) * x + AA(3)^(1/100)
sage: cp = AA.common_polynomial(p)
sage: alarm(0.5); cp.generator()
Traceback (most recent call last):
...
AlarmInterrupt
sage: alarm(0.5); cp.generator()
Traceback (most recent call last):
...
AlarmInterrupt
sage: from sage.doctest.util import ensure_interruptible_after
sage: with ensure_interruptible_after(0.5): cp.generator()
doctest:warning...
RuntimeWarning: cypari2 leaked ... bytes on the PARI stack
sage: with ensure_interruptible_after(0.5): cp.generator()
doctest:warning...
RuntimeWarning: cypari2 leaked ... bytes on the PARI stack
"""
if self._exact:
return
Expand Down
6 changes: 2 additions & 4 deletions src/sage/schemes/elliptic_curves/descent_two_isogeny.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1208,10 +1208,8 @@ def two_descent_by_two_isogeny(E,
Elliptic Curve defined by y^2 = x^3 - x^2 - 900*x - 10098 over Rational Field
sage: E.sha().an()
4
sage: alarm(0.5); two_descent_by_two_isogeny(E, global_limit_large=10^8)
Traceback (most recent call last):
...
AlarmInterrupt
sage: from sage.doctest.util import ensure_interruptible_after
sage: with ensure_interruptible_after(0.5): two_descent_by_two_isogeny(E, global_limit_large=10^8)
"""
cdef Integer a1, a2, a3, a4, a6, s2, s4, s6
cdef Integer c, d, x0
Expand Down
Loading

0 comments on commit cf96408

Please sign in to comment.