diff --git a/debug_gym/gym/workspace.py b/debug_gym/gym/workspace.py index dde503bd2..4abc1e702 100644 --- a/debug_gym/gym/workspace.py +++ b/debug_gym/gym/workspace.py @@ -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 diff --git a/debug_gym/logger.py b/debug_gym/logger.py index 9a44a550e..65dd9dc55 100644 --- a/debug_gym/logger.py +++ b/debug_gym/logger.py @@ -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 diff --git a/tests/gym/test_workspace.py b/tests/gym/test_workspace.py index fcf551adf..385728c23 100644 --- a/tests/gym/test_workspace.py +++ b/tests/gym/test_workspace.py @@ -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