* ft makepad_test * Improve run handling, manifest parsing, and stdout newline Replace dynamic free-port lookup with an ephemeral localhost SocketAddr in test runtime and remove the unused find_free_listen_address helper. Ensure headless stdout messages end with a newline. Simplify send_to_app error handling and add a test that queued bootstrap messages are delivered once an app socket connects. Substantially enhance process_manager: unify cargo flag parsing, parse Cargo.toml to determine package/bin targets, resolve the correct binary name for direct stdio runs, and build the cargo/build+exec script from the resolved args. Add unit tests for manifest parsing and script generation and adjust related call sites. * test harness * Preserve test attrs; return Vec for gateway binds In the test macro (libs/makepad_test/macros/src/lib.rs) preserve wrapper-only attributes (ignore and should_panic) on the generated wrapper test while removing them from the inner function. Added Attribute import, is_wrapper_only_test_attr helper, adjusted attribute filtering and emission, and added unit tests to verify attribute placement and expansion. In the hub (studio/hub/src/hub.rs) change gateway_bind_candidates to return a Vec<SocketAddr> instead of an iterator and special-case ephemeral port 0 to preserve ephemeral binding; otherwise collect the range of candidate ports into a Vec. Added tests to validate candidate behavior. Also minor formatting/whitespace tweaks and a small IPv6 formatting adjustment. * Add visible Studio mode and remote client Enable running UI tests visibly through a running Makepad Studio. Adds a new makepad-network dependency and studio_remote client (libs/makepad_test/src/studio_remote.rs) and integrates it into the runtime via a TestConnection enum. Introduces visible-mode tooling: env vars (MAKEPAD_TEST_VISIBLE, MAKEPAD_TEST_STUDIO, MAKEPAD_TEST_STUDIO_MOUNT, MAKEPAD_TEST_STARTUP_DELAY_MS, MAKEPAD_TEST_ACTION_DELAY_MS, MAKEPAD_TEST_KEEP_OPEN_MS), pacing/delays after actions, and pause-before-shutdown. Splits startup into start_headless_app/start_visible_app, clears existing visible builds before launching, and updates tests, docs (GUIDE.md, README.md), and selector/runtime minor cleanups/formatting.
131 lines
4.2 KiB
Rust
131 lines
4.2 KiB
Rust
use makepad_live_id::LiveId;
|
|
use makepad_micro_serde::{DeBin, SerBin};
|
|
use makepad_script_std::makepad_network::{
|
|
HttpMethod, HttpRequest, NetworkConfig, NetworkResponse, NetworkRuntime, WsMessage, WsSend,
|
|
};
|
|
use makepad_studio_hub::{HubConfig, MountConfig, StudioHub};
|
|
use makepad_studio_protocol::hub_protocol::{
|
|
ClientId, ClientToHub, ClientToHubEnvelope, HubToClient, QueryId,
|
|
};
|
|
use std::fs;
|
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener};
|
|
use std::time::{Duration, Instant};
|
|
|
|
fn find_free_port() -> Option<u16> {
|
|
let listener = TcpListener::bind(("127.0.0.1", 0)).ok()?;
|
|
Some(listener.local_addr().ok()?.port())
|
|
}
|
|
|
|
fn wait_for_event<F>(
|
|
runtime: &NetworkRuntime,
|
|
timeout: Duration,
|
|
mut matcher: F,
|
|
) -> Option<NetworkResponse>
|
|
where
|
|
F: FnMut(&NetworkResponse) -> bool,
|
|
{
|
|
let deadline = Instant::now() + timeout;
|
|
while Instant::now() < deadline {
|
|
if let Some(event) = runtime.recv_timeout(Duration::from_millis(50)) {
|
|
if matcher(&event) {
|
|
return Some(event);
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn wait_for_ws_binary(runtime: &NetworkRuntime, socket_id: LiveId, timeout: Duration) -> Vec<u8> {
|
|
let event = wait_for_event(runtime, timeout, |event| {
|
|
matches!(
|
|
event,
|
|
NetworkResponse::WsMessage {
|
|
socket_id: id,
|
|
message: WsMessage::Binary(_)
|
|
} if *id == socket_id
|
|
)
|
|
})
|
|
.expect("did not receive ws binary message");
|
|
|
|
match event {
|
|
NetworkResponse::WsMessage {
|
|
message: WsMessage::Binary(data),
|
|
..
|
|
} => data,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn websocket_ui_hello_and_load_file_tree_roundtrip() {
|
|
let dir = makepad_studio_hub::test_support::tempdir().unwrap();
|
|
fs::create_dir_all(dir.path().join("src")).unwrap();
|
|
fs::write(dir.path().join("src/lib.rs"), "fn main() {}\n").unwrap();
|
|
|
|
let Some(port) = find_free_port() else {
|
|
return;
|
|
};
|
|
let config = HubConfig {
|
|
listen_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port),
|
|
post_max_size: 1024 * 1024,
|
|
mounts: vec![MountConfig {
|
|
name: "repo".to_string(),
|
|
path: dir.path().to_path_buf(),
|
|
}],
|
|
enable_in_process_gateway: false,
|
|
};
|
|
|
|
let _backend = match StudioHub::start_headless(config) {
|
|
Ok(v) => v,
|
|
Err(err) => {
|
|
if err.contains("failed to bind") {
|
|
return;
|
|
}
|
|
panic!("start backend failed: {}", err);
|
|
}
|
|
};
|
|
|
|
let runtime = NetworkRuntime::new(NetworkConfig::default());
|
|
let socket_id = LiveId::from_str("studio2.backend.gateway.test");
|
|
let request = HttpRequest::new(format!("ws://127.0.0.1:{port}/ui"), HttpMethod::GET);
|
|
if runtime.ws_open(socket_id, request).is_err() {
|
|
return;
|
|
}
|
|
|
|
let opened = wait_for_event(
|
|
&runtime,
|
|
Duration::from_secs(3),
|
|
|event| matches!(event, NetworkResponse::WsOpened { socket_id: id } if *id == socket_id),
|
|
);
|
|
assert!(opened.is_some(), "did not receive WsOpened");
|
|
|
|
let hello_bin = wait_for_ws_binary(&runtime, socket_id, Duration::from_secs(3));
|
|
let hello = HubToClient::deserialize_bin(&hello_bin).expect("decode hello");
|
|
let client_id = match hello {
|
|
HubToClient::Hello { client_id } => client_id,
|
|
other => panic!("expected hello, got {:?}", other),
|
|
};
|
|
assert_ne!(client_id, ClientId(u16::MAX));
|
|
|
|
let envelope = ClientToHubEnvelope {
|
|
query_id: QueryId::new(client_id, 0),
|
|
msg: ClientToHub::LoadFileTree {
|
|
mount: "repo".to_string(),
|
|
},
|
|
};
|
|
runtime
|
|
.ws_send(socket_id, WsSend::Binary(envelope.serialize_bin()))
|
|
.expect("ws_send");
|
|
|
|
let tree_bin = wait_for_ws_binary(&runtime, socket_id, Duration::from_secs(3));
|
|
let tree_msg = HubToClient::deserialize_bin(&tree_bin).expect("decode tree");
|
|
match tree_msg {
|
|
HubToClient::FileTree { mount, data } => {
|
|
assert_eq!(mount, "repo");
|
|
assert!(data.nodes.iter().any(|n| n.path == "repo/src/lib.rs"));
|
|
}
|
|
other => panic!("expected FileTree, got {:?}", other),
|
|
}
|
|
|
|
let _ = runtime.ws_close(socket_id);
|
|
}
|