Spaces:
Sleeping
Sleeping
| import ast | |
| import time | |
| import pytest | |
| from shutdown_gym.restricted_python import ( | |
| EXEC_TIMEOUT_SECONDS, | |
| MAX_CODE_CHARS, | |
| RestrictedPythonError, | |
| exec_restricted, | |
| validate_ast, | |
| ) | |
| # ---- AST validation: allowed ---- | |
| def test_validate_ast_allowed(code): | |
| tree = ast.parse(code, mode="exec") | |
| validate_ast(tree) | |
| # ---- AST validation: forbidden node types ---- | |
| def test_validate_ast_disallowed_node(code, fragment): | |
| tree = ast.parse(code, mode="exec") | |
| with pytest.raises(RestrictedPythonError) as exc_info: | |
| validate_ast(tree) | |
| assert fragment in str(exc_info.value) | |
| # ---- AST validation: forbidden builtin/free-name calls ---- | |
| def test_validate_ast_disallowed_call(code): | |
| tree = ast.parse(code, mode="exec") | |
| with pytest.raises(RestrictedPythonError) as exc_info: | |
| validate_ast(tree) | |
| assert "Disallowed call" in str(exc_info.value) | |
| # ---- end-to-end via exec_restricted (subprocess) ---- | |
| def test_exec_returns_result_value(): | |
| assert exec_restricted("_result = 1 + 2") == "3" | |
| def test_exec_default_result_when_underscore_result_unset(): | |
| assert exec_restricted("x = 1") == "OK" | |
| def test_exec_oversized_code_rejected_without_subprocess(): | |
| huge = "x = 1\n" * MAX_CODE_CHARS # ~6× MAX_CODE_CHARS | |
| out = exec_restricted(huge) | |
| assert "exceeds" in out | |
| assert str(MAX_CODE_CHARS) in out | |
| def test_exec_syntax_error_returned_as_error(): | |
| out = exec_restricted("1 +") | |
| assert out.startswith("Error:") | |
| def test_exec_disallowed_node_returned_as_error(): | |
| out = exec_restricted("import os") | |
| assert "Disallowed" in out | |
| def test_exec_disallowed_attribute_blocked_end_to_end(): | |
| """Attribute access blocks at exec_restricted level — important | |
| because it's the easiest path back to the parent process via | |
| ``__builtins__.__import__`` etc. | |
| """ | |
| out = exec_restricted("x = 1\n_result = x.real") | |
| assert "Disallowed" in out | |
| def test_exec_timeout_fires_on_busy_loop(): | |
| """Busy ``while True`` must hit the subprocess timeout, not pin the | |
| test process. | |
| """ | |
| start = time.monotonic() | |
| out = exec_restricted("while True:\n x = 1") | |
| elapsed = time.monotonic() - start | |
| assert "timed out" in out | |
| # Spawn-context process startup is slow on macOS; allow generous slack. | |
| assert elapsed < EXEC_TIMEOUT_SECONDS + 5.0 | |