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
12 changes: 11 additions & 1 deletion debug_gym/gym/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,25 @@ def write_file(self, filepath: str, content: str):
"""Writes `content` to `filepath` exactly as-is, preserving any trailing newlines."""
abs_filepath = self.resolve_path(filepath)

# We will split content in chunks of 32kB to avoid hitting command length limits.
chunk_size = 32 * 1024 # 32kB
first_chunk = content[:chunk_size]
rest = content[chunk_size:]

# In the following command we:
# - use a single-quoted heredoc (cat <<'nDEBUGGYM_EOF' ... nDEBUGGYM_EOF) so the heredoc body is taken literally (no shell expansion)
# - append a sentinel character DEBUGGYM_DEL inside the heredoc so we can detect/restore trailing newlines later
# - capture the heredoc output into shell variable CONTENT since command substitution strips trailing newlines
# - "${CONTENT%DEBUGGYM_DEL}" removes the trailing sentinel DEBUGGYM_DEL (restoring the original trailing-newline state)
# - echo -n writes the result without adding an extra newline
cmd = f"CONTENT=$(cat <<'DEBUGGYM_EOF'\n{content}DEBUGGYM_DEL\nDEBUGGYM_EOF\n); echo -n \"${{CONTENT%DEBUGGYM_DEL}}\" > {abs_filepath}"
cmd = f"CONTENT=$(cat <<'DEBUGGYM_EOF'\n{first_chunk}DEBUGGYM_DEL\nDEBUGGYM_EOF\n); echo -n \"${{CONTENT%DEBUGGYM_DEL}}\" > {abs_filepath}"
self.terminal.run(cmd, raises=True)

for i in range(0, len(rest), chunk_size):
chunk = rest[i : i + chunk_size]
cmd = f"CONTENT=$(cat <<'DEBUGGYM_EOF'\n{chunk}DEBUGGYM_DEL\nDEBUGGYM_EOF\n); echo -n \"${{CONTENT%DEBUGGYM_DEL}}\" >> {abs_filepath}"
self.terminal.run(cmd, raises=True)

def directory_tree(self, root: str | Path = None, max_depth: int = 1):
root = self.resolve_path(root or self.working_dir, raises=True)
# Use the terminal to run a bash command to list files
Expand Down
7 changes: 6 additions & 1 deletion debug_gym/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,12 @@ def __init__(
):
super().__init__(name)
# If var env "DEBUG_GYM_DEBUG" is set, turn on debug mode
if os.environ.get("DEBUG_GYM_DEBUG"):
if os.environ.get("DEBUG_GYM_DEBUG", "").strip().lower() in (
"1",
"true",
"yes",
"on",
):
level = logging.DEBUG

# Prevent the log messages from being propagated to the root logger
Expand Down
28 changes: 24 additions & 4 deletions tests/gym/test_workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,23 +292,43 @@ def test_read_file_raises_for_nonexistent_file(workspace):
workspace.read_file("/test.txt")


def test_write_file(workspace):
def test_write_file_basic(workspace):
file_path = workspace.working_dir / "test.txt"
file_content = "Hello, DebugGym!\n\n\n"
workspace.write_file("test.txt", file_content)
assert file_path.read_text() == file_content

# Test with single line, no newline at the end.

def test_write_file_single_line_no_newline(workspace):
file_path = workspace.working_dir / "test.txt"
file_content_single_line = "Hello, DebugGym!"
workspace.write_file("test.txt", file_content_single_line)
assert file_path.read_text() == file_content_single_line

# Should still work if the content ends with the delimiter.

def test_write_file_with_delimiter(workspace):
file_path = workspace.working_dir / "test.txt"
file_content_single_line = "Hello, DebugGym!nDEBUGGYM_DEL"
workspace.write_file("test.txt", file_content_single_line)
assert file_path.read_text() == file_content_single_line

# Test with newlines

def test_write_file_with_newlines(workspace):
file_path = workspace.working_dir / "test.txt"
file_content_with_newlines = "Hello, DebugGym!\nThis is a test.\n"
workspace.write_file("test.txt", file_content_with_newlines)
assert file_path.read_text() == file_content_with_newlines


def test_write_file_empty_content(workspace):
file_path = workspace.working_dir / "test.txt"
file_content_empty = ""
workspace.write_file("test.txt", file_content_empty)
assert file_path.read_text() == file_content_empty


def test_write_file_exceeding_max_command_length(workspace):
file_path = workspace.working_dir / "test.txt"
file_content_exceeding_max_command_length = "A" * (2 * 1024**2) # 2MB of 'A's
workspace.write_file("test.txt", file_content_exceeding_max_command_length)
assert file_path.read_text() == file_content_exceeding_max_command_length