agent-desktop/crates/ffi/tests/error_lifetime.rs
Lahfir 3f322728b4
feat!: implement Playwright-grade foundation contract
Settle the Playwright-grade reliability contract in agent-desktop-core
before the Windows/Linux adapters are built, so they inherit it instead
of redesigning it. Every command now observes, waits, verifies, and
reports honestly instead of firing blindly.

Highlights: capability-supertrait split of PlatformAdapter with
not_supported() defaults; canonical role/state vocabulary with live
`is --property visible`; display enumeration (`list-displays`) and honest
`--screen` with scale factor; truthful Automation permission; `native_id`
identity spine; window-id-first resolution; serializable `LocatorQuery`
with live `find`; default-on auto-wait before every ref action; three-way
`hit_test` occlusion gate; `scroll_into_view` in core; core accessible-name
precedence; typed `ActionStep` delivery tier; `ProcessState` and
`APP_UNRESPONSIVE`; `LaunchOptions`; baseline-diff desktop signals
(`wait --event`); typed clipboard (`Text`/`Image`/`FileUrls`); mouse
modifier chords and `mouse-wheel`. Hardened through a 35-reviewer pass with
independent validation and a green live e2e gate (109/0), plus a
head-vs-main performance comparison harness.

BREAKING CHANGE: default-on auto-wait changes the timing of every
previously-untouched ref-action call (bounded 5000 ms default; `--timeout-ms 0`
restores single-shot). `ENVELOPE_VERSION` is now `2.1` (adds the
`APP_UNRESPONSIVE` code and process state in error details). FFI ABI major
is `3` (append-only struct evolution; `wait --event` is intentionally not
exposed over FFI). The legacy string clipboard API is removed in favor of
typed content. `key-down`/`key-up` fail closed until daemon-owned held input
exists. `close-app` verifies termination and the osascript fallback path is
removed. `--text` matching is subtree containment: `find --text X --first`
returns the outermost matching container.
2026-07-20 00:21:38 -07:00

53 lines
1.9 KiB
Rust

use agent_desktop_ffi::error::AdResult;
use std::ffi::CStr;
#[allow(improper_ctypes)]
unsafe extern "C" {
fn ad_adapter_create() -> *mut agent_desktop_ffi::AdAdapter;
fn ad_adapter_destroy(adapter: *mut agent_desktop_ffi::AdAdapter);
fn ad_launch_app(
adapter: *const agent_desktop_ffi::AdAdapter,
id: *const std::os::raw::c_char,
timeout_ms: u64,
out: *mut agent_desktop_ffi::AdWindowInfo,
) -> AdResult;
fn ad_last_error_message() -> *const std::os::raw::c_char;
fn ad_last_error_details() -> *const std::os::raw::c_char;
fn ad_last_error_code() -> AdResult;
}
/// Any failure populates last-error and the pointer stays stable across later
/// successful calls on the same thread.
#[test]
fn last_error_pointer_survives_across_successful_calls() {
unsafe {
let adapter = ad_adapter_create();
assert!(!adapter.is_null());
let bad_id = std::ptr::null();
let mut out_win: agent_desktop_ffi::AdWindowInfo = std::mem::zeroed();
let rc = ad_launch_app(adapter, bad_id, 0, &mut out_win);
assert_eq!(rc, AdResult::ErrInvalidArgs);
let first_msg_ptr = ad_last_error_message();
let first_details_ptr = ad_last_error_details();
assert!(!first_msg_ptr.is_null());
assert!(first_details_ptr.is_null());
let first_msg = CStr::from_ptr(first_msg_ptr).to_string_lossy().into_owned();
for _ in 0..10 {
let next = ad_adapter_create();
assert!(!next.is_null());
ad_adapter_destroy(next);
}
let later_msg_ptr = ad_last_error_message();
assert_eq!(first_msg_ptr, later_msg_ptr);
assert_eq!(first_details_ptr, ad_last_error_details());
let later_msg = CStr::from_ptr(later_msg_ptr).to_string_lossy().into_owned();
assert_eq!(first_msg, later_msg);
assert_eq!(ad_last_error_code(), rc);
ad_adapter_destroy(adapter);
}
}