diff --git a/rust/prepare-test-instance.sh b/rust/prepare-test-instance.sh new file mode 100755 index 0000000..5d5164e --- /dev/null +++ b/rust/prepare-test-instance.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +cd $( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +set -o nounset + +export SQLX_OFFLINE=true + +exec cargo build diff --git a/rust/run-test-instance.sh b/rust/run-test-instance.sh index acdfca9..5667166 100755 --- a/rust/run-test-instance.sh +++ b/rust/run-test-instance.sh @@ -1,13 +1,13 @@ #!/usr/bin/env bash -sleep 100 +cd $( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + set -o nounset port="${1}" db="$(mktemp)" -export SQLX_OFFLINE=true export DATABASE_URL="sqlite://${db}" -cargo run -- --port "${port}" +exec ./target/debug/packager --port "${port}" diff --git a/rust/src/components/trip/mod.rs b/rust/src/components/trip/mod.rs index a4cd15e..7adf87a 100644 --- a/rust/src/components/trip/mod.rs +++ b/rust/src/components/trip/mod.rs @@ -39,6 +39,17 @@ impl From for &'static str { } } +fn trip_state_icon(state: &TripState) -> &'static str { + match state { + TripState::Init => "mdi-magic-staff", + TripState::Planning => "mdi-text-box-outline", + TripState::Planned => "mdi-clock-outline", + TripState::Active => "mdi-play", + TripState::Review => "mdi-magnify", + TripState::Done => "mdi-check", + } +} + pub struct TripTable; impl TripTable { @@ -66,10 +77,15 @@ impl TripTable { @for trip in trips { tr ."h-10" ."even:bg-gray-100" ."hover:bg-purple-100" ."h-full" { (TripTableRow::build(trip.id, &trip.name)) - (TripTableRow::build(trip.id, trip.date_start)) - (TripTableRow::build(trip.id, trip.date_end)) + (TripTableRow::build(trip.id, trip.date_start.to_string())) + (TripTableRow::build(trip.id, trip.date_end.to_string())) (TripTableRow::build(trip.id, (trip.date_end - trip.date_start).whole_days())) - (TripTableRow::build(trip.id, trip.state)) + (TripTableRow::build(trip.id, html!( + span .flex .flex-row .items-center { + span ."mdi" .(trip_state_icon(&trip.state)) ."text-xl" ."mr-2" {} + span { (trip.state) } + } + ))) } } } @@ -81,7 +97,7 @@ impl TripTable { pub struct TripTableRow; impl TripTableRow { - pub fn build(trip_id: Uuid, value: impl std::fmt::Display) -> Markup { + pub fn build(trip_id: Uuid, value: impl maud::Render) -> Markup { html!( td ."border" ."p-0" ."m-0" { a ."inline-block" ."p-2" ."m-0" ."w-full" @@ -441,7 +457,12 @@ impl TripInfo { )) tr .h-full { td ."border" ."p-2" { "State" } - td ."border" ."p-2" { (trip.state) } + td ."border" { + span .flex .flex-row .items-center .justify-start ."gap-2" { + span ."mdi" .(trip_state_icon(&trip.state)) ."text-2xl" ."pl-2" {} + span ."pr-2" ."py-2" { (trip.state) } + } + } @let prev_state = trip.state.prev(); @let next_state = trip.state.next(); diff --git a/rust/test/tests/main.rs b/rust/test/tests/main.rs index e624a99..21750c0 100644 --- a/rust/test/tests/main.rs +++ b/rust/test/tests/main.rs @@ -6,7 +6,10 @@ use std::thread; use thirtyfour::common::capabilities::firefox::FirefoxPreferences; use thirtyfour::{FirefoxCapabilities, WebDriver}; +use std::time; + use std::future::Future; +use std::process::{Command, Stdio}; const PORT: u16 = 3001; const BASEURL: &'static str = "http://localhost"; @@ -31,6 +34,14 @@ impl From for TestError { } } +impl From for TestError { + fn from(error: std::io::Error) -> Self { + Self::AppError { + message: error.to_string(), + } + } +} + fn random_name() -> String { let mut rng = rand::thread_rng(); let length = { 1..=20 }.choose(&mut rng).unwrap(); @@ -49,27 +60,105 @@ where R: Future>, { let event_in_parent = Arc::new((Mutex::new(false), Condvar::new())); - let event_in_subprocess = Arc::clone(&event_in_parent); - let app = thread::spawn(move || { - use std::process::Command; - // panic!(); - let script = concat!(env!("CARGO_MANIFEST_DIR"), "/run-test-instance.sh"); + let prepared_event_in_parent = Arc::new((Mutex::new(false), Condvar::new())); + let prepared_event_in_subprocess = Arc::clone(&prepared_event_in_parent); - println!("starting script {script}"); - let mut handle = Command::new(script).arg(PORT.to_string()).spawn().unwrap(); + let app = thread::spawn(move || -> Result<(), TestError> { + { + let script = concat!(env!("CARGO_MANIFEST_DIR"), "/../prepare-test-instance.sh"); - let (lock, cvar) = &*event_in_subprocess; - let mut done = lock.lock().unwrap(); - while !*done { - done = cvar.wait(done).unwrap(); + println!("[sub] starting prepare script {script}"); + let handle_prepare = Command::new(script) + .stdin(Stdio::null()) + // .stdout(Stdio::null()) + // .stderr(Stdio::null()) + .output()?; + + assert!(handle_prepare.status.success()); + println!("[sub] preparation ok"); + } + + let mut handle_gecko = { + let script = concat!(env!("CARGO_MANIFEST_DIR"), "/../run-test-instance.sh"); + + println!("[sub] starting script {script}"); + let handle = Command::new("geckodriver") + .stdin(Stdio::null()) + // .stdout(Stdio::null()) + // .stderr(Stdio::null()) + .spawn()?; + println!("[sub] geckodriver started"); + handle + }; + + { + println!("[sub] sending prepared event"); + let (lock, cvar) = &*prepared_event_in_subprocess; + let mut prepared = lock.lock().unwrap(); + *prepared = true; + cvar.notify_all(); + println!("[sub] sent prepared event"); + } + + let mut handle_app = { + let script = concat!(env!("CARGO_MANIFEST_DIR"), "/../run-test-instance.sh"); + + println!("[sub] starting script {script}"); + let handle = Command::new(script) + .arg(PORT.to_string()) + .stdin(Stdio::null()) + // .stdout(Stdio::null()) + // .stderr(Stdio::null()) + .spawn()?; + println!("[sub] app started"); + handle + }; + + { + let (lock, cvar) = &*event_in_subprocess; + let mut done = lock.lock().unwrap(); + while !*done { + println!("[sub] waiting for done event"); + done = cvar.wait(done).unwrap(); + } + println!("[sub] done received"); } // at worst, the child already exited, so we don't care about the // return code - let _ = handle.kill(); + println!("[sub] killing app subprocess"); + let _ = handle_app.kill()?; + handle_app.wait()?; + println!("[sub] killed app subprocess"); + + println!("[sub] killing gecko subprocess"); + let _ = handle_gecko.kill()?; + handle_gecko.wait()?; + println!("[sub] killed gecko subprocess"); + + println!("[sub] done"); + + Ok(()) }); + + thread::spawn(move || { + let (lock, cvar) = &*prepared_event_in_parent; + let mut prepared = lock.lock().unwrap(); + while !*prepared { + println!("waiting for prepared event"); + prepared = cvar.wait(prepared).unwrap(); + } + println!("prepared received"); + }) + .join() + .unwrap(); + + println!("preparations done, starting tests in 1s"); + + thread::sleep(time::Duration::from_secs(1)); + let prefs = FirefoxPreferences::new(); let mut caps = FirefoxCapabilities::new(); caps.set_preferences(prefs)?; @@ -79,20 +168,35 @@ where // cloning works for closing, so it's good enough let driver_handle = driver.clone(); + // call the actual function + println!("calling test function"); let result = inner(driver).await; + // let result: Result<(), TestError> = Ok(()); + println!("test function done"); + println!("deinitializing selenium driver"); driver_handle.quit().await?; - // if the child panicked and cannot receive the event, we don't care and just - // exit - let (lock, cvar) = &*event_in_parent; - let mut done = lock.lock().unwrap(); - *done = true; - cvar.notify_one(); + // this has to be done in a separate thread, otherwise the condvar handling + // does not work. it's not sure why. + thread::spawn(move || { + // if the child panicked and cannot receive the event, we don't care and just + // continue to the exit + println!("sending done event"); + let (lock, cvar) = &*event_in_parent; + let mut done = lock.lock().unwrap(); + *done = true; + cvar.notify_all(); + println!("sent done event"); + }) + .join() + .unwrap(); - let _ = app.join().map_err(|_| TestError::AppError { + println!("waiting for subprocess"); + app.join().map_err(|_| TestError::AppError { message: "app panicked".to_string(), - }); + })??; + println!("all done"); Ok(result?) } @@ -145,6 +249,31 @@ async fn check_table( #[tokio::test] async fn test() -> Result<(), TestError> { + let mut handle = { + let script = concat!(env!("CARGO_MANIFEST_DIR"), "/../run-test-instance.sh"); + + println!("[sub] starting script {script}"); + let handle = Command::new(script) + .arg(PORT.to_string()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()?; + println!("[sub] started"); + handle + }; + + // at worst, the child already exited, so we don't care about the + // return code + println!("[sub] killing subprocess"); + let _ = handle.kill().expect("failed to kill child"); + handle.wait().unwrap(); + println!("[sub] killed subprocess"); + + println!("[sub] done"); + + // return Ok(()); + run_test(|driver: WebDriver| async move { for js_enabled in [true] { driver.goto(url("/")).await?;