From 16e3206e57fefc7d25de9dc16ed1ee58d21d44c6 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 3 Sep 2023 21:10:13 -0300 Subject: [PATCH] move private doctest to pytest for evaluation, procedural and system --- mathics/builtin/evaluation.py | 48 -------------- mathics/builtin/procedural.py | 67 ------------------- mathics/builtin/system.py | 10 --- test/builtin/test_evalution.py | 93 ++++++++++++++++++++++++++ test/builtin/test_forms.py | 3 + test/builtin/test_procedural.py | 112 ++++++++++++++++++++++++++++++++ test/builtin/test_strings.py | 3 + test/builtin/test_system.py | 29 +++++++++ test/core/parser/test_parser.py | 2 + test/helper.py | 7 +- 10 files changed, 247 insertions(+), 127 deletions(-) create mode 100644 test/builtin/test_evalution.py create mode 100644 test/builtin/test_system.py diff --git a/mathics/builtin/evaluation.py b/mathics/builtin/evaluation.py index 8458153df..cfdcce8b3 100644 --- a/mathics/builtin/evaluation.py +++ b/mathics/builtin/evaluation.py @@ -38,28 +38,6 @@ class RecursionLimit(Predefined): >> a = a + a : Recursion depth of 512 exceeded. = $Aborted - - #> $RecursionLimit = 20 - = 20 - #> a = a + a - : Recursion depth of 20 exceeded. - = $Aborted - - #> $RecursionLimit = 200 - = 200 - - #> ClearAll[f]; - #> f[x_, 0] := x; f[x_, n_] := f[x + 1, n - 1]; - #> Block[{$RecursionLimit = 20}, f[0, 100]] - = 100 - #> ClearAll[f]; - - #> ClearAll[f]; - #> f[x_, 0] := x; f[x_, n_] := Module[{y = x + 1}, f[y, n - 1]]; - #> Block[{$RecursionLimit = 20}, f[0, 100]] - : Recursion depth of 20 exceeded. - = $Aborted - #> ClearAll[f]; """ name = "$RecursionLimit" @@ -105,28 +83,6 @@ class IterationLimit(Predefined): > $IterationLimit = 1000 - #> ClearAll[f]; f[x_] := f[x + 1]; - #> f[x] - : Iteration limit of 1000 exceeded. - = $Aborted - #> ClearAll[f]; - - #> $IterationLimit = x; - : Cannot set $IterationLimit to x; value must be an integer between 20 and Infinity. - - #> ClearAll[f]; - #> f[x_, 0] := x; f[x_, n_] := f[x + 1, n - 1]; - #> Block[{$IterationLimit = 20}, f[0, 100]] - : Iteration limit of 20 exceeded. - = $Aborted - #> ClearAll[f]; - - # FIX Later - # #> ClearAll[f]; - # #> f[x_, 0] := x; f[x_, n_] := Module[{y = x + 1}, f[y, n - 1]]; - # #> Block[{$IterationLimit = 20}, f[0, 100]] - # = 100 - # #> ClearAll[f]; """ name = "$IterationLimit" @@ -280,10 +236,6 @@ class Unevaluated(Builtin): >> g[Unevaluated[Sequence[a, b, c]]] = g[Unevaluated[Sequence[a, b, c]]] - #> Attributes[h] = Flat; - #> h[items___] := Plus[items] - #> h[1, Unevaluated[Sequence[Unevaluated[2], 3]], Sequence[4, Unevaluated[5]]] - = 15 """ attributes = A_HOLD_ALL_COMPLETE | A_PROTECTED diff --git a/mathics/builtin/procedural.py b/mathics/builtin/procedural.py index bbe9f2938..93e9d4999 100644 --- a/mathics/builtin/procedural.py +++ b/mathics/builtin/procedural.py @@ -165,39 +165,6 @@ class CompoundExpression(BinaryOperator): = d If the last argument is omitted, 'Null' is taken: >> a; - - ## Parser Tests - #> FullForm[Hold[; a]] - : "FullForm[Hold[" cannot be followed by "; a]]" (line 1 of ""). - #> FullForm[Hold[; a ;]] - : "FullForm[Hold[" cannot be followed by "; a ;]]" (line 1 of ""). - - ## Issue331 - #> CompoundExpression[x, y, z] - = z - #> % - = z - - #> CompoundExpression[x, y, Null] - #> % - = y - - #> CompoundExpression[CompoundExpression[x, y, Null], Null] - #> % - = y - - #> CompoundExpression[x, Null, Null] - #> % - = x - - #> CompoundExpression[] - #> % - - ## Issue 531 - #> z = Max[1, 1 + x]; x = 2; z - = 3 - - #> Clear[x]; Clear[z] """ attributes = A_HOLD_ALL | A_PROTECTED | A_READ_PROTECTED @@ -294,10 +261,6 @@ class Do(IterationFunction): | 5 | 7 | 9 - - #> Do[Print["hi"],{1+1}] - | hi - | hi """ allow_loopcontrol = True @@ -330,12 +293,6 @@ class For(Builtin): = 3628800 >> n == 10! = True - - #> n := 1 - #> For[i=1, i<=10, i=i+1, If[i > 5, Return[i]]; n = n * i] - = 6 - #> n - = 120 """ attributes = A_HOLD_REST | A_PROTECTED @@ -473,17 +430,6 @@ class Return(Builtin): >> g[x_] := (Do[If[x < 0, Return[0]], {i, {2, 1, 0, -1}}]; x) >> g[-1] = -1 - - #> h[x_] := (If[x < 0, Return[]]; x) - #> h[1] - = 1 - #> h[-1] - - ## Issue 513 - #> f[x_] := Return[x]; - #> g[y_] := Module[{}, z = f[y]; 2] - #> g[1] - = 2 """ rules = { @@ -518,16 +464,6 @@ class Switch(Builtin): >> Switch[2, 1] : Switch called with 2 arguments. Switch must be called with an odd number of arguments. = Switch[2, 1] - - #> a; Switch[b, b] - : Switch called with 2 arguments. Switch must be called with an odd number of arguments. - = Switch[b, b] - - ## Issue 531 - #> z = Switch[b, b]; - : Switch called with 2 arguments. Switch must be called with an odd number of arguments. - #> z - = Switch[b, b] """ summary_text = "switch based on a value, with patterns allowed" @@ -636,9 +572,6 @@ class While(Builtin): >> While[b != 0, {a, b} = {b, Mod[a, b]}]; >> a = 3 - - #> i = 1; While[True, If[i^2 > 100, Return[i + 1], i++]] - = 12 """ summary_text = "evaluate an expression while a criterion is true" diff --git a/mathics/builtin/system.py b/mathics/builtin/system.py index b4db5b05b..b48e6ecc2 100644 --- a/mathics/builtin/system.py +++ b/mathics/builtin/system.py @@ -174,8 +174,6 @@ class Packages(Predefined): X> $Packages = {ImportExport`,XML`,Internal`,System`,Global`} - #> MemberQ[$Packages, "System`"] - = True """ summary_text = "list the packages loaded in the current session" @@ -197,8 +195,6 @@ class ParentProcessID(Predefined): >> $ParentProcessID = ... - #> Head[$ParentProcessID] == Integer - = True """ summary_text = "id of the process that invoked Mathics" name = "$ParentProcessID" @@ -218,9 +214,6 @@ class ProcessID(Predefined): >> $ProcessID = ... - - #> Head[$ProcessID] == Integer - = True """ summary_text = "id of the Mathics process" name = "$ProcessID" @@ -348,9 +341,6 @@ class SystemWordLength(Predefined): X> $SystemWordLength = 64 - - #> Head[$SystemWordLength] == Integer - = True """ summary_text = "word length of computer system" name = "$SystemWordLength" diff --git a/test/builtin/test_evalution.py b/test/builtin/test_evalution.py new file mode 100644 index 000000000..5b678bd9f --- /dev/null +++ b/test/builtin/test_evalution.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +""" +Unit tests from mathics.builtin.evaluation. +""" + + +from test.helper import check_evaluation, session + +import pytest + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ("ClearAll[a];$RecursionLimit = 20", None, "20", None), + ("a = a + a", ("Recursion depth of 20 exceeded.",), "$Aborted", None), + ("$RecursionLimit = 200", None, "200", None), + ( + "ClearAll[f];f[x_, 0] := x; f[x_, n_] := f[x + 1, n - 1];Block[{$RecursionLimit = 20}, f[0, 100]]", + None, + "100", + None, + ), + ( + "ClearAll[f];f[x_, 0] := x; f[x_, n_] := Module[{y = x + 1}, f[y, n - 1]];Block[{$RecursionLimit = 20}, f[0, 100]]", + ("Recursion depth of 20 exceeded.",), + "$Aborted", + None, + ), + ( + "ClearAll[f]; f[x_] := f[x + 1];f[x]", + ("Iteration limit of 1000 exceeded.",), + "$Aborted", + None, + ), + ( + "$IterationLimit = x;", + ( + "Cannot set $IterationLimit to x; value must be an integer between 20 and Infinity.", + ), + None, + None, + ), + ( + "ClearAll[f];f[x_, 0] := x; f[x_, n_] := f[x + 1, n - 1];Block[{$IterationLimit = 20}, f[0, 100]]", + ("Iteration limit of 20 exceeded.",), + "$Aborted", + None, + ), + ("ClearAll[f];", None, None, None), + ( + "Attributes[h] = Flat;h[items___] := Plus[items];h[1, Unevaluated[Sequence[Unevaluated[2], 3]], Sequence[4, Unevaluated[5]]]", + None, + "15", + None, + ), + # FIX Later + ( + "ClearAll[f];f[x_, 0] := x; f[x_, n_] := Module[{y = x + 1}, f[y, n - 1]];Block[{$IterationLimit = 20}, f[0, 100]]", + None, + "100", + "Fix me!", + ), + ("ClearAll[f];", None, None, None), + ], +) +def test_private_doctests_evaluation(str_expr, msgs, str_expected, fail_msg): + """These tests check the behavior of $RecursionLimit and $IterationLimit""" + + # Here we do not use the session object to check the messages + # produced by the exceptions. If $RecursionLimit / $IterationLimit + # are reached during the evaluation using a MathicsSession object, + # an exception is raised. On the other hand, using the `Evaluation.evaluate` + # method, the exception is handled. + # + # TODO: Maybe it makes sense to clone this exception handling in + # the check_evaluation function. + # + def eval_expr(expr_str): + query = session.evaluation.parse(expr_str) + res = session.evaluation.evaluate(query) + session.evaluation.stopped = False + return res + + res = eval_expr(str_expr) + if msgs is None: + assert len(res.out) == 0 + else: + assert len(res.out) == len(msgs) + for li1, li2 in zip(res.out, msgs): + assert li1.text == li2 + + assert res.result == str_expected diff --git a/test/builtin/test_forms.py b/test/builtin/test_forms.py index f00342ff8..894a9de18 100644 --- a/test/builtin/test_forms.py +++ b/test/builtin/test_forms.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +""" +Unit tests from mathics.builtin.forms. +""" from test.helper import check_evaluation, session diff --git a/test/builtin/test_procedural.py b/test/builtin/test_procedural.py index 779ec683c..7efbb5922 100644 --- a/test/builtin/test_procedural.py +++ b/test/builtin/test_procedural.py @@ -1,4 +1,8 @@ # -*- coding: utf-8 -*- +""" +Unit tests from mathics.builtin.procedural. +""" + from test.helper import check_evaluation, session import pytest @@ -19,3 +23,111 @@ def test_nestwhile(str_expr, str_expected): check_evaluation( str_expr, str_expected, to_string_expr=True, to_string_expected=True ) + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + ("res=CompoundExpression[x, y, z]", None, "z", None), + ("res", None, "z", "Issue 331"), + ("z = Max[1, 1 + x]; x = 2; z", None, "3", "Issue 531"), + ("Clear[x]; Clear[z]; Clear[res];", None, "Null", None), + ( + 'Do[Print["hi"],{1+1}]', + ( + "hi", + "hi", + ), + "Null", + None, + ), + ( + "n := 1; For[i=1, i<=10, i=i+1, If[i > 5, Return[i]]; n = n * i]", + None, + "6", + None, + ), + ("n", None, "120", "Side effect of the previous test"), + ("h[x_] := (If[x < 0, Return[]]; x)", None, "Null", None), + ("h[1]", None, "1", None), + ("h[-1]", None, "Null", None), + ("f[x_] := Return[x];g[y_] := Module[{}, z = f[y]; 2]", None, "Null", None), + ("g[1]", None, "2", "Issue 513"), + ( + "a; Switch[b, b]", + ( + "Switch called with 2 arguments. Switch must be called with an odd number of arguments.", + ), + "Switch[b, b]", + None, + ), + ## Issue 531 + ( + "z = Switch[b, b];", + ( + "Switch called with 2 arguments. Switch must be called with an odd number of arguments.", + ), + "Null", + "Issue 531", + ), + ("z", None, "Switch[b, b]", "Issue 531"), + ("i = 1; While[True, If[i^2 > 100, Return[i + 1], i++]]", None, "12", None), + # These tests check the result of a compound expression which finish with Null. + # The result is different to the one obtained if we use the history (`%`) + # which is test in `test_history_compound_expression` + ("res=CompoundExpression[x, y, Null]", None, "Null", None), + ("res", None, "Null", None), + ( + "res=CompoundExpression[CompoundExpression[x, y, Null], Null]", + None, + "Null", + None, + ), + ("res", None, "Null", None), + ("res=CompoundExpression[x, Null, Null]", None, "Null", None), + ("res", None, "Null", None), + ("res=CompoundExpression[]", None, "Null", None), + ("res", None, "Null", None), + ( + "Clear[f];Clear[g];Clear[h];Clear[i];Clear[n];Clear[res];Clear[z]; ", + None, + "Null", + None, + ), + ], +) +def test_private_doctests_procedural(str_expr, msgs, str_expected, fail_msg): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) + + +def test_history_compound_expression(): + """Test the effect in the history from the evaluation of a CompoundExpression""" + + def eval_expr(expr_str): + query = session.evaluation.parse(expr_str) + return session.evaluation.evaluate(query) + + eval_expr("Clear[x];Clear[y]") + eval_expr("CompoundExpression[x, y, Null]") + assert eval_expr("ToString[%]").result == "y" + eval_expr("CompoundExpression[CompoundExpression[y, x, Null], Null]") + assert eval_expr("ToString[%]").result == "x" + eval_expr("CompoundExpression[x, y, Null, Null]") + assert eval_expr("ToString[%]").result == "y" + eval_expr("CompoundExpression[]") + assert eval_expr("ToString[%]").result == "Null" + eval_expr("Clear[x];Clear[y]") + # Calling `session.evaluation.evaluate` ends by + # set the flag `stopped` to `True`, which produces + # a timeout exception if we evaluate an expression from + # its `evaluate` method... + session.evaluation.stopped = False diff --git a/test/builtin/test_strings.py b/test/builtin/test_strings.py index dda077505..ea095a2ea 100644 --- a/test/builtin/test_strings.py +++ b/test/builtin/test_strings.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +""" +Unit tests from mathics.builtin.string. +""" from test.helper import check_evaluation, session diff --git a/test/builtin/test_system.py b/test/builtin/test_system.py new file mode 100644 index 000000000..ed35753ea --- /dev/null +++ b/test/builtin/test_system.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +""" +Unit tests from mathics.builtin.system. +""" + + +from test.helper import check_evaluation, session + +import pytest + + +@pytest.mark.parametrize( + ("str_expr", "str_expected"), + [ + ('MemberQ[$Packages, "System`"]', "True"), + ("Head[$ParentProcessID] == Integer", "True"), + ("Head[$ProcessID] == Integer", "True"), + ("Head[$SystemWordLength] == Integer", "True"), + ], +) +def test_private_doctests_system(str_expr, str_expected): + """ """ + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + ) diff --git a/test/core/parser/test_parser.py b/test/core/parser/test_parser.py index 74310664c..293a6675d 100644 --- a/test/core/parser/test_parser.py +++ b/test/core/parser/test_parser.py @@ -172,6 +172,8 @@ def testPrecision(self): class GeneralTests(ParserTests): def testCompound(self): + self.invalid_error("FullForm[Hold[; a]]") + self.invalid_error("FullForm[Hold[; a ;]]") self.check( "a ; {b}", Node("CompoundExpression", Symbol("a"), Node("List", Symbol("b"))), diff --git a/test/helper.py b/test/helper.py index fa7b87d47..89c279bd3 100644 --- a/test/helper.py +++ b/test/helper.py @@ -28,7 +28,7 @@ def evaluate(str_expr: str): def check_evaluation( str_expr: str, - str_expected: str, + str_expected: Optional[str] = None, failure_message: str = "", hold_expected: bool = False, to_string_expr: bool = True, @@ -41,7 +41,8 @@ def check_evaluation( its results Compares the expressions represented by ``str_expr`` and ``str_expected`` by - evaluating the first, and optionally, the second. + evaluating the first, and optionally, the second. If ommited, `str_expected` + is assumed to be `"Null"`. to_string_expr: If ``True`` (default value) the result of the evaluation is converted into a Python string. Otherwise, the expression is kept @@ -72,6 +73,8 @@ def check_evaluation( if str_expr is None: reset_session() return + if str_expected is None: + str_expected = "Null" if to_string_expr: str_expr = f"ToString[{str_expr}]"