@@ -4,9 +4,11 @@ use duct::cmd;
44use log:: warn;
55use serde_json;
66use std:: collections:: HashSet ;
7+ use std:: io:: { BufRead , BufReader } ;
78use std:: net:: TcpListener ;
89use std:: path:: { Path , PathBuf } ;
9- use std:: sync:: Mutex ;
10+ use std:: process:: { Command , Stdio } ;
11+ use std:: sync:: { Arc , Mutex } ;
1012use std:: thread;
1113use std:: time:: { Duration , Instant } ;
1214use std:: { env, fs} ;
@@ -239,6 +241,7 @@ pub enum ServerState {
239241 handle : thread:: JoinHandle < ( ) > ,
240242 compose_file : PathBuf ,
241243 project : String ,
244+ logs : Option < Arc < Mutex < String > > > ,
242245 } ,
243246}
244247
@@ -247,6 +250,16 @@ impl ServerState {
247250 Self :: start_with_output ( start_mode, args, None )
248251 }
249252
253+ fn drain_logs ( & self ) -> String {
254+ match self {
255+ ServerState :: Docker { logs : Some ( logs) , .. } => {
256+ let mut guard = logs. lock ( ) . expect ( "logs lock poisoned" ) ;
257+ std:: mem:: take ( & mut * guard)
258+ }
259+ _ => String :: new ( ) ,
260+ }
261+ }
262+
250263 fn start_with_output ( start_mode : StartServer , args : & mut Vec < String > , output : Option < & mut String > ) -> Result < Self > {
251264 // TODO: Currently the server output leaks. We should be capturing it and only printing if the test fails.
252265
@@ -266,31 +279,110 @@ impl ServerState {
266279 let server_url = format ! ( "http://localhost:{server_port}" ) ;
267280 args. push ( server_url. clone ( ) ) ;
268281 let compose_str = compose_file. to_string_lossy ( ) . to_string ( ) ;
282+ let logs: Option < Arc < Mutex < String > > > = output. as_ref ( ) . map ( |_| Arc :: new ( Mutex :: new ( String :: new ( ) ) ) ) ;
269283
270284 let handle = thread:: spawn ( {
271285 let project = project. clone ( ) ;
286+ let logs = logs. clone ( ) ;
272287 move || {
273- let _ = cmd ! (
274- "docker" ,
275- "compose" ,
276- "-f" ,
277- & compose_str,
278- "--project-name" ,
279- & project,
280- "up" ,
281- "--abort-on-container-exit" ,
282- )
283- . env ( "STDB_PORT" , server_port. to_string ( ) )
284- . env ( "STDB_PG_PORT" , pg_port. to_string ( ) )
285- . env ( "STDB_TRACY_PORT" , tracy_port. to_string ( ) )
286- . run ( ) ;
288+ if let Some ( logs) = logs {
289+ let mut child = match Command :: new ( "docker" )
290+ . args ( [
291+ "compose" ,
292+ "-f" ,
293+ & compose_str,
294+ "--project-name" ,
295+ & project,
296+ "up" ,
297+ "--abort-on-container-exit" ,
298+ ] )
299+ . env ( "STDB_PORT" , server_port. to_string ( ) )
300+ . env ( "STDB_PG_PORT" , pg_port. to_string ( ) )
301+ . env ( "STDB_TRACY_PORT" , tracy_port. to_string ( ) )
302+ . stdout ( Stdio :: piped ( ) )
303+ . stderr ( Stdio :: piped ( ) )
304+ . spawn ( )
305+ {
306+ Ok ( child) => child,
307+ Err ( e) => {
308+ let mut guard = logs. lock ( ) . expect ( "logs lock poisoned" ) ;
309+ guard. push_str ( & format ! ( "failed to spawn docker compose: {e}\n " ) ) ;
310+ return ;
311+ }
312+ } ;
313+
314+ let stdout = child. stdout . take ( ) ;
315+ let stderr = child. stderr . take ( ) ;
316+
317+ let stdout_handle = stdout. map ( |stdout| {
318+ let logs = logs. clone ( ) ;
319+ thread:: spawn ( move || {
320+ let mut reader = BufReader :: new ( stdout) ;
321+ let mut line = String :: new ( ) ;
322+ loop {
323+ line. clear ( ) ;
324+ match reader. read_line ( & mut line) {
325+ Ok ( 0 ) => break ,
326+ Ok ( _) => {
327+ let mut guard = logs. lock ( ) . expect ( "logs lock poisoned" ) ;
328+ guard. push_str ( & line) ;
329+ }
330+ Err ( _) => break ,
331+ }
332+ }
333+ } )
334+ } ) ;
335+
336+ let stderr_handle = stderr. map ( |stderr| {
337+ let logs = logs. clone ( ) ;
338+ thread:: spawn ( move || {
339+ let mut reader = BufReader :: new ( stderr) ;
340+ let mut line = String :: new ( ) ;
341+ loop {
342+ line. clear ( ) ;
343+ match reader. read_line ( & mut line) {
344+ Ok ( 0 ) => break ,
345+ Ok ( _) => {
346+ let mut guard = logs. lock ( ) . expect ( "logs lock poisoned" ) ;
347+ guard. push_str ( & line) ;
348+ }
349+ Err ( _) => break ,
350+ }
351+ }
352+ } )
353+ } ) ;
354+
355+ let _ = child. wait ( ) ;
356+ if let Some ( h) = stdout_handle {
357+ let _ = h. join ( ) ;
358+ }
359+ if let Some ( h) = stderr_handle {
360+ let _ = h. join ( ) ;
361+ }
362+ } else {
363+ let _ = cmd ! (
364+ "docker" ,
365+ "compose" ,
366+ "-f" ,
367+ & compose_str,
368+ "--project-name" ,
369+ & project,
370+ "up" ,
371+ "--abort-on-container-exit" ,
372+ )
373+ . env ( "STDB_PORT" , server_port. to_string ( ) )
374+ . env ( "STDB_PG_PORT" , pg_port. to_string ( ) )
375+ . env ( "STDB_TRACY_PORT" , tracy_port. to_string ( ) )
376+ . run ( ) ;
377+ }
287378 }
288379 } ) ;
289380 wait_until_http_ready ( Duration :: from_secs ( 900 ) , & server_url) ?;
290381 Ok ( ServerState :: Docker {
291382 handle,
292383 compose_file,
293384 project,
385+ logs,
294386 } )
295387 }
296388 StartServer :: Yes => {
@@ -426,12 +518,15 @@ fn run_smoketests_batch_captured(server_mode: StartServer, args: &[String], pyth
426518 }
427519
428520 if !res. status . success ( ) {
521+ output. push_str ( & _server. drain_logs ( ) ) ;
429522 return (
430523 output,
431524 Err ( anyhow:: anyhow!( "smoketests exited with status: {}" , res. status) ) ,
432525 ) ;
433526 }
434527
528+ output. push_str ( & _server. drain_logs ( ) ) ;
529+
435530 ( output, Ok ( ( ) ) )
436531}
437532
@@ -806,6 +901,7 @@ fn main() -> Result<()> {
806901 } ) => {
807902 let start_server = server_start_config ( start_server, docker. clone ( ) ) ;
808903 if matches ! ( start_server, StartServer :: Yes { .. } ) {
904+ // TODO: This seems to rebuild a ton, even if we recently ran cargo build. Figure out why..
809905 println ! ( "Building SpacetimeDB.." ) ;
810906
811907 // Pre-build so that `cargo run -p spacetimedb-cli` will immediately start. Otherwise we risk timing out waiting for the server to come up.
0 commit comments