Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion crates/botloftd/src/platform/unix/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,16 @@ fn login_environment() -> Option<Vars> {
let _ = done.send(bytes);
});
let bytes = output.recv_timeout(SHELL_TIMEOUT).ok();
if bytes.is_none() {
let _ = job.terminate();
}
// The pipe closes a moment before the process counts as exited: killing
// the group right at end of output could turn a clean exit into SIGKILL.
// With the output complete, `env` is exiting; wait for it, then sweep
// whatever the profile left behind.
let status = child.wait().ok();
let _ = job.terminate();
let status = child.wait().ok()?;
let status = status?;
let vars = parse(&bytes?)?;
status.success().then_some(vars)
}
Expand Down
9 changes: 7 additions & 2 deletions crates/botloftd/tests/browser_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,16 @@ async fn until_window(b: &mut Browsing, size: &str) -> String {
panic!("the window never became {size}:\n{page}");
}

/// The next frame that is `height` tall.
/// The next frame that is `height` tall, in what it says and in its pixels.
/// Right after the page is resized the browser can still send a frame drawn
/// for the old surface under the new size's metadata (a 1000 x 700 page in a
/// 1000 x 560 picture, seen on Chromium 154 on Linux); the next repaint is
/// right.
async fn frame_of(app: &mut Client, height: u64) -> Value {
for _ in 0..50 {
let frame = app.notification("browser.frame").await;
if frame["height"] == height {
let (width, tall) = jpeg_size(frame["data"].as_str().expect("picture"));
if frame["height"] == height && frame["width"] == width && u64::from(tall) == height {
return frame;
}
}
Expand Down
18 changes: 18 additions & 0 deletions crates/botloftd/tests/common/browsing.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! The browser tests' shared setup (spec 21): a bot in Manual mode, a local
//! site, and helpers to call tools in the background and answer requests.

use std::sync::OnceLock;

use serde_json::{Value, json};
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
use tokio::task::JoinHandle;

use super::bots::ready_bot;
Expand All @@ -14,6 +17,19 @@ pub struct Browsing {
pub bot: Value,
pub mcp: Mcp,
pub site: String,
/// Held until the test ends, after the daemon has closed its browser.
_slot: OwnedSemaphorePermit,
}

/// Real browsers a test binary has open at once. A dozen cold Chromium
/// profiles starting together on a small CI runner could pass the daemon's
/// 30 s start limit, so the tests wait their turn instead.
const BROWSERS_AT_ONCE: usize = 3;

async fn browser_slot() -> OwnedSemaphorePermit {
static SLOTS: OnceLock<std::sync::Arc<Semaphore>> = OnceLock::new();
let slots = SLOTS.get_or_init(|| std::sync::Arc::new(Semaphore::new(BROWSERS_AT_ONCE)));
slots.clone().acquire_owned().await.expect("open semaphore")
}

/// A bot in Manual mode and a local site, or `None` without Edge.
Expand All @@ -22,6 +38,7 @@ pub async fn setup() -> Option<Browsing> {
eprintln!("Microsoft Edge is not installed; skipping");
return None;
}
let slot = browser_slot().await;
// A real browser can be slow to ask on a busy CI runner.
let t = TestDaemon::start_supervised_patient().await;
let mut app = t.session().await;
Expand All @@ -37,6 +54,7 @@ pub async fn setup() -> Option<Browsing> {
bot,
mcp,
site,
_slot: slot,
})
}

Expand Down
Loading