From a7312fc45c26d6eff8e3cc78e9871ab0a02a9d93 Mon Sep 17 00:00:00 2001
From: RinZ27 <222222878+RinZ27@users.noreply.github.com>
Date: Fri, 30 Jan 2026 21:06:08 +0700
Subject: [PATCH] Security: restrict external command execution and system info
in sandbox mode
---
Makefile | 8 +-
mathics/__main__.py | 5 +-
mathics/builtin/system.py | 219 ++++++++++++++++++++++-----
mathics/core/convert/op.py | 4 +-
mathics/core/parser/operators.py | 4 +-
mathics/doc/doc_entries.py | 9 +-
mathics/doc/latex_doc.py | 2 +-
mathics/format/form/util.py | 15 +-
mathics/interrupt.py | 10 +-
mathics/settings.py | 18 +++
test/builtin/drawing/test_image.py | 4 +-
test/builtin/test_file_operations.py | 4 +-
test/builtin/test_system.py | 23 ++-
test/doc/test_doctests.py | 17 ++-
test/test_evaluation.py | 47 +++++-
15 files changed, 308 insertions(+), 81 deletions(-)
diff --git a/Makefile b/Makefile
index ffad84fd6..95fa2df8a 100644
--- a/Makefile
+++ b/Makefile
@@ -44,13 +44,13 @@ MATHICS3_MODULE_OPTION ?= --load-module pymathics.graph,pymathics.natlang
test \
texdoc
-SANDBOX ?=
+MATHICS3_SANDBOX ?=
ifeq ($(OS),Windows_NT)
- SANDBOX = t
+ MATHICS3_SANDBOX = t
else
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
- SANDBOX = t
+ MATHICS3_SANDBOX = t
endif
endif
@@ -155,7 +155,7 @@ doctest-data: mathics/builtin/*.py mathics/doc/documentation/*.mdoc mathics/doc/
#: Run tests that appear in docstring in the code. Use environment variable "DOCTEST_OPTIONS" for doctest options
doctest:
- MATHICS_CHARACTER_ENCODING="ASCII" SANDBOX=$(SANDBOX) $(PYTHON) mathics/docpipeline.py $(DOCTEST_OPTIONS)
+ MATHICS_CHARACTER_ENCODING="ASCII" MATHICS3_SANDBOX=$(MATHICS3_SANDBOX) $(PYTHON) mathics/docpipeline.py $(DOCTEST_OPTIONS)
#: Run tests that appear in docstring in the code, stopping on the first error.
doctest-x:
diff --git a/mathics/__main__.py b/mathics/__main__.py
index b24825ebd..28ac37327 100755
--- a/mathics/__main__.py
+++ b/mathics/__main__.py
@@ -357,7 +357,10 @@ def interactive_eval_loop(shell, full_form: bool, strict_wl_output: bool):
show_echo(source_code, evaluation)
if len(source_code) and source_code[0] == "!":
- subprocess.run(source_code[1:], shell=True)
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ evaluation.message("Run", "dis")
+ else:
+ subprocess.run(source_code[1:], shell=True)
shell.definitions.increment_line_no(1)
continue
if query is None:
diff --git a/mathics/builtin/system.py b/mathics/builtin/system.py
index 397ebdf56..96d32e9a1 100644
--- a/mathics/builtin/system.py
+++ b/mathics/builtin/system.py
@@ -63,20 +63,31 @@ class Breakpoint(Builtin):
Here is how to use 'mathics.disabled_breakpoint':
- >> SetEnvironment["PYTHONBREAKPOINT" -> "mathics.disabled_breakpoint"];
+ X> SetEnvironment["PYTHONBREAKPOINT" -> "mathics.disabled_breakpoint"];
- >> Breakpoint[]
- = Hit disabled breakpoint.
+ X> Breakpoint[]
+ | Hit disabled breakpoint.
= Breakpoint[]
+ X> Breakpoint[]
+ : Breakpoint::dis: Execution of external commands is disabled.
+ = Null
+
The environment variable 'PYTHONBREAKPOINT' can be changed at runtime to switch \
'breakpoint()' and 'Breakpoint[]' behavior.
"""
summary_text = "invoke Python breakpoint()"
+ messages = {"dis": "Execution of external commands is disabled."}
+
def eval(self, evaluation: Evaluation):
"Breakpoint[]"
+ from mathics import settings
+
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ evaluation.message("Breakpoint", "dis")
+ return SymbolNull
breakpoint()
@@ -88,17 +99,25 @@ class CommandLine(Predefined):
is a list of strings passed on the command line to launch the Mathics3 session.
- >> $CommandLine
+ S> $CommandLine
= {...}
+
+ S> $CommandLine
+ = {}
"""
name = "$CommandLine"
+ messages = {"dis": "Execution of external commands is disabled."}
summary_text = (
"get the command line arguments passed when the current Mathics3 "
"session was launched"
)
def evaluate(self, evaluation: Evaluation) -> Expression:
+ from mathics import settings
+
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ return ListExpression()
return ListExpression(*(String(arg) for arg in sys.argv))
@@ -114,17 +133,21 @@ class Environment(Builtin):
S> Environment["HOME"]
= ...
- See also
- :'GetEnvironment':
- /doc/reference-of-built-in-symbols/global-system-information/getenvironment/ and
- :'SetEnvironment':
- /doc/reference-of-built-in-symbols/global-system-information/setenvironment/.
+ S> Environment["HOME"]
+ : Environment::dis: Execution of external commands is disabled.
+ = $Failed
"""
+ messages = {"dis": "Execution of external commands is disabled."}
summary_text = "list the system environment variables"
def eval(self, var, evaluation: Evaluation):
"Environment[var_String]"
+ from mathics import settings
+
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ evaluation.message("Environment", "dis")
+ return SymbolFailed
env_var = var.get_string_value()
if env_var not in os.environ:
return SymbolFailed
@@ -150,29 +173,26 @@ class GetEnvironment(Builtin):
On POSIX systems, the following gets the users HOME directory:
S> GetEnvironment["HOME"]
- = ...
-
- We can get both the HOME directory and the user name in one go:
- S> GetEnvironment[{"HOME", "USER"}]
- = ...
-
- Arguments however must be strings:
- S> GetEnvironment[HOME]
- : HOME is not ALL or a string or a list of strings.
- = GetEnvironment[HOME]
+ = ...
- See also
- :'Environment':
- /doc/reference-of-built-in-symbols/global-system-information/environment/ and
- :'SetEnvironment':
- /doc/reference-of-built-in-symbols/global-system-information/setenvironment/.
+ S> GetEnvironment["HOME"]
+ : GetEnvironment::dis: Execution of external commands is disabled.
+ = $Failed
"""
- messages = {"name": "`1` is not ALL or a string or a list of strings."}
+ messages = {
+ "name": "`1` is not ALL or a string or a list of strings.",
+ "dis": "Execution of external commands is disabled.",
+ }
summary_text = "retrieve the value of a system environment variable"
def eval(self, var, evaluation: Evaluation):
"GetEnvironment[var___]"
+ from mathics import settings
+
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ evaluation.message("GetEnvironment", "dis")
+ return SymbolFailed
if isinstance(var, String):
env_var = var.value
tup = (
@@ -289,7 +309,7 @@ def evaluate(self, evaluation: Evaluation) -> String:
class MachineName(Predefined):
- """
+ r"""
:WMA link:https://reference.wolfram.com/language/ref/\\$MachineName.html
@@ -300,13 +320,26 @@ class MachineName(Predefined):
S> $MachineName
= ...
+
+ S> $MachineName
+ : $MachineName::dis: Execution of external commands is disabled.
+ = $Failed
"""
name = "$MachineName"
+ messages = {"dis": "Execution of external commands is disabled."}
summary_text = "get the name of computer that Mathics3 is running"
def evaluate(self, evaluation: Evaluation) -> String:
- return String(platform.uname().node)
+ from mathics import settings
+
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ evaluation.message("$MachineName", "dis")
+ return SymbolFailed
+ try:
+ return String(platform.uname().node)
+ except Exception:
+ return String("unknown")
class MathicsVersion(Predefined):
@@ -477,15 +510,24 @@ class ParentProcessID(Predefined):
system under which it is run.
- >> $ParentProcessID
+ S> $ParentProcessID
= ...
+ S> $ParentProcessID
+ : $ParentProcessID::dis: Execution of external commands is disabled.
+ = $Failed
"""
name = "$ParentProcessID"
+ messages = {"dis": "Execution of external commands is disabled."}
summary_text = "get process id of the process that invoked Mathics3"
def evaluate(self, evaluation: Evaluation) -> Integer:
+ from mathics import settings
+
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ evaluation.message("$ParentProcessID", "dis")
+ return SymbolFailed
return Integer(os.getppid())
@@ -499,14 +541,24 @@ class ProcessID(Predefined):
which it is run.
- >> $ProcessID
+ S> $ProcessID
= ...
+
+ S> $ProcessID
+ : $ProcessID::dis: Execution of external commands is disabled.
+ = $Failed
"""
name = "$ProcessID"
+ messages = {"dis": "Execution of external commands is disabled."}
summary_text = "get process id of the Mathics process"
def evaluate(self, evaluation: Evaluation) -> Integer:
+ from mathics import settings
+
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ evaluation.message("$ProcessID", "dis")
+ return SymbolFailed
return Integer(os.getpid())
@@ -571,10 +623,16 @@ class Run(Builtin):
= ...
"""
+ messages = {"dis": "Execution of external commands is disabled."}
summary_text = "run a system command"
def eval(self, command, evaluation: Evaluation):
"Run[command_String]"
+ from mathics import settings
+
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ evaluation.message("Run", "dis")
+ return SymbolFailed
command_str = command.to_python()
return Integer(subprocess.call(command_str, shell=True))
@@ -588,14 +646,22 @@ class ScriptCommandLine(Predefined):
is a list of string arguments when running the kernel is script mode.
- >> $ScriptCommandLine
- = {...}
+ S> $ScriptCommandLine
+ = {}
+
+ S> $ScriptCommandLine
+ = {}
"""
summary_text = "list of command line arguments"
name = "$ScriptCommandLine"
+ messages = {"dis": "Execution of external commands is disabled."}
def evaluate(self, evaluation: Evaluation):
+ from mathics import settings
+
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ return ListExpression()
try:
dash_index = sys.argv.index("--")
except ValueError:
@@ -649,6 +715,10 @@ class SetEnvironment(Builtin):
Set a single environment variable:
S> SetEnvironment["FOO" -> "bar"]
+ S> SetEnvironment["FOO" -> "bar"]
+ : SetEnvironment::dis: Execution of external commands is disabled.
+ = $Failed
+
See that the environment variable has changed:
S> GetEnvironment["FOO"]
= FOO -> bar
@@ -681,11 +751,20 @@ class SetEnvironment(Builtin):
/doc/reference-of-built-in-symbols/global-system-information/getenvironment/.
"""
- messages = {"value": "`1` must be a string or None."}
+ messages = {
+ "value": "`1` must be a string or None.",
+ "dis": "Execution of external commands is disabled.",
+ }
summary_text = "set system environment variable(s)"
def eval(self, rule, evaluation):
"SetEnvironment[rule_Rule]"
+ from mathics import settings
+
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ evaluation.message("SetEnvironment", "dis")
+ return SymbolFailed
+
env_var_name, env_var_value = rule.elements
# WMA does not give an error message if env_var_name is not a String - weird.
if not isinstance(env_var_name, String):
@@ -695,13 +774,19 @@ def eval(self, rule, evaluation):
evaluation.message("SetEnvironment", "value", env_var_value)
return SymbolFailed
- os.environ[env_var_name.value] = (
- None if None is SymbolNone else env_var_value.value
- )
+ if env_var_value is SymbolNone:
+ os.environ.pop(env_var_name.value, None)
+ else:
+ os.environ[env_var_name.value] = env_var_value.value
return SymbolNull
def eval_list(self, rules: Expression, evaluation: Evaluation):
"SetEnvironment[{rules__}]"
+ from mathics import settings
+
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ evaluation.message("SetEnvironment", "dis")
+ return SymbolFailed
# All the rules must be of the form
for rule in rules.elements:
@@ -825,14 +910,24 @@ class UserName(Predefined):
\Mathics session.
- X> $UserName
+ S> $UserName
= ...
+
+ S> $UserName
+ : $UserName::dis: Execution of external commands is disabled.
+ = $Failed
"""
name = "$UserName"
+ messages = {"dis": "Execution of external commands is disabled."}
summary_text = "get login name of the user that invoked the current session"
def evaluate(self, evaluation: Evaluation) -> String:
+ from mathics import settings
+
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ evaluation.message("$UserName", "dis")
+ return SymbolFailed
try:
user = os.getlogin()
except Exception:
@@ -896,14 +991,24 @@ class SystemMemory(Predefined):
Returns the total amount of physical memory.
- >> $SystemMemory
+ S> $SystemMemory
= ...
+
+ S> $SystemMemory
+ : $SystemMemory::dis: Execution of external commands is disabled.
+ = $Failed
"""
name = "$SystemMemory"
summary_text = "get the total amount of physical memory in the system"
+ messages = {"dis": "Execution of external commands is disabled."}
def evaluate(self, evaluation: Evaluation) -> Integer:
+ from mathics import settings
+
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ evaluation.message("$SystemMemory", "dis")
+ return SymbolFailed
totalmem = psutil.virtual_memory().total
return Integer(totalmem)
@@ -916,18 +1021,28 @@ class MemoryAvailable(Builtin):
Returns the amount of the available physical memory.
- >> MemoryAvailable[]
+ S> MemoryAvailable[]
= ...
+ S> MemoryAvailable[]
+ : MemoryAvailable::dis: Execution of external commands is disabled.
+ = $Failed
+
The relationship between \\$SystemMemory, MemoryAvailable, and MemoryInUse:
- >> $SystemMemory > MemoryAvailable[] > MemoryInUse[]
+ S> $SystemMemory > MemoryAvailable[] > MemoryInUse[]
= True
"""
summary_text = "get the available amount of physical memory in the system"
+ messages = {"dis": "Execution of external commands is disabled."}
def eval(self, evaluation: Evaluation) -> Integer:
"""MemoryAvailable[]"""
+ from mathics import settings
+
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ evaluation.message("MemoryAvailable", "dis")
+ return SymbolFailed
totalmem = psutil.virtual_memory().available
return Integer(totalmem)
@@ -943,14 +1058,24 @@ class SystemMemory(Predefined):
This system however doesn't have that installed, so -1 is returned instead.
- >> $SystemMemory
+ S> $SystemMemory
= -1
+
+ S> $SystemMemory
+ : $SystemMemory::dis: Execution of external commands is disabled.
+ = $Failed
"""
summary_text = "the total amount of physical memory in the system"
name = "$SystemMemory"
+ messages = {"dis": "Execution of external commands is disabled."}
def evaluate(self, evaluation: Evaluation) -> Integer:
+ from mathics import settings
+
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ evaluation.message("$SystemMemory", "dis")
+ return SymbolFailed
return IntegerM1
class MemoryAvailable(Builtin):
@@ -959,16 +1084,26 @@ class MemoryAvailable(Builtin):
- 'MemoryAvailable'
-
- Returns the amount of the available physical when Python module "psutil" is installed.
+
- Returns the amount of the available physical memory when Python module "psutil" is installed.
This system however doesn't have that installed, so -1 is returned instead.
- >> MemoryAvailable[]
+ S> MemoryAvailable[]
= -1
+
+ S> MemoryAvailable[]
+ : MemoryAvailable::dis: Execution of external commands is disabled.
+ = $Failed
"""
summary_text = "get the available amount of physical memory in the system"
+ messages = {"dis": "Execution of external commands is disabled."}
def eval(self, evaluation: Evaluation) -> Integer:
"""MemoryAvailable[]"""
+ from mathics import settings
+
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ evaluation.message("MemoryAvailable", "dis")
+ return SymbolFailed
return IntegerM1
diff --git a/mathics/core/convert/op.py b/mathics/core/convert/op.py
index c0fad95b1..8ec8e1e8e 100644
--- a/mathics/core/convert/op.py
+++ b/mathics/core/convert/op.py
@@ -30,8 +30,8 @@
val: operator_to_ascii[key] for key, val in operator_to_unicode.items()
}
-UNICODE_TO_AMSLATEX = OPERATOR_CONVERSION_TABLES["unicode-to-amslatex"]
-UNICODE_TO_LATEX = OPERATOR_CONVERSION_TABLES["unicode-to-latex"]
+UNICODE_TO_AMSLATEX = OPERATOR_CONVERSION_TABLES.get("unicode-to-amslatex", {})
+UNICODE_TO_LATEX = OPERATOR_CONVERSION_TABLES.get("unicode-to-latex", {})
AMSTEX_OPERATORS = {
diff --git a/mathics/core/parser/operators.py b/mathics/core/parser/operators.py
index 7ebc1216c..6418f5187 100644
--- a/mathics/core/parser/operators.py
+++ b/mathics/core/parser/operators.py
@@ -35,7 +35,9 @@
nonassoc_binary_operators = OPERATOR_DATA["non-associative-binary-operators"]
operator_precedences = OPERATOR_DATA["operator-precedences"]
operator_to_amslatex = OPERATOR_DATA["operator-to-amslatex"]
-operator_to_string = OPERATOR_DATA["operator-to-string"]
+operator_to_string = OPERATOR_DATA.get(
+ "operator-to-string", OPERATOR_DATA.get("operator-to_string", {})
+)
postfix_operators = OPERATOR_DATA["postfix-operators"]
prefix_operators = OPERATOR_DATA["prefix-operators"]
right_binary_operators = OPERATOR_DATA["right-binary-operators"]
diff --git a/mathics/doc/doc_entries.py b/mathics/doc/doc_entries.py
index e6dd99474..a16f2acf2 100644
--- a/mathics/doc/doc_entries.py
+++ b/mathics/doc/doc_entries.py
@@ -317,7 +317,7 @@ class DocTest:
the documentation.
* `X>` Shows the example in the docs, but disables testing the example.
* `S>` Shows the example in the docs, but disables testing if environment
- variable SANDBOX is set.
+ variable MATHICS3_SANDBOX is set.
* `=` Compares the result text.
* `:` Compares an (error) message.
`|` Prints output.
@@ -362,8 +362,11 @@ def strip_sentinal(line: str):
self.private = testcase[0] == "#"
# Ignored test cases are NOT executed, but shown as part of the docs
- # Sandboxed test cases are NOT executed if environment SANDBOX is set
- if testcase[0] == "X" or (testcase[0] == "S" and getenv("SANDBOX", False)):
+ # Sandboxed test cases are NOT executed if environment MATHICS3_SANDBOX is set
+ from mathics import settings
+
+ is_sandbox = not settings.ENABLE_SYSTEM_COMMANDS
+ if testcase[0] == "X" or (testcase[0] == "S" and is_sandbox):
self.ignore = True
# substitute '>' again so we get the correct formatting
testcase[0] = ">"
diff --git a/mathics/doc/latex_doc.py b/mathics/doc/latex_doc.py
index 968aa9a11..41408d101 100644
--- a/mathics/doc/latex_doc.py
+++ b/mathics/doc/latex_doc.py
@@ -540,7 +540,7 @@ class LaTeXDocTest(DocTest):
the documentation.
* `X>` Shows the example in the docs, but disables testing the example.
* `S>` Shows the example in the docs, but disables testing if environment
- variable SANDBOX is set.
+ variable MATHICS3_SANDBOX is set.
* `=` Compares the result text.
* `:` Compares an (error) message.
`|` Prints output.
diff --git a/mathics/format/form/util.py b/mathics/format/form/util.py
index 82679f6f0..a3a7a7584 100644
--- a/mathics/format/form/util.py
+++ b/mathics/format/form/util.py
@@ -35,10 +35,10 @@ class _WrongFormattedExpression(Exception):
ARITHMETIC_OPERATOR_STRINGS: Final[FrozenSet[str]] = frozenset(
[
- *operator_to_string["Divide"],
- *operator_to_string["NonCommutativeMultiply"],
- *operator_to_string["Power"],
- *operator_to_string["Times"],
+ *operator_to_string.get("Divide", ["/"]),
+ *operator_to_string.get("NonCommutativeMultiply", ["**"]),
+ *operator_to_string.get("Power", ["^"]),
+ *operator_to_string.get("Times", ["*"]),
" ",
]
)
@@ -109,12 +109,13 @@ def collect_in_pre_post_arguments(
operator_spec = render_function(head, evaluation, **kwargs)
if head is SymbolInfix:
operator_spec = [
- f"{operator_to_string['Infix']}{operator_spec}{operator_to_string['Infix']}"
+ f"{operator_to_string.get('Infix', '')}{operator_spec}"
+ f"{operator_to_string.get('Infix', '')}"
]
elif head is SymbolPrefix:
- operator_spec = f"{operator_spec}{operator_to_string['Prefix']}"
+ operator_spec = f"{operator_spec}{operator_to_string.get('Prefix', '')}"
elif head is SymbolPostfix:
- operator_spec = f"{operator_to_string['Postfix']}{operator_spec}"
+ operator_spec = f"{operator_to_string.get('Postfix', '')}{operator_spec}"
return operands, operator_spec, precedence, group_name
# At least two parameters: get the operator spec.
diff --git a/mathics/interrupt.py b/mathics/interrupt.py
index df70e8039..c47470565 100644
--- a/mathics/interrupt.py
+++ b/mathics/interrupt.py
@@ -41,7 +41,10 @@ def inspect_eval_loop(evaluation: Evaluation):
query, source_code = evaluation.parse_feeder_returning_code(shell)
# show_echo(source_code, evaluation)
if len(source_code) and source_code[0] == "!" and shell is not None:
- subprocess.run(source_code[1:], shell=True)
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ evaluation.message("Run", "dis")
+ else:
+ subprocess.run(source_code[1:], shell=True)
if shell.definitions is not None:
shell.definitions.increment_line_no(1)
continue
@@ -90,7 +93,10 @@ def Mathics3_interrupt_handler(
print_fn("continuing")
break
elif user_input in ("debugger", "d"):
- breakpoint()
+ if not settings.ENABLE_SYSTEM_COMMANDS:
+ print_fn("Execution of external commands is disabled.")
+ else:
+ breakpoint()
elif user_input in ("exit", "quit"):
print_fn("Mathics3 exited because of an interrupt.")
sys.exit(3)
diff --git a/mathics/settings.py b/mathics/settings.py
index 288f1cf09..65190cebc 100644
--- a/mathics/settings.py
+++ b/mathics/settings.py
@@ -4,6 +4,7 @@
Some of the values can be adjusted via Environment Variables.
"""
+
import os
import os.path as osp
import sys
@@ -94,6 +95,23 @@ def get_srcdir():
# users to access local files.
ENABLE_FILES_MODULE = True
+# Leave this True unless you have specific reason for not permitting
+# users to execute system commands.
+# If MATHICS3_SANDBOX environment variable is set, this defaults to False.
+ENABLE_SYSTEM_COMMANDS = (
+ os.environ.get(
+ "MATHICS3_ENABLE_SYSTEM_COMMANDS",
+ str(
+ not (
+ os.environ.get("MATHICS3_SANDBOX")
+ or sys.platform in ("emscripten", "wasi")
+ )
+ ),
+ ).lower()
+ == "true"
+)
+
+
# Rocky: this is probably a hack. LoadModule[] needs to handle
# whatever it is that setting this thing did.
default_pymathics_modules: List[str] = []
diff --git a/test/builtin/drawing/test_image.py b/test/builtin/drawing/test_image.py
index c7e52b74e..b6c94ca91 100644
--- a/test/builtin/drawing/test_image.py
+++ b/test/builtin/drawing/test_image.py
@@ -56,8 +56,8 @@
reason="Test doesn't work in a when scikit-image is not installed",
)
@pytest.mark.skipif(
- os.getenv("SANDBOX", False),
- reason="Test doesn't work in a sandboxed environment with access to local files",
+ os.getenv("MATHICS3_SANDBOX"),
+ reason="Files module is disabled in sandbox mode",
)
@pytest.mark.parametrize(("str_expr, str_expected, msg"), image_tests)
def test_image(str_expr: str, str_expected: str, msg: str, message=""):
diff --git a/test/builtin/test_file_operations.py b/test/builtin/test_file_operations.py
index 5e9db2845..04c44e95d 100644
--- a/test/builtin/test_file_operations.py
+++ b/test/builtin/test_file_operations.py
@@ -100,8 +100,8 @@
],
)
@pytest.mark.skipif(
- os.getenv("SANDBOX", False),
- reason="Test doesn't work in a sandboxed environment with access to local files",
+ os.getenv("MATHICS3_SANDBOX"),
+ reason="Files module is disabled in sandbox mode",
)
def test_private_doctests_file_properties(str_expr, msgs, str_expected, fail_msg):
"""file_opertions.file_properties"""
diff --git a/test/builtin/test_system.py b/test/builtin/test_system.py
index c48eefc5f..4ea6cb7d0 100644
--- a/test/builtin/test_system.py
+++ b/test/builtin/test_system.py
@@ -4,17 +4,36 @@
"""
+import os
from test.helper import check_evaluation
import pytest
+from mathics import settings
+
@pytest.mark.parametrize(
("str_expr", "str_expected", "assert_tag_message"),
[
('MemberQ[$Packages, "System`"]', "True", "$Packages"),
- ("Head[$ParentProcessID] == Integer", "True", "$ParentProcessID"),
- ("Head[$ProcessID] == Integer", "True", "$ProcessID"),
+ pytest.param(
+ "Head[$ParentProcessID] == Integer",
+ "True",
+ "$ParentProcessID",
+ marks=pytest.mark.skipif(
+ not settings.ENABLE_SYSTEM_COMMANDS,
+ reason="In sandbox mode, $ParentProcessID returns $Failed",
+ ),
+ ),
+ pytest.param(
+ "Head[$ProcessID] == Integer",
+ "True",
+ "$ProcessID",
+ marks=pytest.mark.skipif(
+ not settings.ENABLE_SYSTEM_COMMANDS,
+ reason="In sandbox mode, $ProcessID returns $Failed",
+ ),
+ ),
("Head[$SessionID] == Integer", "True", "$SessionID"),
("Head[$SystemWordLength] == Integer", "True", "$SystemWordLength"),
],
diff --git a/test/doc/test_doctests.py b/test/doc/test_doctests.py
index d721001e1..cd1ec562f 100644
--- a/test/doc/test_doctests.py
+++ b/test/doc/test_doctests.py
@@ -2,6 +2,8 @@
Pytests for the documentation system. Basic functions and classes.
"""
+from unittest.mock import patch
+
from mathics.core.evaluation import Message, Print
from mathics.core.load_builtin import import_and_load_builtins
from mathics.doc.common_doc import MathicsMainDocumentation
@@ -92,10 +94,11 @@ def test_create_doctest():
},
},
]
- for index, test_case in enumerate(test_cases):
- doctest = DocTest(1, test_case["test"], key)
- for property_key, value in test_case["properties"].items():
- if property_key == "result" and value is None:
- assert getattr(doctest, property_key) == ""
- else:
- assert getattr(doctest, property_key) == value
+ with patch("mathics.settings.ENABLE_SYSTEM_COMMANDS", True):
+ for index, test_case in enumerate(test_cases):
+ doctest = DocTest(1, test_case["test"], key)
+ for property_key, value in test_case["properties"].items():
+ if property_key == "result" and value is None:
+ assert getattr(doctest, property_key) == ""
+ else:
+ assert getattr(doctest, property_key) == value
diff --git a/test/test_evaluation.py b/test/test_evaluation.py
index d21b7ac07..b4f3503f6 100644
--- a/test/test_evaluation.py
+++ b/test/test_evaluation.py
@@ -3,6 +3,8 @@
import pytest
+from mathics import settings
+
from .helper import check_evaluation, evaluate, session
@@ -25,15 +27,50 @@
(r"Sum[2^(-i), {i, 1, \[Infinity]}]", "1"),
# Global System Information
(r"Abs[$ByteOrdering]", "1"),
- (r"Head[$CommandLine]", "List"),
+ pytest.param(
+ r"Head[$CommandLine]",
+ "List",
+ marks=pytest.mark.skipif(
+ not settings.ENABLE_SYSTEM_COMMANDS,
+ reason="In sandbox mode, $CommandLine returns {}",
+ ),
+ ),
(r"Head[$Machine]", "String"),
- (r"Head[$MachineName]", "String"),
+ pytest.param(
+ r"Head[$MachineName]",
+ "String",
+ marks=pytest.mark.skipif(
+ not settings.ENABLE_SYSTEM_COMMANDS,
+ reason="In sandbox mode, $MachineName returns $Failed",
+ ),
+ ),
(r"""Length[Names["System`*"]] > 1024""", "True"),
(r"Length[$Packages] >= 5", "True"),
- (r"Head[$ParentProcessID]", "Integer"),
- (r"Head[$ProcessID]", "Integer"),
+ pytest.param(
+ r"Head[$ParentProcessID]",
+ "Integer",
+ marks=pytest.mark.skipif(
+ not settings.ENABLE_SYSTEM_COMMANDS,
+ reason="In sandbox mode, $ParentProcessID returns $Failed",
+ ),
+ ),
+ pytest.param(
+ r"Head[$ProcessID]",
+ "Integer",
+ marks=pytest.mark.skipif(
+ not settings.ENABLE_SYSTEM_COMMANDS,
+ reason="In sandbox mode, $ProcessID returns $Failed",
+ ),
+ ),
(r"Head[$ProcessorType]", "String"),
- (r"Head[$ScriptCommandLine]", "List"),
+ pytest.param(
+ r"Head[$ScriptCommandLine]",
+ "List",
+ marks=pytest.mark.skipif(
+ not settings.ENABLE_SYSTEM_COMMANDS,
+ reason="In sandbox mode, $ScriptCommandLine returns {}",
+ ),
+ ),
(r"Head[$SystemID]", "String"),
(r"Head[$SystemWordLength]", "Integer"),
# This doesn't work if not logged or in some OS's