From dc4c436dba6de3f366ac10bd38392d3d97d9d3a2 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Tue, 16 Jan 2024 14:54:36 -0300 Subject: [PATCH] MathicsSession.evaluate_as_in_cli (#931) This PR implements a method in the `MathicsSession` class that uses the `Evaluation.evaluate` method. This allows to handle exceptions and special symbols like % or Line references. This method is used in certain pytests. TODO: define a better name for the method and improve docstrings... --- mathics/session.py | 8 +++++ test/builtin/test_attributes.py | 28 ++-------------- .../{test_evalution.py => test_evaluation.py} | 33 ++----------------- test/builtin/test_functional.py | 19 ++--------- test/builtin/test_messages.py | 19 ++--------- test/builtin/test_procedural.py | 17 ++++++++-- test/helper.py | 29 +++++++++++++++- 7 files changed, 59 insertions(+), 94 deletions(-) rename test/builtin/{test_evalution.py => test_evaluation.py} (68%) diff --git a/mathics/session.py b/mathics/session.py index ae23eaa6c..874b61a2a 100644 --- a/mathics/session.py +++ b/mathics/session.py @@ -86,6 +86,7 @@ def reset(self, add_builtin=True, catch_interrupt=False): self.last_result = None def evaluate(self, str_expression, timeout=None, form=None): + """Parse str_expression and evaluate using the `evaluate` method of the Expression""" self.evaluation.out.clear() expr = parse(self.definitions, MathicsSingleLineFeeder(str_expression)) if form is None: @@ -93,6 +94,13 @@ def evaluate(self, str_expression, timeout=None, form=None): self.last_result = expr.evaluate(self.evaluation) return self.last_result + def evaluate_as_in_cli(self, str_expression, timeout=None, form=None): + """This method parse and evaluate the expression using the session.evaluation.evaluate method""" + query = self.evaluation.parse(str_expression) + res = self.evaluation.evaluate(query) + self.evaluation.stopped = False + return res + def format_result(self, str_expression=None, timeout=None, form=None): if str_expression: self.evaluate(str_expression, timeout=None, form=None) diff --git a/test/builtin/test_attributes.py b/test/builtin/test_attributes.py index f1bdbd073..d145c246c 100644 --- a/test/builtin/test_attributes.py +++ b/test/builtin/test_attributes.py @@ -4,7 +4,7 @@ """ import os -from test.helper import check_evaluation, session +from test.helper import check_evaluation, check_evaluation_as_in_cli, session import pytest @@ -282,28 +282,4 @@ def test_private_doctests_attributes_with_exceptions( 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 + check_evaluation_as_in_cli(str_expr, str_expected, fail_msg, msgs) diff --git a/test/builtin/test_evalution.py b/test/builtin/test_evaluation.py similarity index 68% rename from test/builtin/test_evalution.py rename to test/builtin/test_evaluation.py index 50f43c6a3..86011239f 100644 --- a/test/builtin/test_evalution.py +++ b/test/builtin/test_evaluation.py @@ -4,7 +4,7 @@ """ -from test.helper import check_evaluation, reset_session, session +from test.helper import check_evaluation_as_in_cli, session import pytest @@ -72,33 +72,4 @@ ) 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. - # - - if str_expr is None: - reset_session() - return - - 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 + check_evaluation_as_in_cli(str_expr, str_expected, fail_msg, msgs) diff --git a/test/builtin/test_functional.py b/test/builtin/test_functional.py index 2522cdc38..ef697c540 100644 --- a/test/builtin/test_functional.py +++ b/test/builtin/test_functional.py @@ -5,7 +5,7 @@ import sys import time -from test.helper import check_evaluation, evaluate, session +from test.helper import check_evaluation, check_evaluation_as_in_cli, evaluate, session import pytest @@ -95,22 +95,7 @@ ) def test_private_doctests_apply_fns_to_lists(str_expr, msgs, str_expected, fail_msg): """functional.apply_fns_to_lists""" - - 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 + check_evaluation_as_in_cli(str_expr, str_expected, fail_msg, msgs) @pytest.mark.parametrize( diff --git a/test/builtin/test_messages.py b/test/builtin/test_messages.py index e8af0cedf..106e02894 100644 --- a/test/builtin/test_messages.py +++ b/test/builtin/test_messages.py @@ -4,7 +4,7 @@ """ -from test.helper import check_evaluation, session +from test.helper import check_evaluation_as_in_cli, session import pytest @@ -136,19 +136,4 @@ ) def test_private_doctests_messages(str_expr, msgs, str_expected, fail_msg): """These tests check the behavior the module messages""" - - 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 + check_evaluation_as_in_cli(str_expr, str_expected, fail_msg, msgs) diff --git a/test/builtin/test_procedural.py b/test/builtin/test_procedural.py index bdeed120b..ca0105bfb 100644 --- a/test/builtin/test_procedural.py +++ b/test/builtin/test_procedural.py @@ -3,7 +3,7 @@ Unit tests from mathics.builtin.procedural. """ -from test.helper import check_evaluation, session +from test.helper import check_evaluation, check_evaluation_as_in_cli, session import pytest @@ -117,6 +117,19 @@ def test_private_doctests_procedural(str_expr, msgs, str_expected, fail_msg): def test_history_compound_expression(): """Test the effect in the history from the evaluation of a CompoundExpression""" + check_evaluation_as_in_cli("Clear[x];Clear[y]") + check_evaluation_as_in_cli("CompoundExpression[x, y, Null]") + check_evaluation_as_in_cli("ToString[%]", "y") + check_evaluation_as_in_cli( + "CompoundExpression[CompoundExpression[y, x, Null], Null]" + ) + check_evaluation_as_in_cli("ToString[%]", "x") + check_evaluation_as_in_cli("CompoundExpression[x, y, Null, Null]") + check_evaluation_as_in_cli("ToString[%]", "y") + check_evaluation_as_in_cli("CompoundExpression[]") + check_evaluation_as_in_cli("ToString[%]", "Null") + check_evaluation_as_in_cli("Clear[x];Clear[y];") + return def eval_expr(expr_str): query = session.evaluation.parse(expr_str) @@ -125,7 +138,7 @@ def eval_expr(expr_str): 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]") + 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" diff --git a/test/helper.py b/test/helper.py index 89c279bd3..49ba6aeb2 100644 --- a/test/helper.py +++ b/test/helper.py @@ -27,7 +27,7 @@ def evaluate(str_expr: str): def check_evaluation( - str_expr: str, + str_expr: Optional[str], str_expected: Optional[str] = None, failure_message: str = "", hold_expected: bool = False, @@ -122,3 +122,30 @@ def check_evaluation( print(" and ") print(f"expected=<<{msg}>>") assert False, " do not match." + + +def check_evaluation_as_in_cli( + str_expr: Optional[str] = None, + str_expected: Optional[str] = None, + failure_message: str = "", + expected_messages: Optional[tuple] = None, +): + """ + Use this method when special Symbols like Return, %, %%, + $IterationLimit, $RecursionLimit, etc. are used in the tests. + """ + if str_expr is None: + reset_session() + return + + res = session.evaluate_as_in_cli(str_expr) + if expected_messages is None: + assert len(res.out) == 0 + else: + assert len(res.out) == len(expected_messages) + for li1, li2 in zip(res.out, expected_messages): + assert li1.text == li2 + + if failure_message: + assert res.result == str_expected, failure_message + assert res.result == str_expected