33
44//! Portable sandbox lifecycle conformance scenarios.
55
6- use std:: time:: Duration ;
6+ use std:: collections:: HashSet ;
7+ use std:: time:: { Duration , Instant } ;
78
89use serde:: Deserialize ;
910
@@ -16,10 +17,17 @@ const TRANSITION_INTERVAL: Duration = Duration::from_secs(2);
1617
1718#[ derive( Debug , Deserialize ) ]
1819struct SandboxState {
20+ id : String ,
1921 name : String ,
2022 phase : String ,
2123}
2224
25+ #[ derive( Debug , Deserialize ) ]
26+ struct SandboxListPage {
27+ sandboxes : Vec < SandboxState > ,
28+ next_page_token : String ,
29+ }
30+
2331/// Certify sandbox stop, start, and deletion lifecycle behavior.
2432pub const SANDBOX_LIFECYCLE_SCENARIO : Scenario = Scenario {
2533 name : "sandbox-lifecycle" ,
@@ -118,9 +126,10 @@ async fn stopped_can_be_deleted(runner: &mut OpenShellRunner) -> Result<(), Stri
118126 . await ?;
119127
120128 run_lifecycle_command ( runner, "stop" , & sandbox_name, "stopped-delete/stop" ) . await ?;
121- wait_for_phase ( runner, & sandbox_name, "Stopped" , "stopped-delete/stopped" ) . await ?;
129+ let sandbox =
130+ wait_for_phase ( runner, & sandbox_name, "Stopped" , "stopped-delete/stopped" ) . await ?;
122131 run_lifecycle_command ( runner, "delete" , & sandbox_name, "stopped-delete/delete" ) . await ?;
123- wait_for_absence ( runner, & sandbox_name, "stopped-delete/deleted" ) . await ?;
132+ wait_for_absence ( runner, & sandbox . id , & sandbox_name, "stopped-delete/deleted" ) . await ?;
124133 runner. forget_sandbox ( & sandbox_name) ;
125134 Ok ( ( ) )
126135}
@@ -151,7 +160,9 @@ async fn create_running_sandbox(
151160 . await
152161 . map_err ( |error| error. to_string ( ) ) ?;
153162 create. require_success ( ) ?;
154- wait_for_phase ( runner, sandbox_name, "Ready" , & format ! ( "{step}/ready" ) ) . await
163+ wait_for_phase ( runner, sandbox_name, "Ready" , & format ! ( "{step}/ready" ) )
164+ . await
165+ . map ( |_| ( ) )
155166}
156167
157168async fn run_lifecycle_command (
@@ -199,7 +210,7 @@ async fn wait_for_phase(
199210 sandbox_name : & str ,
200211 expected_phase : & str ,
201212 step : & str ,
202- ) -> Result < ( ) , String > {
213+ ) -> Result < SandboxState , String > {
203214 let sandbox_name = sandbox_name. to_string ( ) ;
204215 let expected_phase = expected_phase. to_string ( ) ;
205216 let step = step. to_string ( ) ;
@@ -229,7 +240,7 @@ async fn wait_for_phase(
229240 "sandbox get returned {:?}; expected '{sandbox_name}'" ,
230241 state. name
231242 ) ) ,
232- Ok ( state) if state. phase == expected_phase => Poll :: Ready ( ( ) ) ,
243+ Ok ( state) if state. phase == expected_phase => Poll :: Ready ( state ) ,
233244 Ok ( state) => Poll :: Pending ( format ! (
234245 "sandbox '{sandbox_name}' phase is {:?}; expected {expected_phase:?}" ,
235246 state. phase
@@ -246,9 +257,11 @@ async fn wait_for_phase(
246257
247258async fn wait_for_absence (
248259 runner : & mut OpenShellRunner ,
260+ sandbox_id : & str ,
249261 sandbox_name : & str ,
250262 step : & str ,
251263) -> Result < ( ) , String > {
264+ let sandbox_id = sandbox_id. to_string ( ) ;
252265 let sandbox_name = sandbox_name. to_string ( ) ;
253266 let step = step. to_string ( ) ;
254267 let poll_step = step. clone ( ) ;
@@ -257,22 +270,79 @@ async fn wait_for_absence(
257270 & poll_step,
258271 TRANSITION_TIMEOUT ,
259272 TRANSITION_INTERVAL ,
260- async move |runner| {
261- let result = runner
262- . step ( format ! ( "{step}/get" ) )
263- . description ( format ! ( "sandbox '{sandbox_name}' is no longer retrievable" ) )
264- . with_timeout ( COMMAND_TIMEOUT )
265- . run ( & [ "sandbox" , "get" , & sandbox_name, "--output" , "json" ] )
266- . await ;
267- match result {
268- Ok ( result) if !result. success ( ) => Poll :: Ready ( ( ) ) ,
269- Ok ( _) => {
270- Poll :: Pending ( format ! ( "sandbox '{sandbox_name}' is still retrievable" ) )
271- }
272- Err ( error) => Poll :: Pending ( error. to_string ( ) ) ,
273- }
273+ async move |runner| match sandbox_is_listed ( runner, & sandbox_id, & sandbox_name, & step)
274+ . await
275+ {
276+ Ok ( false ) => Poll :: Ready ( ( ) ) ,
277+ Ok ( true ) => Poll :: Pending ( format ! (
278+ "sandbox '{sandbox_name}' with ID '{sandbox_id}' is still listed"
279+ ) ) ,
280+ Err ( error) => Poll :: Pending ( error) ,
274281 } ,
275282 )
276283 . await
277284 . map_err ( |error| error. to_string ( ) )
278285}
286+
287+ async fn sandbox_is_listed (
288+ runner : & OpenShellRunner ,
289+ sandbox_id : & str ,
290+ sandbox_name : & str ,
291+ step : & str ,
292+ ) -> Result < bool , String > {
293+ let deadline = Instant :: now ( ) + COMMAND_TIMEOUT ;
294+ let mut seen_page_tokens = HashSet :: new ( ) ;
295+ let mut page_token = String :: new ( ) ;
296+ let mut page = 0u32 ;
297+ loop {
298+ let remaining = deadline. saturating_duration_since ( Instant :: now ( ) ) ;
299+ if remaining. is_zero ( ) {
300+ return Err ( format ! (
301+ "sandbox list observation for '{sandbox_name}' exceeded its {COMMAND_TIMEOUT:?} deadline"
302+ ) ) ;
303+ }
304+
305+ let result = runner
306+ . step ( format ! ( "{step}/list/{page}" ) )
307+ . description ( format ! (
308+ "sandbox list confirms whether '{sandbox_name}' with ID '{sandbox_id}' still exists"
309+ ) )
310+ . with_timeout ( remaining)
311+ . run ( & [
312+ "sandbox" ,
313+ "list" ,
314+ "--page-size" ,
315+ "1000" ,
316+ "--page-token" ,
317+ & page_token,
318+ "--output" ,
319+ "json" ,
320+ ] )
321+ . await
322+ . map_err ( |error| error. to_string ( ) ) ?;
323+ result. require_success ( ) ?;
324+
325+ let response = result
326+ . json :: < SandboxListPage > ( )
327+ . map_err ( |error| error. to_string ( ) ) ?;
328+ if response
329+ . sandboxes
330+ . iter ( )
331+ . any ( |sandbox| sandbox. id == sandbox_id)
332+ {
333+ return Ok ( true ) ;
334+ }
335+ if response. next_page_token . is_empty ( ) {
336+ return Ok ( false ) ;
337+ }
338+ if !seen_page_tokens. insert ( response. next_page_token . clone ( ) ) {
339+ return Err ( format ! (
340+ "sandbox list returned a repeated page token while looking for '{sandbox_name}'"
341+ ) ) ;
342+ }
343+ page_token = response. next_page_token ;
344+ page = page
345+ . checked_add ( 1 )
346+ . ok_or_else ( || "sandbox list page counter overflowed" . to_string ( ) ) ?;
347+ }
348+ }
0 commit comments