Spaces:
Sleeping
Sleeping
Download tests/test_executor.py from emrekuruu/spreadsheetbench-openenv: direct link, hf CLI and curl.
- Browser
- Download file 5.93 kB
-
https://huggingface.co/spaces/emrekuruu/spreadsheetbench-openenv/resolve/main/tests/test_executor.py
- Command line
-
hf download hf://spaces/emrekuruu/spreadsheetbench-openenv/tests/test_executor.py
-
curl -L -o test_executor.py https://huggingface.co/spaces/emrekuruu/spreadsheetbench-openenv/resolve/main/tests/test_executor.py
5.93 kB
| """Bounded subprocess tests for generated SpreadsheetBench scripts.""" | |
| from __future__ import annotations | |
| import os | |
| from pathlib import Path | |
| import subprocess | |
| import time | |
| import openpyxl | |
| from spreadsheetbench_env.executor import ExecutorLimits, ScriptExecutor | |
| def _input_workbook(path: Path) -> None: | |
| workbook = openpyxl.Workbook() | |
| workbook.active["A1"] = 4 | |
| workbook.save(path) | |
| workbook.close() | |
| def test_executor_injects_private_paths_and_scrubs_parent_credentials( | |
| tmp_path: Path, | |
| monkeypatch, | |
| ) -> None: | |
| source = tmp_path / "source.xlsx" | |
| _input_workbook(source) | |
| monkeypatch.setenv("SPREADSHEETBENCH_TEST_SECRET", "must-not-leak") | |
| executor = ScriptExecutor(tmp_path / "session") | |
| code = """ | |
| import os | |
| import openpyxl | |
| INPUT_PATH = '/tmp/attacker-input.xlsx' | |
| OUTPUT_PATH = '/tmp/attacker-output.xlsx' | |
| wb = openpyxl.load_workbook(INPUT_PATH) | |
| wb.active['A2'] = os.environ.get('SPREADSHEETBENCH_TEST_SECRET', 'scrubbed') | |
| wb.active['A3'] = os.environ.get('OPENBLAS_NUM_THREADS') | |
| wb.save(OUTPUT_PATH) | |
| """ | |
| result = executor.run(code, source, case_name="case-1") | |
| assert result.ok is True | |
| assert result.output_path is not None | |
| assert result.output_path.parent == executor.root / "case-1" | |
| output = openpyxl.load_workbook(result.output_path, data_only=True) | |
| assert output.active["A1"].value == 4 | |
| assert output.active["A2"].value == "scrubbed" | |
| assert output.active["A3"].value == "1" | |
| output.close() | |
| assert not Path("/tmp/attacker-output.xlsx").exists() | |
| executor.close() | |
| assert not executor.root.exists() | |
| def test_executor_uses_a_distinct_directory_for_each_case(tmp_path: Path) -> None: | |
| source = tmp_path / "source.xlsx" | |
| _input_workbook(source) | |
| executor = ScriptExecutor(tmp_path / "session") | |
| code = "import shutil\nshutil.copyfile(INPUT_PATH, OUTPUT_PATH)\n" | |
| first = executor.run(code, source, case_name="case-1") | |
| second = executor.run(code, source, case_name="case-2") | |
| assert first.ok and second.ok | |
| assert first.output_path != second.output_path | |
| assert first.output_path is not None and second.output_path is not None | |
| assert first.output_path.parent != second.output_path.parent | |
| executor.close() | |
| def test_executor_does_not_use_preexec_fn_in_threaded_server( | |
| tmp_path: Path, | |
| monkeypatch, | |
| ) -> None: | |
| source = tmp_path / "source.xlsx" | |
| _input_workbook(source) | |
| executor = ScriptExecutor(tmp_path / "session") | |
| real_popen = subprocess.Popen | |
| received_preexec: list[object] = [] | |
| def recording_popen(*args, **kwargs): | |
| received_preexec.append(kwargs.get("preexec_fn")) | |
| return real_popen(*args, **kwargs) | |
| monkeypatch.setattr(subprocess, "Popen", recording_popen) | |
| result = executor.run( | |
| "import shutil\nshutil.copyfile(INPUT_PATH, OUTPUT_PATH)\n", | |
| source, | |
| case_name="no-preexec", | |
| ) | |
| assert result.ok is True | |
| assert received_preexec == [None] | |
| executor.close() | |
| def test_timeout_kills_the_generated_process_group(tmp_path: Path) -> None: | |
| source = tmp_path / "source.xlsx" | |
| _input_workbook(source) | |
| executor = ScriptExecutor( | |
| tmp_path / "session", | |
| limits=ExecutorLimits(timeout_s=0.5, cpu_seconds=5), | |
| ) | |
| code = """ | |
| import subprocess | |
| import sys | |
| import time | |
| child = subprocess.Popen([sys.executable, '-c', 'import time; time.sleep(60)']) | |
| print(child.pid, flush=True) | |
| time.sleep(60) | |
| """ | |
| result = executor.run(code, source, case_name="timeout") | |
| assert result.ok is False | |
| assert result.timed_out is True | |
| assert "timeout" in result.error.lower() | |
| child_pid = int(result.stdout.strip().splitlines()[0]) | |
| deadline = time.monotonic() + 3 | |
| while time.monotonic() < deadline: | |
| try: | |
| os.kill(child_pid, 0) | |
| except ProcessLookupError: | |
| break | |
| time.sleep(0.05) | |
| else: | |
| raise AssertionError(f"generated child process {child_pid} survived timeout") | |
| executor.close() | |
| def test_timeout_does_not_wait_for_detached_child_output_pipes(tmp_path: Path) -> None: | |
| source = tmp_path / "source.xlsx" | |
| _input_workbook(source) | |
| executor = ScriptExecutor( | |
| tmp_path / "session", | |
| limits=ExecutorLimits(timeout_s=0.2, cpu_seconds=5), | |
| ) | |
| code = """ | |
| import subprocess | |
| import sys | |
| import time | |
| child = subprocess.Popen( | |
| [sys.executable, '-c', 'import time; time.sleep(10)'], | |
| start_new_session=True, | |
| ) | |
| print(child.pid, flush=True) | |
| time.sleep(10) | |
| """ | |
| started = time.monotonic() | |
| result = executor.run(code, source, case_name="detached-child") | |
| elapsed = time.monotonic() - started | |
| assert result.ok is False | |
| assert result.timed_out is True | |
| assert elapsed < 2.0 | |
| child_pid = int(result.stdout.strip().splitlines()[0]) | |
| try: | |
| os.kill(child_pid, 0) | |
| except ProcessLookupError: | |
| pass | |
| else: | |
| os.kill(child_pid, 9) | |
| executor.close() | |
| def test_executor_rejects_unsafe_case_names(tmp_path: Path) -> None: | |
| source = tmp_path / "source.xlsx" | |
| _input_workbook(source) | |
| executor = ScriptExecutor(tmp_path / "session") | |
| try: | |
| executor.run("", source, case_name="../escape") | |
| except ValueError as error: | |
| assert "case name" in str(error) | |
| else: | |
| raise AssertionError("unsafe case name was accepted") | |
| finally: | |
| executor.close() | |
| def test_concurrent_executors_lease_distinct_runner_uids_and_release_them( | |
| tmp_path: Path, | |
| ) -> None: | |
| limits = ExecutorLimits(run_uid_base=20_000, run_gid=65_534) | |
| first = ScriptExecutor(tmp_path / "first", limits=limits) | |
| second = ScriptExecutor(tmp_path / "second", limits=limits) | |
| assert first.leased_uid == 20_000 | |
| assert second.leased_uid == 20_001 | |
| first.close() | |
| replacement = ScriptExecutor(tmp_path / "replacement", limits=limits) | |
| assert replacement.leased_uid == 20_000 | |
| second.close() | |
| replacement.close() | |