From 9958d74b1fe1813eabdfb4eeb4cda7a35537e32c Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Thu, 20 Mar 2025 17:09:42 +0700 Subject: [PATCH 1/5] Implement specified timeout for slow doctests --- src/sage/doctest/forker.py | 53 ++++++++++++++++++++++++++++++++++++- src/sage/doctest/parsing.py | 6 ++--- src/sage/doctest/sources.py | 20 +++++++------- 3 files changed, 66 insertions(+), 13 deletions(-) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 85af26d20ad..3cf1097bf96 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -517,6 +517,52 @@ def getvalue(self): TestResults = namedtuple('TestResults', 'failed attempted') +def _parse_example_timeout(source: str, default_timeout: float) -> float: + """ + Parse the timeout value from a doctest example's source. + + INPUT: + + - ``source`` -- the source code of a ``doctest.Example`` + - ``default_timeout`` -- the default timeout value to use + + OUTPUT: + + - a float, the timeout value to use for the example + + TESTS:: + + sage: from sage.doctest.forker import _parse_example_timeout + sage: _parse_example_timeout("sleep(10) # long time (10s)", 5.0r) + 10.0 + sage: _parse_example_timeout("sleep(10) # long time", 5.0r) + 5.0 + sage: _parse_example_timeout("sleep(10) # long time (1a2s)", 5.0r) + Traceback (most recent call last): + ... + ValueError: malformed optional tag '# long time (1a2s)', should be s + sage: _parse_example_timeout("sleep(10) # long time (:issue:`12345`)", 5.0r) + 5.0 + """ + # TODO this double-parsing is inefficient, should make :meth:`SageDocTestParser.parse` + # return subclass of doctest.Example that already include the timeout value + from sage.doctest.parsing import parse_optional_tags + value = parse_optional_tags(source).get("long time", None) + if value is None: + # either has the "long time" tag without any value in parentheses, + # or tag not present + return default_timeout + assert isinstance(value, str) + match = re.fullmatch(r'(\S*\d)\s*s', value.strip()) + if match: + try: + return float(match[1]) + except ValueError: + raise ValueError(f"malformed optional tag '# long time ({value})', should be s") + else: + return default_timeout + + class SageDocTestRunner(doctest.DocTestRunner): def __init__(self, *args, **kwds): """ @@ -582,6 +628,8 @@ def _run(self, test, compileflags, out): Since it needs to be able to read stdout, it should be called while spoofing using :class:`SageSpoofInOut`. + INPUT: see :meth:`run`. + EXAMPLES:: sage: from sage.doctest.parsing import SageOutputChecker @@ -627,6 +675,7 @@ def _run(self, test, compileflags, out): check = self._checker.check_output # Process each example. + example: doctest.Example for examplenum, example in enumerate(test.examples): if failures: # If exitfirst is set, abort immediately after a @@ -816,8 +865,10 @@ def compiler(example): if example.warnings: for warning in example.warnings: out(self._failure_header(test, example, f'Warning: {warning}')) + if outcome is SUCCESS: - if self.options.warn_long > 0 and example.cputime + check_timer.cputime > self.options.warn_long: + if self.options.warn_long > 0 and example.cputime + check_timer.cputime > _parse_example_timeout( + example.source, self.options.warn_long): self.report_overtime(out, test, example, got, check_timer=check_timer) elif example.warnings: diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index e1d7edc271f..e986073bdbb 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -829,7 +829,7 @@ def __ne__(self, other): """ return not (self == other) - def parse(self, string, *args): + def parse(self, string, *args) -> list[doctest.Example | str]: r""" A Sage specialization of :class:`doctest.DocTestParser`. @@ -1015,8 +1015,8 @@ def parse(self, string, *args): string = find_python_continuation.sub(r"\1" + ellipsis_tag + r"\2", string) string = find_sage_prompt.sub(r"\1>>> sage: ", string) string = find_sage_continuation.sub(r"\1...", string) - res = doctest.DocTestParser.parse(self, string, *args) - filtered = [] + res: list[doctest.Example | str] = doctest.DocTestParser.parse(self, string, *args) + filtered: list[doctest.Example | str] = [] persistent_optional_tags = self.file_optional_tags persistent_optional_tag_setter = None persistent_optional_tag_setter_index = None diff --git a/src/sage/doctest/sources.py b/src/sage/doctest/sources.py index ba6c05137b3..5395136f18a 100644 --- a/src/sage/doctest/sources.py +++ b/src/sage/doctest/sources.py @@ -193,7 +193,7 @@ def __ne__(self, other): """ return not (self == other) - def _process_doc(self, doctests, doc, namespace, start): + def _process_doc(self, doctests: list[doctest.DocTest], doc, namespace, start): """ Appends doctests defined in ``doc`` to the list ``doctests``. @@ -266,7 +266,7 @@ def file_optional_tags(self): """ return set() - def _create_doctests(self, namespace, tab_okay=None): + def _create_doctests(self, namespace, tab_okay=None) -> tuple[list[doctest.DocTest], dict]: """ Create a list of doctests defined in this source. @@ -314,7 +314,7 @@ def _create_doctests(self, namespace, tab_okay=None): probed_tags=self.options.probe, file_optional_tags=self.file_optional_tags) self.linking = False - doctests = [] + doctests: list[doctest.DocTest] = [] in_docstring = False unparsed_doc = False doc = [] @@ -480,7 +480,7 @@ def __iter__(self): for lineno, line in enumerate(self.source.split('\n')): yield lineno + self.lineno_shift, line + '\n' - def create_doctests(self, namespace): + def create_doctests(self, namespace) -> tuple[list[doctest.DocTest], dict]: r""" Create doctests from this string. @@ -492,8 +492,8 @@ def create_doctests(self, namespace): - ``doctests`` -- list of doctests defined by this string - - ``tab_locations`` -- either ``False`` or a list of linenumbers - on which tabs appear + - ``extras`` -- dictionary with ``extras['tab']`` either + ``False`` or a list of linenumbers on which tabs appear EXAMPLES:: @@ -503,10 +503,12 @@ def create_doctests(self, namespace): sage: s = "'''\n sage: 2 + 2\n 4\n'''" sage: PythonStringSource = dynamic_class('PythonStringSource',(StringDocTestSource, PythonSource)) sage: PSS = PythonStringSource('', s, DocTestDefaults(), 'runtime') - sage: dt, tabs = PSS.create_doctests({}) + sage: dt, extras = PSS.create_doctests({}) sage: for t in dt: ....: print("{} {}".format(t.name, t.examples[0].sage_source)) 2 + 2 + sage: extras + {...'tab': []...} """ return self._create_doctests(namespace) @@ -736,7 +738,7 @@ def file_optional_tags(self): from .parsing import parse_file_optional_tags return parse_file_optional_tags(self) - def create_doctests(self, namespace): + def create_doctests(self, namespace) -> tuple[list[doctest.DocTest], dict]: r""" Return a list of doctests for this file. @@ -910,7 +912,7 @@ class SourceLanguage: Currently supported languages include Python, ReST and LaTeX. """ - def parse_docstring(self, docstring, namespace, start): + def parse_docstring(self, docstring, namespace, start) -> list[doctest.DocTest]: """ Return a list of doctest defined in this docstring. From 8baef0bc4f5a09077eed8c5a16e16a308b3ddcb3 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Thu, 20 Mar 2025 19:11:24 +0700 Subject: [PATCH 2/5] Add some long time markers --- .../vector_calculus/vector_calc_curvilinear.rst | 5 ++++- src/sage/doctest/forker.py | 2 +- src/sage/dynamics/arithmetic_dynamics/projective_ds.py | 2 +- src/sage/groups/perm_gps/permgroup_named.py | 4 ++-- src/sage/interacts/test_jupyter.rst | 2 +- src/sage/interfaces/maxima_abstract.py | 2 +- src/sage/rings/semirings/tropical_mpolynomial.py | 3 ++- src/sage/schemes/elliptic_curves/ell_generic.py | 2 ++ src/sage/stats/distributions/discrete_gaussian_lattice.py | 2 +- src/sage/symbolic/expression.pyx | 2 +- 10 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_curvilinear.rst b/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_curvilinear.rst index af11769e274..6bb616988c2 100644 --- a/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_curvilinear.rst +++ b/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_curvilinear.rst @@ -232,7 +232,8 @@ The Laplacian of a scalar field:: The Laplacian of a vector field:: - sage: Du = laplacian(u) + sage: # long time + sage: Du = laplacian(u) # long time (20s) sage: Du.display() Delta(u) = ((r^2*d^2(u_r)/dr^2 + 2*r*d(u_r)/dr - 2*u_r(r, th, ph) + d^2(u_r)/dth^2 - 2*d(u_theta)/dth)*sin(th)^2 - ((2*u_theta(r, th, ph) @@ -247,6 +248,7 @@ The Laplacian of a vector field:: Since this expression is quite lengthy, we may ask for a display component by component:: + sage: # long time sage: Du.display_comp() Delta(u)^1 = ((r^2*d^2(u_r)/dr^2 + 2*r*d(u_r)/dr - 2*u_r(r, th, ph) + d^2(u_r)/dth^2 - 2*d(u_theta)/dth)*sin(th)^2 - ((2*u_theta(r, th, ph) - d(u_r)/dth)*cos(th) @@ -260,6 +262,7 @@ component:: We may expand each component:: + sage: # long time sage: for i in E.irange(): ....: s = Du[i].expand() sage: Du.display_comp() diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 3cf1097bf96..769e19721ba 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -2193,7 +2193,7 @@ def dispatch(self): sage: DC.reporter = DR sage: DC.dispatcher = DD sage: DC.timer = Timer().start() - sage: DD.dispatch() + sage: DD.dispatch() # long time (20s) sage -t .../sage/modules/free_module_homspace.py [... tests, ...s wall] sage -t .../sage/rings/big_oh.py diff --git a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py index 4b7548a6af5..7dcad6ade7c 100644 --- a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py +++ b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py @@ -6974,7 +6974,7 @@ def Lattes_to_curve(self, return_conjugation=False, check_lattes=False): sage: P.=ProjectiveSpace(QQbar, 1) sage: E=EllipticCurve([1, 2]) sage: f=P.Lattes_map(E, 2) - sage: f.Lattes_to_curve(check_lattes=true) + sage: f.Lattes_to_curve(check_lattes=true) # long time (30s) Elliptic Curve defined by y^2 = x^3 + x + 2 over Rational Field """ diff --git a/src/sage/groups/perm_gps/permgroup_named.py b/src/sage/groups/perm_gps/permgroup_named.py index bd57a0f32c6..ca8482326c7 100644 --- a/src/sage/groups/perm_gps/permgroup_named.py +++ b/src/sage/groups/perm_gps/permgroup_named.py @@ -3513,8 +3513,8 @@ class SmallPermutationGroup(PermutationGroup_generic): [ 2 0 -1 -2 0 1] [ 2 0 -1 2 0 -1] sage: def numgps(n): return ZZ(libgap.NumberSmallGroups(n)) - sage: all(SmallPermutationGroup(n,k).id() == [n,k] - ....: for n in [1..64] for k in [1..numgps(n)]) # long time (180s) + sage: all(SmallPermutationGroup(n,k).id() == [n,k] # long time (180s) + ....: for n in [1..64] for k in [1..numgps(n)]) True sage: H = SmallPermutationGroup(6,1) sage: H.is_abelian() diff --git a/src/sage/interacts/test_jupyter.rst b/src/sage/interacts/test_jupyter.rst index bde61ee3676..57e62882749 100644 --- a/src/sage/interacts/test_jupyter.rst +++ b/src/sage/interacts/test_jupyter.rst @@ -205,7 +205,7 @@ Test all interacts from the Sage interact library:: Exact value of the integral \(\displaystyle\int_{0}^{2}x^{2} + 1\,\mathrm{d}x=4.666666666666668\) - sage: test(interacts.calculus.function_tool) + sage: test(interacts.calculus.function_tool) # long time (10s) ...Interactive function with 7 widgets f: EvalText(value='sin(x)', description='f') g: EvalText(value='cos(x)', description='g') diff --git a/src/sage/interfaces/maxima_abstract.py b/src/sage/interfaces/maxima_abstract.py index 234e9373fca..351a6d83e8e 100644 --- a/src/sage/interfaces/maxima_abstract.py +++ b/src/sage/interfaces/maxima_abstract.py @@ -304,7 +304,7 @@ def _commands(self, verbose=True): EXAMPLES:: # The output is kind of random - sage: sorted(maxima._commands(verbose=False)) + sage: sorted(maxima._commands(verbose=False)) # long time (40s) [... 'display', ... diff --git a/src/sage/rings/semirings/tropical_mpolynomial.py b/src/sage/rings/semirings/tropical_mpolynomial.py index d5f5a9d2e43..9657c13b241 100644 --- a/src/sage/rings/semirings/tropical_mpolynomial.py +++ b/src/sage/rings/semirings/tropical_mpolynomial.py @@ -543,12 +543,13 @@ def dual_subdivision(self): A subdivision with many faces, not all of which are triangles:: + sage: # long time sage: T = TropicalSemiring(QQ) sage: R. = PolynomialRing(T) sage: p3 = (R(8) + R(4)*x + R(2)*y + R(1)*x^2 + x*y + R(1)*y^2 ....: + R(2)*x^3 + x^2*y + x*y^2 + R(4)*y^3 + R(8)*x^4 ....: + R(4)*x^3*y + x^2*y^2 + R(2)*x*y^3 + y^4) - sage: pc = p3.dual_subdivision(); pc + sage: pc = p3.dual_subdivision(); pc # long time (40s) Polyhedral complex with 10 maximal cells sage: [p.Vrepresentation() for p in pc.maximal_cells_sorted()] [(A vertex at (0, 0), A vertex at (0, 1), A vertex at (1, 0)), diff --git a/src/sage/schemes/elliptic_curves/ell_generic.py b/src/sage/schemes/elliptic_curves/ell_generic.py index 32e2fac3696..f5ec5bd8512 100644 --- a/src/sage/schemes/elliptic_curves/ell_generic.py +++ b/src/sage/schemes/elliptic_curves/ell_generic.py @@ -2464,6 +2464,8 @@ def multiplication_by_m(self, m, x_only=False): sage: assert(E(eval(f,P)) == 2*P) The following test shows that :issue:`6413` is fixed for elliptic curves over finite fields:: + + sage: # long time sage: p = 7 sage: K. = GF(p^2) sage: E = EllipticCurve(K, [a + 3, 5 - a]) diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index 78f21d29fbb..25f1ac5a8b3 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -504,7 +504,7 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): ....: add_samples(1000) sage: sum(counter.values()) # random 3000 - sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: # needs sage.symbolic + sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: # needs sage.symbolic # long time (20s) ....: add_samples(1000) If the covariance provided is not positive definite, an error is thrown:: diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index ae97f236e94..fc20b61a269 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -13201,7 +13201,7 @@ cdef class Expression(Expression_abc): answer:: sage: f = ln(1+4/5*sin(x)) - sage: integrate(f, x, -3.1415, 3.1415) # random + sage: integrate(f, x, -3.1415, 3.1415) # random # long time (10s) integrate(log(4/5*sin(x) + 1), x, -3.14150000000000, 3.14150000000000) sage: # needs sage.libs.giac From ea574a8b97e79cee0ce6bc0fa146848f82074d22 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 21 Mar 2025 10:17:35 +0700 Subject: [PATCH 3/5] Show time taken in GitHub annotation --- src/sage/doctest/forker.py | 24 +++++++++++++++------ src/sage/doctest/test.py | 36 +++++++++++++++++++++++++++++++ src/sage/doctest/tests/sleep2.rst | 4 ++++ 3 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 src/sage/doctest/tests/sleep2.rst diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 769e19721ba..938e5721ee7 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -1235,7 +1235,7 @@ def compile_and_execute(self, example, compiler, globs): example.total_state = self.running_global_digest.hexdigest() example.doctest_state = self.running_doctest_digest.hexdigest() - def _failure_header(self, test, example, message='Failed example:'): + def _failure_header(self, test, example, message='Failed example:', extra=None): """ We strip out ``sage:`` prompts, so we override :meth:`doctest.DocTestRunner._failure_header` for better @@ -1247,6 +1247,14 @@ def _failure_header(self, test, example, message='Failed example:'): - ``example`` -- a :class:`doctest.Example` instance in ``test`` + - ``message`` -- a message to be shown. Must not have a newline + + - ``extra`` -- an extra message to be shown in GitHub annotation + + Note that ``message`` and ``extra`` are not accepted by + :meth:`doctest.DocTestRunner._failure_header`, as such by Liskov + substitution principle this method must be callable without passing those. + OUTPUT: string used for reporting that the given example failed EXAMPLES:: @@ -1310,6 +1318,8 @@ def _failure_header(self, test, example, message='Failed example:'): message += ' [failed in baseline]' else: command = f'::error title={message}' + if extra: + message += f': {extra}' if extra := getattr(example, 'extra', None): message += f': {extra}' if test.filename: @@ -1610,12 +1620,12 @@ def report_overtime(self, out, test, example, got, *, check_timer=None): Test ran for 1.23s cpu, 2.50s wall Check ran for 2.34s cpu, 3.12s wall """ - out(self._failure_header(test, example, 'Warning: slow doctest:') + - ('Test ran for %.2fs cpu, %.2fs wall\nCheck ran for %.2fs cpu, %.2fs wall\n' - % (example.cputime, - example.walltime, - check_timer.cputime, - check_timer.walltime))) + time_info = ('Test ran for %.2fs cpu, %.2fs wall\nCheck ran for %.2fs cpu, %.2fs wall\n' + % (example.cputime, + example.walltime, + check_timer.cputime, + check_timer.walltime)) + out(self._failure_header(test, example, 'Warning: slow doctest:', time_info) + time_info) def report_unexpected_exception(self, out, test, example, exc_info): r""" diff --git a/src/sage/doctest/test.py b/src/sage/doctest/test.py index bbfa3acef01..0d71a277460 100644 --- a/src/sage/doctest/test.py +++ b/src/sage/doctest/test.py @@ -47,6 +47,42 @@ ... 0 +Check slow doctest warnings are correctly raised:: + + sage: subprocess.call(["sage", "-t", "--warn-long", # long time + ....: "--random-seed=0", "--optional=sage", "sleep2.rst"], **kwds) + Running doctests... + Doctesting 1 file. + sage -t --warn-long --random-seed=0 sleep2.rst + ********************************************************************** + File "sleep2.rst", line 4, in sage.doctest.tests.sleep2 + Warning: slow doctest: + while walltime(t) < 2: pass + Test ran for ...s cpu, ...s wall + Check ran for ...s cpu, ...s wall + [2 tests, ...s wall] + ---------------------------------------------------------------------- + All tests passed! + ---------------------------------------------------------------------- + ... + 0 + sage: subprocess.call(["sage", "-t", "--format=github", "--warn-long", # long time + ....: "--random-seed=0", "--optional=sage", "sleep2.rst"], **kwds) + Running doctests... + Doctesting 1 file. + sage -t --warn-long --random-seed=0 sleep2.rst + ********************************************************************** + ::warning title=Warning: slow doctest:,file=sleep2.rst,line=4::slow doctest:: Test ran for ...s cpu, ...s wall%0ACheck ran for ...s cpu, ...s wall%0A + while walltime(t) < 2: pass + Test ran for ...s cpu, ...s wall + Check ran for ...s cpu, ...s wall + [2 tests, ...s wall] + ---------------------------------------------------------------------- + All tests passed! + ---------------------------------------------------------------------- + ... + 0 + Check handling of tolerances:: sage: subprocess.call(["sage", "-t", "--warn-long", "0", # long time diff --git a/src/sage/doctest/tests/sleep2.rst b/src/sage/doctest/tests/sleep2.rst new file mode 100644 index 00000000000..3e9d8c4ed78 --- /dev/null +++ b/src/sage/doctest/tests/sleep2.rst @@ -0,0 +1,4 @@ +Test raising slow doctest warning (cputime instead of walltime is checked so we need busy loop):: + + sage: t = walltime() + sage: while walltime(t) < 2: pass From 218a85989d76dc5349d83060453ff44ca0cab4da Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 5 Jul 2025 18:05:41 +0700 Subject: [PATCH 4/5] Change time limit format --- .../vector_calculus/vector_calc_curvilinear.rst | 2 +- src/sage/doctest/forker.py | 16 +++++++++------- .../arithmetic_dynamics/projective_ds.py | 2 +- src/sage/groups/perm_gps/permgroup_named.py | 2 +- src/sage/interacts/test_jupyter.rst | 2 +- src/sage/interfaces/maxima_abstract.py | 2 +- src/sage/rings/semirings/tropical_mpolynomial.py | 2 +- .../distributions/discrete_gaussian_lattice.py | 2 +- src/sage/symbolic/expression.pyx | 2 +- 9 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_curvilinear.rst b/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_curvilinear.rst index 6bb616988c2..337c3896360 100644 --- a/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_curvilinear.rst +++ b/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_curvilinear.rst @@ -233,7 +233,7 @@ The Laplacian of a scalar field:: The Laplacian of a vector field:: sage: # long time - sage: Du = laplacian(u) # long time (20s) + sage: Du = laplacian(u) # long time (limit 20s) sage: Du.display() Delta(u) = ((r^2*d^2(u_r)/dr^2 + 2*r*d(u_r)/dr - 2*u_r(r, th, ph) + d^2(u_r)/dth^2 - 2*d(u_theta)/dth)*sin(th)^2 - ((2*u_theta(r, th, ph) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index d906091b113..06ff7d91a5d 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -534,14 +534,16 @@ def _parse_example_timeout(source: str, default_timeout: float) -> float: TESTS:: sage: from sage.doctest.forker import _parse_example_timeout - sage: _parse_example_timeout("sleep(10) # long time (10s)", 5.0r) + sage: _parse_example_timeout("sleep(10) # long time (limit 10s)", 5.0r) 10.0 - sage: _parse_example_timeout("sleep(10) # long time", 5.0r) + sage: _parse_example_timeout("sleep(10) # long time (limit 10s, possible regression)", 5.0r) + 10.0 + sage: _parse_example_timeout("sleep(10) # long time (20s)", 5.0r) 5.0 - sage: _parse_example_timeout("sleep(10) # long time (1a2s)", 5.0r) + sage: _parse_example_timeout("sleep(10) # long time (limit 1a2s)", 5.0r) Traceback (most recent call last): ... - ValueError: malformed optional tag '# long time (1a2s)', should be s + ValueError: malformed optional tag '# long time (limit 1a2s)', should be '# long time (limit s)' sage: _parse_example_timeout("sleep(10) # long time (:issue:`12345`)", 5.0r) 5.0 """ @@ -554,12 +556,12 @@ def _parse_example_timeout(source: str, default_timeout: float) -> float: # or tag not present return default_timeout assert isinstance(value, str) - match = re.fullmatch(r'(\S*\d)\s*s', value.strip()) + match = re.fullmatch(r'\s*limit\s+(\S+)s(\s*,.*)?', value.strip()) if match: try: return float(match[1]) except ValueError: - raise ValueError(f"malformed optional tag '# long time ({value})', should be s") + raise ValueError(f"malformed optional tag '# long time ({value})', should be '# long time (limit s)'") else: return default_timeout @@ -2205,7 +2207,7 @@ def dispatch(self): sage: DC.reporter = DR sage: DC.dispatcher = DD sage: DC.timer = Timer().start() - sage: DD.dispatch() # long time (20s) + sage: DD.dispatch() # long time (limit 20s) sage -t .../sage/modules/free_module_homspace.py [... tests, ...s wall] sage -t .../sage/rings/big_oh.py diff --git a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py index 9cb7f4e9304..fbd2428780d 100644 --- a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py +++ b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py @@ -6974,7 +6974,7 @@ def Lattes_to_curve(self, return_conjugation=False, check_lattes=False): sage: P.=ProjectiveSpace(QQbar, 1) sage: E=EllipticCurve([1, 2]) sage: f=P.Lattes_map(E, 2) - sage: f.Lattes_to_curve(check_lattes=true) # long time (30s) + sage: f.Lattes_to_curve(check_lattes=true) # long time (limit 30s) Elliptic Curve defined by y^2 = x^3 + x + 2 over Rational Field """ diff --git a/src/sage/groups/perm_gps/permgroup_named.py b/src/sage/groups/perm_gps/permgroup_named.py index ca8482326c7..a7ed8e744f5 100644 --- a/src/sage/groups/perm_gps/permgroup_named.py +++ b/src/sage/groups/perm_gps/permgroup_named.py @@ -3513,7 +3513,7 @@ class SmallPermutationGroup(PermutationGroup_generic): [ 2 0 -1 -2 0 1] [ 2 0 -1 2 0 -1] sage: def numgps(n): return ZZ(libgap.NumberSmallGroups(n)) - sage: all(SmallPermutationGroup(n,k).id() == [n,k] # long time (180s) + sage: all(SmallPermutationGroup(n,k).id() == [n,k] # long time (limit 180s) ....: for n in [1..64] for k in [1..numgps(n)]) True sage: H = SmallPermutationGroup(6,1) diff --git a/src/sage/interacts/test_jupyter.rst b/src/sage/interacts/test_jupyter.rst index 57e62882749..61c382d2b45 100644 --- a/src/sage/interacts/test_jupyter.rst +++ b/src/sage/interacts/test_jupyter.rst @@ -205,7 +205,7 @@ Test all interacts from the Sage interact library:: Exact value of the integral \(\displaystyle\int_{0}^{2}x^{2} + 1\,\mathrm{d}x=4.666666666666668\) - sage: test(interacts.calculus.function_tool) # long time (10s) + sage: test(interacts.calculus.function_tool) # long time (limit 10s) ...Interactive function with 7 widgets f: EvalText(value='sin(x)', description='f') g: EvalText(value='cos(x)', description='g') diff --git a/src/sage/interfaces/maxima_abstract.py b/src/sage/interfaces/maxima_abstract.py index 351a6d83e8e..02bc6f7eaea 100644 --- a/src/sage/interfaces/maxima_abstract.py +++ b/src/sage/interfaces/maxima_abstract.py @@ -304,7 +304,7 @@ def _commands(self, verbose=True): EXAMPLES:: # The output is kind of random - sage: sorted(maxima._commands(verbose=False)) # long time (40s) + sage: sorted(maxima._commands(verbose=False)) # long time (limit 40s) [... 'display', ... diff --git a/src/sage/rings/semirings/tropical_mpolynomial.py b/src/sage/rings/semirings/tropical_mpolynomial.py index 9657c13b241..02602bc3f45 100644 --- a/src/sage/rings/semirings/tropical_mpolynomial.py +++ b/src/sage/rings/semirings/tropical_mpolynomial.py @@ -549,7 +549,7 @@ def dual_subdivision(self): sage: p3 = (R(8) + R(4)*x + R(2)*y + R(1)*x^2 + x*y + R(1)*y^2 ....: + R(2)*x^3 + x^2*y + x*y^2 + R(4)*y^3 + R(8)*x^4 ....: + R(4)*x^3*y + x^2*y^2 + R(2)*x*y^3 + y^4) - sage: pc = p3.dual_subdivision(); pc # long time (40s) + sage: pc = p3.dual_subdivision(); pc # long time (limit 40s) Polyhedral complex with 10 maximal cells sage: [p.Vrepresentation() for p in pc.maximal_cells_sorted()] [(A vertex at (0, 0), A vertex at (0, 1), A vertex at (1, 0)), diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index 25f1ac5a8b3..3916dfe18e1 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -504,7 +504,7 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): ....: add_samples(1000) sage: sum(counter.values()) # random 3000 - sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: # needs sage.symbolic # long time (20s) + sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: # needs sage.symbolic # long time (limit 20s) ....: add_samples(1000) If the covariance provided is not positive definite, an error is thrown:: diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 5c42a616ac6..0328d9fccaa 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -13221,7 +13221,7 @@ cdef class Expression(Expression_abc): answer:: sage: f = ln(1+4/5*sin(x)) - sage: integrate(f, x, -3.1415, 3.1415) # random # long time (10s) + sage: integrate(f, x, -3.1415, 3.1415) # random # long time (limit 10s) integrate(log(4/5*sin(x) + 1), x, -3.14150000000000, 3.14150000000000) sage: # needs sage.libs.giac From 994d0281b37c3701fc0b7c675f78e664d3c66bf0 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 5 Jul 2025 18:15:20 +0700 Subject: [PATCH 5/5] Add some more long time markers --- src/sage/doctest/forker.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 06ff7d91a5d..6087abdde2e 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -2247,6 +2247,7 @@ class should be accessed by the child process. EXAMPLES:: + sage: # long time sage: from sage.doctest.forker import DocTestWorker, DocTestTask sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.reporting import DocTestReporter @@ -2371,6 +2372,7 @@ def start(self): TESTS:: + sage: # long time sage: from sage.doctest.forker import DocTestWorker, DocTestTask sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.reporting import DocTestReporter @@ -2410,6 +2412,7 @@ def read_messages(self): EXAMPLES:: + sage: # long time sage: from sage.doctest.forker import DocTestWorker, DocTestTask sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.reporting import DocTestReporter @@ -2444,6 +2447,7 @@ def save_result_output(self): EXAMPLES:: + sage: # long time sage: from sage.doctest.forker import DocTestWorker, DocTestTask sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.reporting import DocTestReporter