Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions tests/test_entry_point_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from __future__ import annotations

import os
import sys
from pathlib import Path
from unittest.mock import patch

Expand All @@ -11,6 +13,8 @@
validate_entry_point_resolved,
)

import pytest


# ── Unit tests: validate_entry_point (syntax) ─────────────────────────

Expand Down Expand Up @@ -75,6 +79,10 @@ def test_valid_path_passes(self, tmp_path: Path) -> None:
(tmp_path / "main.py").write_text("pass")
assert validate_entry_point_resolved(tmp_path, "main.py") is None

@pytest.mark.skipif(
sys.platform == "win32" and not os.environ.get("CI"),
reason="Creating symlinks on Windows requires admin privileges or Developer Mode",
)
def test_symlink_escape_rejected(self, tmp_path: Path) -> None:
"""A symlink pointing outside staging must be caught."""
escape_target = tmp_path / "outside" / "secret.py"
Expand Down
4 changes: 2 additions & 2 deletions tests/test_figure_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,8 +415,8 @@ def test_render_simple_script(self, tmp_path):
# Use a script that creates a valid PNG without matplotlib
# (creates a minimal 1x1 PNG file directly)
script = textwrap.dedent("""\
import struct, zlib
output_path = "{output_dir}/fig_test.png"
import struct, zlib, os
output_path = os.path.join(r"{output_dir}", "fig_test.png")
# Minimal valid PNG: 1x1 white pixel
def write_png(path):
sig = b'\\x89PNG\\r\\n\\x1a\\n'
Expand Down
6 changes: 3 additions & 3 deletions tests/test_rc_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ def test_resolve_config_returns_none_when_missing(
def test_resolve_config_explicit_path_no_search() -> None:
result = resolve_config_path("/some/explicit/path.yaml")
assert result is not None
assert str(result) == "/some/explicit/path.yaml"
assert result == Path("/some/explicit/path.yaml")


# --- cmd_init tests ---
Expand Down Expand Up @@ -289,7 +289,7 @@ def test_cmd_init_creates_config(
assert code == 0
created = tmp_path / "config.arc.yaml"
assert created.exists()
content = created.read_text()
content = created.read_text(encoding="utf-8")
assert 'provider: "openai"' in content
assert "Created config.arc.yaml" in capsys.readouterr().out

Expand Down Expand Up @@ -317,7 +317,7 @@ def test_cmd_init_force_overwrites(
args = argparse.Namespace(force=True)
code = rc_cli.cmd_init(args)
assert code == 0
assert (tmp_path / "config.arc.yaml").read_text() != "old\n"
assert (tmp_path / "config.arc.yaml").read_text(encoding="utf-8") != "old\n"


def test_cmd_run_missing_config_shows_init_hint(
Expand Down
16 changes: 10 additions & 6 deletions tests/test_rc_docker_sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import subprocess
import sys
import threading
from pathlib import Path
from unittest.mock import MagicMock, patch
Expand Down Expand Up @@ -51,8 +52,9 @@ def test_build_run_command_network_none(tmp_path: Path):
assert "--memory=8192m" in cmd
assert "--shm-size=2048m" in cmd
assert cmd[-1] == "main.py"
# Should contain --user (non-root)
assert "--user" in cmd
# Should contain --user on POSIX (non-root); skipped on Windows
if sys.platform != "win32":
assert "--user" in cmd


def test_build_run_command_setup_only(tmp_path: Path):
Expand All @@ -74,8 +76,9 @@ def test_build_run_command_setup_only(tmp_path: Path):
# Should NOT have --network none (needs network for setup)
network_indices = [i for i, x in enumerate(cmd) if x == "--network"]
assert len(network_indices) == 0
# Should have --user (runs as host user so experiment can write results.json)
assert "--user" in cmd
# Should have --user on POSIX (runs as host user so experiment can write results.json)
if sys.platform != "win32":
assert "--user" in cmd


def test_build_run_command_full_network(tmp_path: Path):
Expand All @@ -90,8 +93,9 @@ def test_build_run_command_full_network(tmp_path: Path):
# No --network none
network_indices = [i for i, x in enumerate(cmd) if x == "--network"]
assert len(network_indices) == 0
# Should have --user (non-root)
assert "--user" in cmd
# Should have --user on POSIX (non-root)
if sys.platform != "win32":
assert "--user" in cmd


def test_build_run_command_no_gpu(tmp_path: Path):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_rc_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3355,7 +3355,7 @@ def test_writes_json_to_stage_dir(self, tmp_path: Path) -> None:
rc_executor._validate_draft_quality(draft, stage_dir=tmp_path)
assert (tmp_path / "draft_quality.json").exists()
data = json.loads(
(tmp_path / "draft_quality.json").read_text()
(tmp_path / "draft_quality.json").read_text(encoding="utf-8")
)
assert "section_analysis" in data
assert "overall_warnings" in data
Expand Down
2 changes: 1 addition & 1 deletion tests/test_rc_sentinel.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def test_sentinel_script_is_executable(self) -> None:

def test_sentinel_script_has_shebang(self) -> None:
script = Path(__file__).parent.parent / "sentinel.sh"
first_line = script.read_text().splitlines()[0]
first_line = script.read_text(encoding="utf-8").splitlines()[0]
assert first_line.startswith("#!/")

def test_sentinel_prints_usage_on_no_args(self) -> None:
Expand Down
6 changes: 3 additions & 3 deletions tests/test_rc_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ def test_rcconfig_export_from_dict(self) -> None:
import yaml
from pathlib import Path

data = yaml.safe_load(Path("config.researchclaw.example.yaml").read_text())
data = yaml.safe_load(Path("config.researchclaw.example.yaml").read_text(encoding="utf-8"))
data["export"] = {
"target_conference": "icml_2025",
"authors": "Test Author",
Expand All @@ -595,7 +595,7 @@ def test_stage_23_valid(self) -> None:
import yaml
from pathlib import Path

data = yaml.safe_load(Path("config.researchclaw.example.yaml").read_text())
data = yaml.safe_load(Path("config.researchclaw.example.yaml").read_text(encoding="utf-8"))
data.setdefault("security", {})["hitl_required_stages"] = [1, 22, 23]
result = validate_config(data, check_paths=False)
assert result.ok, f"Errors: {result.errors}"
Expand All @@ -622,7 +622,7 @@ def test_stage_24_invalid(self) -> None:
import yaml
from pathlib import Path

data = yaml.safe_load(Path("config.researchclaw.example.yaml").read_text())
data = yaml.safe_load(Path("config.researchclaw.example.yaml").read_text(encoding="utf-8"))
data.setdefault("security", {})["hitl_required_stages"] = [24]
result = validate_config(data, check_paths=False)
assert not result.ok
Expand Down
4 changes: 2 additions & 2 deletions tests/test_universal_codegen_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ def test_domain_to_docker_mapping(self):
if not profiles_path.exists():
pytest.skip("docker_profiles.yaml not found")

with profiles_path.open() as f:
with profiles_path.open(encoding="utf-8") as f:
docker_config = yaml.safe_load(f)

domain_map = docker_config.get("domain_map", {})
Expand All @@ -384,7 +384,7 @@ def test_all_loaded_domains_have_docker_mapping(self):
if not profiles_path.exists():
pytest.skip("docker_profiles.yaml not found")

with profiles_path.open() as f:
with profiles_path.open(encoding="utf-8") as f:
docker_config = yaml.safe_load(f)

domain_map = docker_config.get("domain_map", {})
Expand Down