Skip to content

Commit 975560b

Browse files
committed
losedows
1 parent 4b6d00f commit 975560b

2 files changed

Lines changed: 121 additions & 3 deletions

File tree

‎src/bittty/pty/windows.py‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,13 @@ def read(self, size: int = -1) -> bytes:
3131

3232
def write(self, data: bytes) -> int:
3333
"""Write bytes data."""
34-
# winpty.write() actually expects bytes according to stub but error suggests strings
35-
return self.pty.write(data)
34+
# winpty.write() expects strings, not bytes
35+
try:
36+
return self.pty.write(data)
37+
except TypeError:
38+
# Fallback: convert bytes to string
39+
text = data.decode("utf-8", errors="replace")
40+
return self.pty.write(text)
3641

3742
def close(self) -> None:
3843
"""Close the PTY."""
@@ -113,7 +118,16 @@ def spawn_process(self, command: str, env: Optional[Dict[str, str]] = ENV) -> su
113118
if self.closed:
114119
raise OSError("PTY is closed")
115120

116-
self.pty.spawn(command, env=env)
121+
# Convert env dict to winpty format: null-separated "KEY=VALUE" string
122+
if env:
123+
env_strs = []
124+
for key, value in env.items():
125+
env_strs.append(f"{key}={value}")
126+
env_string = "\0".join(env_strs) + "\0"
127+
else:
128+
env_string = ""
129+
130+
self.pty.spawn(command, env=env_string)
117131

118132
# Return a process-like object that provides compatibility with subprocess.Popen
119133
process = WinptyProcessWrapper(self.pty)

‎tests/unit/test_windows_env.py‎

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""Test Windows PTY environment string formatting."""
2+
3+
import pytest
4+
from unittest.mock import Mock, patch
5+
from bittty.pty.windows import WindowsPTY
6+
7+
8+
@pytest.mark.windows
9+
def test_env_dict_to_string_conversion():
10+
"""Test that env dict is converted to correct null-separated string format."""
11+
12+
# Mock winpty to avoid actual PTY creation
13+
mock_winpty = Mock()
14+
mock_pty = Mock()
15+
mock_winpty.PTY.return_value = mock_pty
16+
17+
with patch("bittty.pty.windows.winpty", mock_winpty):
18+
pty = WindowsPTY(24, 80)
19+
20+
# Test with env dict
21+
test_env = {"PATH": "/bin", "USER": "test", "HOME": "/home/test"}
22+
23+
# Mock the spawn method to capture the env string
24+
spawn_calls = []
25+
26+
def capture_spawn(command, env):
27+
spawn_calls.append((command, env))
28+
29+
mock_pty.spawn = capture_spawn
30+
31+
pty.spawn_process("cmd.exe", env=test_env)
32+
33+
# Check the call was made
34+
assert len(spawn_calls) == 1
35+
command, env_string = spawn_calls[0]
36+
37+
assert command == "cmd.exe"
38+
39+
# Parse the env string back to verify format
40+
env_parts = env_string.rstrip("\0").split("\0")
41+
reconstructed_env = {}
42+
for part in env_parts:
43+
key, value = part.split("=", 1)
44+
reconstructed_env[key] = value
45+
46+
assert reconstructed_env == test_env
47+
48+
49+
@pytest.mark.windows
50+
def test_empty_env_string():
51+
"""Test that empty/None env results in empty string."""
52+
53+
mock_winpty = Mock()
54+
mock_pty = Mock()
55+
mock_winpty.PTY.return_value = mock_pty
56+
57+
with patch("bittty.pty.windows.winpty", mock_winpty):
58+
pty = WindowsPTY(24, 80)
59+
60+
spawn_calls = []
61+
62+
def capture_spawn(command, env):
63+
spawn_calls.append((command, env))
64+
65+
mock_pty.spawn = capture_spawn
66+
67+
# Test with None env
68+
pty.spawn_process("cmd.exe", env=None)
69+
70+
assert len(spawn_calls) == 1
71+
command, env_string = spawn_calls[0]
72+
assert command == "cmd.exe"
73+
assert env_string == ""
74+
75+
76+
@pytest.mark.windows
77+
def test_env_with_special_chars():
78+
"""Test env vars with special characters are handled correctly."""
79+
80+
mock_winpty = Mock()
81+
mock_pty = Mock()
82+
mock_winpty.PTY.return_value = mock_pty
83+
84+
with patch("bittty.pty.windows.winpty", mock_winpty):
85+
pty = WindowsPTY(24, 80)
86+
87+
spawn_calls = []
88+
89+
def capture_spawn(command, env):
90+
spawn_calls.append((command, env))
91+
92+
mock_pty.spawn = capture_spawn
93+
94+
# Test with special chars
95+
test_env = {"PATH": "C:\\Program Files;C:\\Windows", "TEMP": "C:\\Temp\\with spaces"}
96+
97+
pty.spawn_process("cmd.exe", env=test_env)
98+
99+
command, env_string = spawn_calls[0]
100+
101+
# Verify the string contains the expected format
102+
assert "PATH=C:\\Program Files;C:\\Windows" in env_string
103+
assert "TEMP=C:\\Temp\\with spaces" in env_string
104+
assert env_string.endswith("\0")

0 commit comments

Comments
 (0)