Skip to content

Commit fe8a18e

Browse files
committed
fix test
Signed-off-by: kerthcet <kerthcet@gmail.com>
1 parent 0f43be9 commit fe8a18e

2 files changed

Lines changed: 146 additions & 9 deletions

File tree

examples/programmatic_session.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Example: Programmatic Session Control
4+
5+
Demonstrates how to use non-interactive sessions for:
6+
- Multi-step command sequences
7+
- Inspecting session output programmatically
8+
- Handling session state and errors
9+
- Building automation scripts
10+
"""
11+
12+
import sys
13+
import time
14+
from sandd import Server
15+
16+
17+
def main():
18+
# Start server
19+
server = Server("0.0.0.0", 8765)
20+
print(f"Server listening on {server.address}")
21+
22+
# Wait for daemon
23+
daemon_id = "daemon-1"
24+
print(f"\nWaiting for daemon '{daemon_id}'...")
25+
if not server.wait_for_daemon(daemon_id, timeout=30):
26+
print(f"Daemon '{daemon_id}' did not connect")
27+
sys.exit(1)
28+
29+
print(f"Daemon '{daemon_id}' connected!\n")
30+
31+
# Create a non-interactive session
32+
print("=== Creating Session ===")
33+
session = server.new_session(daemon_id, rows=24, cols=80)
34+
print("Session created\n")
35+
36+
# Example 1: Execute command and capture output
37+
print("=== Example 1: Basic Command ===")
38+
session.write(b"echo 'Hello from session'\n")
39+
time.sleep(0.2)
40+
output = session.read(timeout=1.0)
41+
if output:
42+
print(f"Output: {output.decode()}")
43+
44+
# Example 2: Multi-step workflow
45+
print("\n=== Example 2: Multi-Step Workflow ===")
46+
steps = [
47+
("mkdir -p /tmp/test", "Creating directory"),
48+
("cd /tmp/test", "Changing directory"),
49+
("pwd", "Verifying location"),
50+
("echo 'test' > file.txt", "Creating file"),
51+
("cat file.txt", "Reading file"),
52+
]
53+
54+
for cmd, description in steps:
55+
print(f"{description}: {cmd}")
56+
session.write(f"{cmd}\n".encode())
57+
time.sleep(0.1)
58+
output = session.read(timeout=1.0)
59+
if output:
60+
result = output.decode().strip()
61+
if result:
62+
print(f" → {result}")
63+
64+
# Example 3: Error handling
65+
print("\n=== Example 3: Error Handling ===")
66+
session.write(b"exit 42\n") # Exit with non-zero code
67+
time.sleep(0.2)
68+
69+
# Try to write after exit - should fail gracefully
70+
try:
71+
session.write(b"echo 'after exit'\n")
72+
output = session.read(timeout=1.0)
73+
if output:
74+
print(f"Output: {output.decode()}")
75+
except Exception as e:
76+
print(f"Session closed (expected): {e}")
77+
78+
# Example 4: Create new session for long-running task
79+
print("\n=== Example 4: Long-Running Task ===")
80+
session2 = server.new_session(daemon_id)
81+
session2.write(b"for i in 1 2 3; do echo \"Step $i\"; sleep 1; done\n")
82+
83+
# Stream output as it arrives
84+
start = time.time()
85+
while time.time() - start < 5:
86+
output = session2.read(timeout=0.5)
87+
if output:
88+
print(output.decode(), end='', flush=True)
89+
else:
90+
break
91+
92+
session2.close()
93+
print("\n\nSession closed")
94+
95+
96+
if __name__ == "__main__":
97+
main()

python/tests/test_e2e.py

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,42 @@ def run_cmd(daemon_id):
107107
assert all(r.success for r in results)
108108
assert all("Response from" in r.stdout for r in results)
109109

110+
def test_concurrent_execution_same_daemon(self, server):
111+
"""Execute multiple commands concurrently on the same daemon"""
112+
import concurrent.futures
113+
import time
114+
115+
daemon_id = "daemon-debian-1"
116+
117+
def run_sleep(n):
118+
start = time.time()
119+
result = server.exec(daemon_id, f"sleep {n} && echo 'slept {n}s'", timeout=10)
120+
duration = time.time() - start
121+
return result, duration
122+
123+
def run_fast():
124+
start = time.time()
125+
result = server.exec(daemon_id, "echo 'fast command'", timeout=5)
126+
duration = time.time() - start
127+
return result, duration
128+
129+
# Start slow command (3s) and fast command concurrently
130+
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
131+
slow_future = executor.submit(run_sleep, 3)
132+
fast_future = executor.submit(run_fast)
133+
134+
# Fast command should complete quickly, not wait for slow one
135+
fast_result, fast_duration = fast_future.result()
136+
assert fast_result.success
137+
assert "fast command" in fast_result.stdout
138+
assert fast_duration < 1.0 # Should finish in <1s, not wait for 3s sleep
139+
140+
# Slow command completes independently
141+
slow_result, slow_duration = slow_future.result()
142+
assert slow_result.success
143+
assert "slept 3s" in slow_result.stdout
144+
assert 2.5 < slow_duration < 4.0
145+
110146

111147
class TestE2ELabels:
112148
"""Test label-based filtering in E2E"""
@@ -289,18 +325,22 @@ def test_session_multiline_commands(self, server):
289325

290326
try:
291327
# Send multi-line command
292-
session.write(b"for i in 1 2 3; do\n")
293-
time.sleep(0.2)
294-
session.write(b"echo $i\n")
295-
time.sleep(0.2)
296-
session.write(b"done\n")
328+
session.write(b"for i in 1 2 3; do echo $i; done\n")
297329
time.sleep(0.5)
298330

299-
output = session.read(timeout=2.0)
300-
assert output is not None
301-
output_str = output.decode('utf-8', errors='ignore')
331+
# Read all output chunks
332+
all_output = b''
333+
for _ in range(5):
334+
output = session.read(timeout=0.5)
335+
if output:
336+
all_output += output
337+
else:
338+
break
339+
340+
assert all_output
341+
output_str = all_output.decode('utf-8', errors='ignore')
302342
# Should see the numbers
303-
assert '1' in output_str and '2' in output_str
343+
assert '1' in output_str and '2' in output_str and '3' in output_str
304344

305345
finally:
306346
session.close()

0 commit comments

Comments
 (0)