@@ -33,6 +33,7 @@ def verify_env_check(
3333 expect_rows : Optional [int ] = None ,
3434 expect_cols : Optional [int ] = None ,
3535 ) -> Result :
36+ """Verify that probe reported correct environment."""
3637 msgs = self ._sb .get_messages (session_id = session_id , msg_type = "env_check" )
3738 if not msgs :
3839 return Result (False , f"No env_check message from session { session_id } " )
@@ -52,6 +53,7 @@ def verify_env_check(
5253 return Result (True , "env_check OK" , data = msg )
5354
5455 def verify_pgrp_isolation (self , sessions : list [str ]) -> Result :
56+ """Verify that probes in different panes have different process groups."""
5557 pgrps = {}
5658 for sid in sessions :
5759 msgs = self ._sb .get_messages (session_id = sid , msg_type = "env_check" )
@@ -68,6 +70,7 @@ def verify_signal_delivery(
6870 session_id : str ,
6971 signal_name : str ,
7072 ) -> Result :
73+ """Verify that probe received a specific signal."""
7174 msgs = self ._sb .get_messages (session_id = session_id , msg_type = "signal" )
7275 matching = [m for m in msgs if m .get ("signal" ) == signal_name ]
7376 if not matching :
@@ -84,9 +87,13 @@ def verify_signal_isolation(
8487 inactive : list [str ],
8588 signal : str ,
8689 ) -> Result :
90+ """Verify signal was received by active pane but NOT by inactive panes."""
91+ # Active should have it
8792 active_result = self .verify_signal_delivery (active , signal )
8893 if not active_result .passed :
8994 return Result (False , f"Active pane { active } did not receive { signal } " )
95+
96+ # Inactive should not
9097 for sid in inactive :
9198 msgs = self ._sb .get_messages (session_id = sid , msg_type = "signal" )
9299 matching = [m for m in msgs if m .get ("signal" ) == signal ]
@@ -99,20 +106,53 @@ def verify_signal_isolation(
99106 return Result (True , f"{ signal } isolation OK: { active } got it, { inactive } did not" )
100107
101108 def verify_output_token (self , session_id : str ) -> Result :
102- msgs = self ._sb .get_messages (session_id = session_id , msg_type = "output_token" )
103- if not msgs :
104- return Result (False , f"No output_token from { session_id } " )
105- token = msgs [0 ]["token" ]
106- raw = self ._driver .read_raw (timeout = 2.0 )
107- if token .encode () in raw :
108- return Result (True , "output_token passthrough OK" , data = {"token" : token })
109+ """Verify that probe's output token appeared in client byte stream.
110+
111+ 1. Wait for probe to emit a fresh token via sideband (proves probe alive).
112+ 2. Read client output and check if that token traversed the PTY pipeline.
113+
114+ Probe sends sideband first then stdout, so by the time we see the
115+ sideband message, the token is already in the PTY/client buffer.
116+ """
117+ import time as _time
118+ old_count = len (self ._sb .get_messages (
119+ session_id = session_id , msg_type = "output_token" ))
120+ # Step 1: wait for a fresh sideband token (probe emits every ~1s)
121+ deadline = _time .monotonic () + 3.0
122+ new_msgs = []
123+ while _time .monotonic () < deadline :
124+ new_msgs = self ._sb .get_messages (
125+ session_id = session_id , msg_type = "output_token" )[old_count :]
126+ if new_msgs :
127+ break
128+ _time .sleep (0.2 )
129+ if not new_msgs :
130+ return Result (False ,
131+ f"No new output_token from probe within 3s "
132+ f"(had { old_count } before)" )
133+ # Step 2: read client output in a loop — under high-throughput
134+ # scenarios, read_raw may return early (pexpect shrinks timeout
135+ # after the first chunk), so retry until the token appears or we
136+ # hit the overall deadline.
137+ raw = b""
138+ read_deadline = _time .monotonic () + 5.0
139+ while _time .monotonic () < read_deadline :
140+ raw += self ._driver .read_raw (timeout = 1.0 )
141+ for m in reversed (new_msgs ):
142+ if m ["token" ].encode () in raw :
143+ return Result (True , "output_token passthrough OK" ,
144+ data = {"token" : m ["token" ]})
145+ _time .sleep (0.1 )
109146 return Result (
110147 False ,
111- f"Token { token } not found in client output ({ len (raw )} bytes read)" ,
112- data = {"token" : token , "raw_len" : len (raw )},
148+ f"Token { new_msgs [- 1 ]['token' ]} not found in client output "
149+ f"({ len (raw )} bytes read, { len (new_msgs )} new tokens from sideband)" ,
150+ data = {"token" : new_msgs [- 1 ]["token" ], "raw_len" : len (raw ),
151+ "new_token_count" : len (new_msgs )},
113152 )
114153
115154 def verify_input_token (self , session_id : str ) -> Result :
155+ """Send a token via client stdin and verify probe received it."""
116156 token = secrets .token_hex (16 )
117157 self ._driver .send_line (token )
118158
@@ -128,3 +168,48 @@ def verify_input_token(self, session_id: str) -> Result:
128168 f"Probe did not report receiving token { token } " ,
129169 data = {"sent" : token , "received_msgs" : msgs },
130170 )
171+
172+ def verify_file_contains (self , file_path : str , pattern : str ) -> Result :
173+ """Check that a file exists and contains the given pattern."""
174+ if not os .path .exists (file_path ):
175+ return Result (False , f"File not found: { file_path } " )
176+ with open (file_path , "r" , errors = "replace" ) as f :
177+ content = f .read ()
178+ if pattern in content :
179+ return Result (True , f"Pattern found in { file_path } " )
180+ return Result (False , f"Pattern '{ pattern } ' not in { file_path } ({ len (content )} bytes)" )
181+
182+ def verify_fd_count (self , pid : int , max_fds : int ) -> Result :
183+ """Check that a process has at most max_fds open file descriptors."""
184+ try :
185+ proc = psutil .Process (pid )
186+ fds = proc .num_fds ()
187+ except (psutil .NoSuchProcess , psutil .AccessDenied ) as e :
188+ return Result (False , f"Cannot check fds: { e } " )
189+ passed = fds <= max_fds
190+ return Result (passed , f"fd count: { fds } (max { max_fds } )" , data = {"fds" : fds })
191+
192+ def verify_winsize_update (
193+ self ,
194+ session_id : str ,
195+ rows : int ,
196+ cols : int ,
197+ ) -> Result :
198+ """After resize, verify probe got SIGWINCH and updated winsize."""
199+ # Check for SIGWINCH
200+ sig_result = self .verify_signal_delivery (session_id , "SIGWINCH" )
201+ if not sig_result .passed :
202+ return Result (False , "No SIGWINCH received after resize" )
203+ # Check latest env_check for updated winsize
204+ msgs = self ._sb .get_messages (session_id = session_id , msg_type = "env_check" )
205+ if not msgs :
206+ return Result (False , "No env_check after SIGWINCH" )
207+ latest = msgs [- 1 ]
208+ ws = latest .get ("winsize" , {})
209+ if ws .get ("rows" ) == rows and ws .get ("cols" ) == cols :
210+ return Result (True , f"winsize updated to { rows } x{ cols } " , data = latest )
211+ return Result (
212+ False ,
213+ f"Expected { rows } x{ cols } , got { ws .get ('rows' )} x{ ws .get ('cols' )} " ,
214+ data = latest ,
215+ )
0 commit comments