mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-17 02:04:13 +00:00
Use real desktop stack in tests
This commit is contained in:
parent
5e20011fd1
commit
153480256a
8 changed files with 305 additions and 435 deletions
|
|
@ -53,12 +53,25 @@ pub struct TestApp {
|
|||
container_id: String,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TestAppOptions {
|
||||
pub env: BTreeMap<String, String>,
|
||||
pub replace_path: bool,
|
||||
}
|
||||
|
||||
impl TestApp {
|
||||
pub fn new(auth: AuthConfig) -> Self {
|
||||
Self::with_setup(auth, |_| {})
|
||||
}
|
||||
|
||||
pub fn with_setup<F>(auth: AuthConfig, setup: F) -> Self
|
||||
where
|
||||
F: FnOnce(&Path),
|
||||
{
|
||||
Self::with_options(auth, TestAppOptions::default(), setup)
|
||||
}
|
||||
|
||||
pub fn with_options<F>(auth: AuthConfig, options: TestAppOptions, setup: F) -> Self
|
||||
where
|
||||
F: FnOnce(&Path),
|
||||
{
|
||||
|
|
@ -69,7 +82,7 @@ impl TestApp {
|
|||
|
||||
let container_id = unique_container_id();
|
||||
let image = ensure_test_image();
|
||||
let env = build_env(&layout, &auth);
|
||||
let env = build_env(&layout, &auth, &options);
|
||||
let mounts = build_mounts(root.path(), &env);
|
||||
let base_url = run_container(&container_id, &image, &mounts, &env, &auth);
|
||||
|
||||
|
|
@ -191,7 +204,11 @@ fn ensure_test_image() -> String {
|
|||
.clone()
|
||||
}
|
||||
|
||||
fn build_env(layout: &TestLayout, auth: &AuthConfig) -> BTreeMap<String, String> {
|
||||
fn build_env(
|
||||
layout: &TestLayout,
|
||||
auth: &AuthConfig,
|
||||
options: &TestAppOptions,
|
||||
) -> BTreeMap<String, String> {
|
||||
let mut env = BTreeMap::new();
|
||||
env.insert(
|
||||
"HOME".to_string(),
|
||||
|
|
@ -240,20 +257,35 @@ fn build_env(layout: &TestLayout, auth: &AuthConfig) -> BTreeMap<String, String>
|
|||
env.insert("SANDBOX_AGENT_TEST_AUTH_TOKEN".to_string(), token.clone());
|
||||
}
|
||||
|
||||
let mut custom_path_entries = custom_path_entries(layout.install_dir.parent().expect("install base"));
|
||||
custom_path_entries.extend(explicit_path_entries());
|
||||
custom_path_entries.sort();
|
||||
custom_path_entries.dedup();
|
||||
|
||||
if custom_path_entries.is_empty() {
|
||||
env.insert("PATH".to_string(), DEFAULT_PATH.to_string());
|
||||
if options.replace_path {
|
||||
env.insert(
|
||||
"PATH".to_string(),
|
||||
options.env.get("PATH").cloned().unwrap_or_default(),
|
||||
);
|
||||
} else {
|
||||
let joined = custom_path_entries
|
||||
.iter()
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(":");
|
||||
env.insert("PATH".to_string(), format!("{joined}:{DEFAULT_PATH}"));
|
||||
let mut custom_path_entries =
|
||||
custom_path_entries(layout.install_dir.parent().expect("install base"));
|
||||
custom_path_entries.extend(explicit_path_entries());
|
||||
custom_path_entries.sort();
|
||||
custom_path_entries.dedup();
|
||||
|
||||
if custom_path_entries.is_empty() {
|
||||
env.insert("PATH".to_string(), DEFAULT_PATH.to_string());
|
||||
} else {
|
||||
let joined = custom_path_entries
|
||||
.iter()
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(":");
|
||||
env.insert("PATH".to_string(), format!("{joined}:{DEFAULT_PATH}"));
|
||||
}
|
||||
}
|
||||
|
||||
for (key, value) in &options.env {
|
||||
if key == "PATH" {
|
||||
continue;
|
||||
}
|
||||
env.insert(key.clone(), value.clone());
|
||||
}
|
||||
|
||||
env
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ use reqwest::{Method, StatusCode};
|
|||
use sandbox_agent::router::AuthConfig;
|
||||
use serde_json::{json, Value};
|
||||
use serial_test::serial;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[path = "support/docker.rs"]
|
||||
mod docker_support;
|
||||
|
|
@ -21,15 +20,6 @@ struct EnvVarGuard {
|
|||
previous: Option<std::ffi::OsString>,
|
||||
}
|
||||
|
||||
struct FakeDesktopEnv {
|
||||
_temp: TempDir,
|
||||
_path: EnvVarGuard,
|
||||
_xdg_state_home: EnvVarGuard,
|
||||
_assume_linux: EnvVarGuard,
|
||||
_display_num: EnvVarGuard,
|
||||
_fake_state_dir: EnvVarGuard,
|
||||
}
|
||||
|
||||
impl EnvVarGuard {
|
||||
fn set(key: &'static str, value: &str) -> Self {
|
||||
let previous = std::env::var_os(key);
|
||||
|
|
@ -97,153 +87,6 @@ exit 0
|
|||
);
|
||||
}
|
||||
|
||||
fn setup_fake_desktop_env() -> FakeDesktopEnv {
|
||||
let temp = tempfile::tempdir().expect("create fake desktop tempdir");
|
||||
let bin_dir = temp.path().join("bin");
|
||||
let xdg_state_home = temp.path().join("state");
|
||||
let fake_state_dir = temp.path().join("desktop-state");
|
||||
fs::create_dir_all(&bin_dir).expect("create fake desktop bin dir");
|
||||
fs::create_dir_all(&xdg_state_home).expect("create xdg state home");
|
||||
fs::create_dir_all(&fake_state_dir).expect("create fake state dir");
|
||||
|
||||
write_executable(
|
||||
&bin_dir.join("Xvfb"),
|
||||
r#"#!/usr/bin/env sh
|
||||
set -eu
|
||||
display="${1:-:99}"
|
||||
number="${display#:}"
|
||||
socket="/tmp/.X11-unix/X${number}"
|
||||
mkdir -p /tmp/.X11-unix
|
||||
touch "$socket"
|
||||
cleanup() {
|
||||
rm -f "$socket"
|
||||
exit 0
|
||||
}
|
||||
trap cleanup INT TERM EXIT
|
||||
while :; do
|
||||
sleep 1
|
||||
done
|
||||
"#,
|
||||
);
|
||||
|
||||
write_executable(
|
||||
&bin_dir.join("openbox"),
|
||||
r#"#!/usr/bin/env sh
|
||||
set -eu
|
||||
trap 'exit 0' INT TERM
|
||||
while :; do
|
||||
sleep 1
|
||||
done
|
||||
"#,
|
||||
);
|
||||
|
||||
write_executable(
|
||||
&bin_dir.join("dbus-launch"),
|
||||
r#"#!/usr/bin/env sh
|
||||
set -eu
|
||||
echo "DBUS_SESSION_BUS_ADDRESS=unix:path=/tmp/sandbox-agent-test-bus"
|
||||
echo "DBUS_SESSION_BUS_PID=$$"
|
||||
"#,
|
||||
);
|
||||
|
||||
write_executable(
|
||||
&bin_dir.join("xrandr"),
|
||||
r#"#!/usr/bin/env sh
|
||||
set -eu
|
||||
cat <<'EOF'
|
||||
Screen 0: minimum 1 x 1, current 1440 x 900, maximum 32767 x 32767
|
||||
EOF
|
||||
"#,
|
||||
);
|
||||
|
||||
write_executable(
|
||||
&bin_dir.join("import"),
|
||||
r#"#!/usr/bin/env sh
|
||||
set -eu
|
||||
printf '\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\000\001\000\000\000\001\010\006\000\000\000\037\025\304\211\000\000\000\013IDATx\234c\000\001\000\000\005\000\001\r\n-\264\000\000\000\000IEND\256B`\202'
|
||||
"#,
|
||||
);
|
||||
|
||||
write_executable(
|
||||
&bin_dir.join("xdotool"),
|
||||
r#"#!/usr/bin/env sh
|
||||
set -eu
|
||||
state_dir="${SANDBOX_AGENT_DESKTOP_FAKE_STATE_DIR:?missing fake state dir}"
|
||||
state_file="${state_dir}/mouse"
|
||||
mkdir -p "$state_dir"
|
||||
if [ ! -f "$state_file" ]; then
|
||||
printf '0 0\n' > "$state_file"
|
||||
fi
|
||||
|
||||
read_state() {
|
||||
read -r x y < "$state_file"
|
||||
}
|
||||
|
||||
write_state() {
|
||||
printf '%s %s\n' "$1" "$2" > "$state_file"
|
||||
}
|
||||
|
||||
command="${1:-}"
|
||||
case "$command" in
|
||||
getmouselocation)
|
||||
read_state
|
||||
printf 'X=%s\nY=%s\nSCREEN=0\nWINDOW=0\n' "$x" "$y"
|
||||
;;
|
||||
mousemove)
|
||||
shift
|
||||
x="${1:-0}"
|
||||
y="${2:-0}"
|
||||
shift 2 || true
|
||||
while [ "$#" -gt 0 ]; do
|
||||
token="$1"
|
||||
shift
|
||||
case "$token" in
|
||||
mousemove)
|
||||
x="${1:-0}"
|
||||
y="${2:-0}"
|
||||
shift 2 || true
|
||||
;;
|
||||
mousedown|mouseup)
|
||||
shift 1 || true
|
||||
;;
|
||||
click)
|
||||
if [ "${1:-}" = "--repeat" ]; then
|
||||
shift 2 || true
|
||||
fi
|
||||
shift 1 || true
|
||||
;;
|
||||
esac
|
||||
done
|
||||
write_state "$x" "$y"
|
||||
;;
|
||||
type|key)
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
"#,
|
||||
);
|
||||
|
||||
let original_path = std::env::var_os("PATH").unwrap_or_default();
|
||||
let mut paths = vec![bin_dir];
|
||||
paths.extend(std::env::split_paths(&original_path));
|
||||
let merged_path = std::env::join_paths(paths).expect("join PATH");
|
||||
|
||||
FakeDesktopEnv {
|
||||
_temp: temp,
|
||||
_path: EnvVarGuard::set_os("PATH", merged_path.as_os_str()),
|
||||
_xdg_state_home: EnvVarGuard::set_os("XDG_STATE_HOME", xdg_state_home.as_os_str()),
|
||||
_assume_linux: EnvVarGuard::set("SANDBOX_AGENT_DESKTOP_TEST_ASSUME_LINUX", "1"),
|
||||
_display_num: EnvVarGuard::set("SANDBOX_AGENT_DESKTOP_DISPLAY_NUM", "190"),
|
||||
_fake_state_dir: EnvVarGuard::set_os(
|
||||
"SANDBOX_AGENT_DESKTOP_FAKE_STATE_DIR",
|
||||
fake_state_dir.as_os_str(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn serve_registry_once(document: Value) -> String {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").expect("bind registry server");
|
||||
let address = listener.local_addr().expect("registry address");
|
||||
|
|
@ -335,6 +178,34 @@ async fn send_request_raw(
|
|||
(status, headers, bytes.to_vec())
|
||||
}
|
||||
|
||||
async fn launch_desktop_focus_window(app: &docker_support::DockerApp, display: &str) {
|
||||
let command = r#"nohup xterm -geometry 80x24+40+40 -title 'Sandbox Desktop Test' -e sh -lc 'sleep 60' >/tmp/sandbox-agent-xterm.log 2>&1 < /dev/null & for _ in $(seq 1 50); do wid="$(xdotool search --onlyvisible --name 'Sandbox Desktop Test' 2>/dev/null | head -n 1 || true)"; if [ -n "$wid" ]; then xdotool windowactivate "$wid"; exit 0; fi; sleep 0.1; done; exit 1"#;
|
||||
let (status, _, body) = send_request(
|
||||
app,
|
||||
Method::POST,
|
||||
"/v1/processes/run",
|
||||
Some(json!({
|
||||
"command": "sh",
|
||||
"args": ["-lc", command],
|
||||
"env": {
|
||||
"DISPLAY": display,
|
||||
},
|
||||
"timeoutMs": 10_000
|
||||
})),
|
||||
&[],
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
status,
|
||||
StatusCode::OK,
|
||||
"unexpected desktop focus window launch response: {}",
|
||||
String::from_utf8_lossy(&body)
|
||||
);
|
||||
let parsed = parse_json(&body);
|
||||
assert_eq!(parsed["exitCode"], 0);
|
||||
}
|
||||
|
||||
fn parse_json(bytes: &[u8]) -> Value {
|
||||
if bytes.is_empty() {
|
||||
Value::Null
|
||||
|
|
|
|||
|
|
@ -1,14 +1,25 @@
|
|||
use super::*;
|
||||
use serial_test::serial;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn v1_desktop_status_reports_install_required_when_dependencies_are_missing() {
|
||||
let temp = tempfile::tempdir().expect("create empty path tempdir");
|
||||
let _path = EnvVarGuard::set_os("PATH", temp.path().as_os_str());
|
||||
let _assume_linux = EnvVarGuard::set("SANDBOX_AGENT_DESKTOP_TEST_ASSUME_LINUX", "1");
|
||||
let mut env = BTreeMap::new();
|
||||
env.insert(
|
||||
"PATH".to_string(),
|
||||
temp.path().to_string_lossy().to_string(),
|
||||
);
|
||||
|
||||
let test_app = TestApp::new(AuthConfig::disabled());
|
||||
let test_app = TestApp::with_options(
|
||||
AuthConfig::disabled(),
|
||||
docker_support::TestAppOptions {
|
||||
env,
|
||||
replace_path: true,
|
||||
},
|
||||
|_| {},
|
||||
);
|
||||
|
||||
let (status, _, body) =
|
||||
send_request(&test_app.app, Method::GET, "/v1/desktop/status", None, &[]).await;
|
||||
|
|
@ -29,8 +40,7 @@ async fn v1_desktop_status_reports_install_required_when_dependencies_are_missin
|
|||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn v1_desktop_lifecycle_and_actions_work_with_fake_runtime() {
|
||||
let _fake = setup_fake_desktop_env();
|
||||
async fn v1_desktop_lifecycle_and_actions_work_with_real_runtime() {
|
||||
let test_app = TestApp::new(AuthConfig::disabled());
|
||||
|
||||
let (status, _, body) = send_request(
|
||||
|
|
@ -184,6 +194,8 @@ async fn v1_desktop_lifecycle_and_actions_work_with_fake_runtime() {
|
|||
assert_eq!(position["x"], 220);
|
||||
assert_eq!(position["y"], 230);
|
||||
|
||||
launch_desktop_focus_window(&test_app.app, &display).await;
|
||||
|
||||
let (status, _, body) = send_request(
|
||||
&test_app.app,
|
||||
Method::POST,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue