From 8d0c2caa0d52dcbd7b05e38f65159fed937b1751 Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Fri, 3 Feb 2017 17:31:53 +0100 Subject: [PATCH 1/7] Test `generic_visit`. --- tests/test_transformer.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_transformer.py b/tests/test_transformer.py index ae71239..cc4b35a 100644 --- a/tests/test_transformer.py +++ b/tests/test_transformer.py @@ -1,16 +1,29 @@ from . import compile from . import execute +from RestrictedPython import RestrictingNodeTransformer from RestrictedPython._compat import IS_PY2 from RestrictedPython._compat import IS_PY3 from RestrictedPython.Guards import guarded_iter_unpack_sequence from RestrictedPython.Guards import guarded_unpack_sequence +import ast import contextlib import pytest import RestrictedPython import types +def test_transformer__RestrictingNodeTransformer__generic_visit__1(): + """It log an error if there is an unknown ast node visited.""" + class MyFancyNode(ast.AST): + pass + + transformer = RestrictingNodeTransformer() + transformer.visit(MyFancyNode()) + assert transformer.errors == [ + 'Line None: MyFancyNode statements are not allowed.'] + + @pytest.mark.parametrize(*compile) def test_transformer__RestrictingNodeTransformer__visit_Num__1(compile): """It compiles a number successfully.""" From 3467fbfe04b91c9f7618b6c916f5bcc855718cbe Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Fri, 3 Feb 2017 17:50:30 +0100 Subject: [PATCH 2/7] Allow all base datatypes. --- src/RestrictedPython/transformer.py | 24 ++++++++---------------- tests/test_transformer.py | 28 +++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/RestrictedPython/transformer.py b/src/RestrictedPython/transformer.py index b4750f7..3d16abe 100644 --- a/src/RestrictedPython/transformer.py +++ b/src/RestrictedPython/transformer.py @@ -515,38 +515,30 @@ def visit_Num(self, node): return self.node_contents_visit(node) def visit_Str(self, node): - """Allow strings without restrictions.""" + """Allow string literals without restrictions.""" return self.node_contents_visit(node) def visit_Bytes(self, node): - """Allow bytes without restrictions. + """Allow bytes literals without restrictions. Bytes is Python 3 only. """ - self.not_allowed(node) + return self.node_contents_visit(node) def visit_List(self, node): - """ - - """ + """Allow list literals without restrictions.""" return self.node_contents_visit(node) def visit_Tuple(self, node): - """ - - """ + """Allow tuple literals without restrictions.""" return self.node_contents_visit(node) def visit_Set(self, node): - """ - - """ - self.not_allowed(node) + """Allow set literals without restrictions.""" + return self.node_contents_visit(node) def visit_Dict(self, node): - """ - - """ + """Allow dict literals without restrictions.""" return self.node_contents_visit(node) def visit_Ellipsis(self, node): diff --git a/tests/test_transformer.py b/tests/test_transformer.py index cc4b35a..2097664 100644 --- a/tests/test_transformer.py +++ b/tests/test_transformer.py @@ -24,12 +24,30 @@ class MyFancyNode(ast.AST): 'Line None: MyFancyNode statements are not allowed.'] +@pytest.mark.parametrize(*execute) +def test_transformer__RestrictingNodeTransformer__visit_Num__1(execute): + """It allows to use number literals.""" + glb = execute('a = 42') + assert glb['a'] == 42 + + +@pytest.mark.parametrize(*execute) +def test_transformer__RestrictingNodeTransformer__visit_Bytes__1(execute): + """It allows to use bytes literals.""" + glb = execute('a = b"code"') + assert glb['a'] == b"code" + + +@pytest.mark.parametrize(*execute) +def test_transformer__RestrictingNodeTransformer__visit_Set__1(execute): + """It allows to use bytes literals.""" + glb = execute('a = {1, 2, 3}') + assert glb['a'] == set([1, 2, 3]) + + +@pytest.mark.skipif(IS_PY2, + reason="Ellipsis is new in Python 3") @pytest.mark.parametrize(*compile) -def test_transformer__RestrictingNodeTransformer__visit_Num__1(compile): - """It compiles a number successfully.""" - result = compile('42') - assert result.errors == () - assert str(result.code.__class__.__name__) == 'code' @pytest.mark.parametrize(*compile) From 29a76596acfc19685108cfc384fc6afe9e8461f5 Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Fri, 3 Feb 2017 17:51:23 +0100 Subject: [PATCH 3/7] Deny Ellipsis - I see no reason to allow it, sorry. --- src/RestrictedPython/transformer.py | 3 ++- tests/test_transformer.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/RestrictedPython/transformer.py b/src/RestrictedPython/transformer.py index 3d16abe..bbb7a63 100644 --- a/src/RestrictedPython/transformer.py +++ b/src/RestrictedPython/transformer.py @@ -542,8 +542,9 @@ def visit_Dict(self, node): return self.node_contents_visit(node) def visit_Ellipsis(self, node): - """ + """Deny using `...`. + Ellipsis is exists only in Python 3. """ self.not_allowed(node) diff --git a/tests/test_transformer.py b/tests/test_transformer.py index 2097664..b3c62f1 100644 --- a/tests/test_transformer.py +++ b/tests/test_transformer.py @@ -48,6 +48,10 @@ def test_transformer__RestrictingNodeTransformer__visit_Set__1(execute): @pytest.mark.skipif(IS_PY2, reason="Ellipsis is new in Python 3") @pytest.mark.parametrize(*compile) +def test_transformer__RestrictingNodeTransformer__visit_Ellipsis__1(compile): + """It prevents using the `ellipsis` statement.""" + result = compile('...') + assert result.errors == ('Line 1: Ellipsis statements are not allowed.',) @pytest.mark.parametrize(*compile) From 271d82a6cb69785d3f7f17e4076a9d90fb592d3f Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Fri, 3 Feb 2017 20:25:27 +0100 Subject: [PATCH 4/7] Fix untested code and test it. --- src/RestrictedPython/transformer.py | 2 +- tests/test_transformer.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/RestrictedPython/transformer.py b/src/RestrictedPython/transformer.py index bbb7a63..2ae3eb1 100644 --- a/src/RestrictedPython/transformer.py +++ b/src/RestrictedPython/transformer.py @@ -494,7 +494,7 @@ def generic_visit(self, node): # ) self.warn( node, - '{o.__class__.__name__}' + '{0.__class__.__name__}' ' statement is not known to RestrictedPython'.format(node) ) self.not_allowed(node) diff --git a/tests/test_transformer.py b/tests/test_transformer.py index b3c62f1..98218be 100644 --- a/tests/test_transformer.py +++ b/tests/test_transformer.py @@ -22,6 +22,8 @@ class MyFancyNode(ast.AST): transformer.visit(MyFancyNode()) assert transformer.errors == [ 'Line None: MyFancyNode statements are not allowed.'] + assert transformer.warnings == [ + 'Line None: MyFancyNode statement is not known to RestrictedPython'] @pytest.mark.parametrize(*execute) From dccd23cc249ae49ad81b77f675800c423f3eb3f8 Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Sat, 4 Feb 2017 12:32:27 +0100 Subject: [PATCH 5/7] Fix doc. --- tests/test_transformer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_transformer.py b/tests/test_transformer.py index 98218be..dc10a41 100644 --- a/tests/test_transformer.py +++ b/tests/test_transformer.py @@ -48,7 +48,7 @@ def test_transformer__RestrictingNodeTransformer__visit_Set__1(execute): @pytest.mark.skipif(IS_PY2, - reason="Ellipsis is new in Python 3") + reason="... is new in Python 3") @pytest.mark.parametrize(*compile) def test_transformer__RestrictingNodeTransformer__visit_Ellipsis__1(compile): """It prevents using the `ellipsis` statement.""" From 181eb8858c29aa5cc587f60c7de6ffeb93927c81 Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Sat, 4 Feb 2017 12:56:42 +0100 Subject: [PATCH 6/7] Testing Expression node and compile_restricted_eval. --- src/RestrictedPython/transformer.py | 7 ++++--- tests/__init__.py | 4 ++++ tests/test_compile.py | 21 +++++++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/RestrictedPython/transformer.py b/src/RestrictedPython/transformer.py index 2ae3eb1..a3fb2c0 100644 --- a/src/RestrictedPython/transformer.py +++ b/src/RestrictedPython/transformer.py @@ -613,15 +613,16 @@ def visit_Starred(self, node): return self.node_contents_visit(node) # Expressions + def visit_Expression(self, node): """Allow Expression statements without restrictions. - Python 2 only AST Element. + + They are in the AST when using the `eval` compile mode. """ return self.node_contents_visit(node) def visit_Expr(self, node): - """Allow Expr statements without restrictions. - Python 3+ AST Element.""" + """Allow Expr statements (any expression) without restrictions.""" return self.node_contents_visit(node) def visit_UnaryOp(self, node): diff --git a/tests/__init__.py b/tests/__init__.py index 9e787b4..36f112a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -21,7 +21,11 @@ def _execute(source, glb=None): compile = ('compile', [RestrictedPython.compile.compile_restricted_exec]) execute = ('execute', [_execute(RestrictedPython.compile.compile_restricted_exec)]) +compile_eval = ('compile_eval', + [RestrictedPython.compile.compile_restricted_eval]) + if IS_PY2: from RestrictedPython import RCompile compile[1].append(RCompile.compile_restricted_exec) + compile_eval[1].append(RCompile.compile_restricted_eval) execute[1].append(_execute(RCompile.compile_restricted_exec)) diff --git a/tests/test_compile.py b/tests/test_compile.py index c5de7e9..40fb0a9 100644 --- a/tests/test_compile.py +++ b/tests/test_compile.py @@ -1,4 +1,5 @@ from . import compile +from . import compile_eval from RestrictedPython import compile_restricted from RestrictedPython import CompileResult from RestrictedPython._compat import IS_PY2 @@ -114,3 +115,23 @@ def test_compile__compile_restricted_exec__10(compile): assert ( "Line 2: SyntaxError: Missing parentheses in call to 'exec' in on " "statement: exec 'q = 1'",) == result.errors + + +FUNCTION_DEF = """\ +def a(): + pass +""" + + +@pytest.mark.parametrize(*compile_eval) +def test_compile__compile_restricted_eval__1(compile_eval): + """It compiles code as an Expression. + + Function definitions are not allowed in Expressions. + """ + result = compile_eval(FUNCTION_DEF) + if compile_eval is RestrictedPython.compile.compile_restricted_eval: + assert result.errors == ( + 'Line 1: SyntaxError: invalid syntax in on statement: def a():',) + else: + assert result.errors == ('invalid syntax (, line 1)',) From 771d98d4fdc76b67031a13f530d0b8032483a3e8 Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Sat, 4 Feb 2017 13:45:00 +0100 Subject: [PATCH 7/7] Actually test Expression nodes. --- tests/__init__.py | 31 +++++++++++++++++++++++++------ tests/test_compile.py | 7 +++++++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 36f112a..1374915 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -3,29 +3,48 @@ import RestrictedPython +def _compile(compile_func, source): + """Compile some source with a compile func.""" + result = compile_func(source) + assert result.errors == (), result.errors + assert result.code is not None + return result.code + + def _execute(compile_func): """Factory to create an execute function.""" def _execute(source, glb=None): - result = compile_func(source) - assert result.errors == (), result.errors - assert result.code is not None + code = _compile(compile_func, source) if glb is None: glb = {} - exec(result.code, glb) + exec(code, glb) return glb return _execute +def _eval(compile_func): + """Factory to create an eval function.""" + def _eval(source, glb=None): + code = _compile(compile_func, source) + if glb is None: + glb = {} + return eval(code, glb) + return _eval + + # Define the arguments for @pytest.mark.parametrize to be able to test both the # old and the new implementation to be equal: compile = ('compile', [RestrictedPython.compile.compile_restricted_exec]) -execute = ('execute', - [_execute(RestrictedPython.compile.compile_restricted_exec)]) compile_eval = ('compile_eval', [RestrictedPython.compile.compile_restricted_eval]) +execute = ('execute', + [_execute(RestrictedPython.compile.compile_restricted_exec)]) +r_eval = ('r_eval', + [_eval(RestrictedPython.compile.compile_restricted_eval)]) if IS_PY2: from RestrictedPython import RCompile compile[1].append(RCompile.compile_restricted_exec) compile_eval[1].append(RCompile.compile_restricted_eval) execute[1].append(_execute(RCompile.compile_restricted_exec)) + r_eval[1].append(_eval(RCompile.compile_restricted_eval)) diff --git a/tests/test_compile.py b/tests/test_compile.py index 40fb0a9..c4aaf56 100644 --- a/tests/test_compile.py +++ b/tests/test_compile.py @@ -1,5 +1,6 @@ from . import compile from . import compile_eval +from . import r_eval from RestrictedPython import compile_restricted from RestrictedPython import CompileResult from RestrictedPython._compat import IS_PY2 @@ -135,3 +136,9 @@ def test_compile__compile_restricted_eval__1(compile_eval): 'Line 1: SyntaxError: invalid syntax in on statement: def a():',) else: assert result.errors == ('invalid syntax (, line 1)',) + + +@pytest.mark.parametrize(*r_eval) +def test_compile__compile_restricted_eval__2(r_eval): + """It compiles code as an Expression.""" + assert r_eval('4 * 6') == 24