makepad/libs/flow/tests/engine_publish.rs
Arena Agent d8d5ea8b6a Repair integration manifests and port nigig exports onto upstream widget families
Restore missing required asset/flow packages from the pinned nigig revision; retain upstream script isolation, X11 hover, and widget-core fixes. Validated makepad-widgets data feature with Rust 1.97.1.
2026-10-26 11:23:26 +00:00

152 lines
5.9 KiB
Rust

#![cfg(not(target_arch = "wasm32"))]
use makepad_asset_client::ApiEndpoints;
use makepad_flow::engine::executors::publish::{
AssetListQuery, AssetStoreConfig, AssetWorker, PublishExecutor,
};
use makepad_flow::engine::executors::{Executor, Poll};
use makepad_flow::{Literal, Loc, Node, Port, PortType, Value};
use std::path::PathBuf;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
struct Roots(Vec<PathBuf>);
impl Roots {
fn new() -> Self { Self(Vec::new()) }
fn make(&mut self, label: &str) -> PathBuf {
let nonce = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos();
let path = std::env::temp_dir().join(format!("flow-publish-{}-{label}-{nonce}", std::process::id()));
std::fs::create_dir_all(&path).unwrap();
self.0.push(path.clone());
path
}
}
impl Drop for Roots {
fn drop(&mut self) {
for path in &self.0 { let _ = std::fs::remove_dir_all(path); }
}
}
fn start_store(root: PathBuf) -> (makepad_asset_store::AssetServer, String) {
let mut config = makepad_asset_store::ServerConfig::new(root.clone());
config.control_addr = "127.0.0.1:0".parse().unwrap();
config.data_addr = "127.0.0.1:0".parse().unwrap();
config.bootstrap_admin = true;
config.discovery = None;
config.log = false;
let server = makepad_asset_store::AssetServer::start(config).unwrap();
let token = std::fs::read_to_string(root.join("admin-token")).unwrap().trim().to_string();
(server, token)
}
fn node() -> Node {
Node {
id: "library".into(),
kind: "publish".into(),
type_name: "Publish".into(),
params: vec![
("title".into(), Literal::Str("Sunset result".into())),
("namespace".into(), Literal::Str("flows".into())),
("tags".into(), Literal::Arr(vec![Literal::Str("flow".into()), Literal::Str("sunset".into())])),
("description".into(), Literal::Str("painted from a prompt".into())),
("alias".into(), Literal::Str("flows/sunset-result".into())),
],
inputs: Vec::new(),
outputs: vec![Port { name: "asset".into(), ty: PortType::Json }],
at: None,
size: None,
flip: false,
loc: Loc::default(),
fn_src: None,
face_src: None,
on_fail: "fail".into(),
label: None,
domain: None,
doc: None,
}
}
fn picture(fill: u8) -> Value {
let mut pixels = vec![fill; 256 * 256 * 4];
for alpha in pixels.iter_mut().skip(3).step_by(4) { *alpha = 255; }
let png = makepad_ai_hub::testpattern::encode_png_rgba(&pixels, 256, 256).unwrap();
Value::media(PortType::Image, "image/png", png)
}
fn publish(worker: &AssetWorker, value: Value) -> makepad_strict_json::Value {
let mut executor = PublishExecutor::new(
Some(worker.handle()),
"prompt-to-library".into(),
"instance-1".into(),
String::new(),
);
executor.start(&node(), &[("value".into(), value)]).unwrap();
let deadline = Instant::now() + Duration::from_secs(10);
loop {
match executor.poll() {
Poll::Done(outputs) => {
let value = outputs.into_iter().find(|(port, _)| port == "asset").unwrap().1;
return makepad_strict_json::parse(&value.bytes).unwrap();
}
Poll::Failed(error) => panic!("publish failed: {error}"),
_ if Instant::now() < deadline => std::thread::sleep(Duration::from_millis(20)),
other => panic!("publish timed out after {other:?}"),
}
}
}
#[test]
fn picture_publish_and_alias_republish_create_revisions() {
let mut roots = Roots::new();
let (mut server, token) = start_store(roots.make("store"));
let config = AssetStoreConfig {
archive_outputs: false,
cache_dir: roots.make("cache"),
token: Some(token),
endpoints: Some(ApiEndpoints { control: server.control_addr(), data: server.data_addr() }),
server_id: Some(server.server_id()),
discovery_port: 0,
discovery_wait_ms: 0,
};
let mut worker = AssetWorker::start(config).unwrap();
let first = publish(&worker, picture(30));
let second = publish(&worker, picture(90));
assert_eq!(first.get("id").and_then(|v| v.as_str()), second.get("id").and_then(|v| v.as_str()));
assert_ne!(first.get("revision").and_then(|v| v.as_str()), second.get("revision").and_then(|v| v.as_str()));
assert_eq!(second.get("alias").and_then(|v| v.as_str()), Some("flows/sunset-result"));
assert_eq!(second.get("namespace").and_then(|v| v.as_str()), Some("flows"));
assert_eq!(second.get("title").and_then(|v| v.as_str()), Some("Sunset result"));
assert_eq!(second.get("kind").and_then(|v| v.as_str()), Some("texture"));
let rows = worker.handle().list(AssetListQuery { text: String::new(), namespace: Some("flows".into()), limit: 10, cursor: None }).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].title, "Sunset result");
assert_eq!(rows[0].namespace, "flows");
assert_eq!(rows[0].tags, vec!["flow", "sunset"]);
worker.stop();
server.shutdown();
}
#[test]
fn missing_store_failure_names_discovery() {
let mut roots = Roots::new();
let mut config = AssetStoreConfig::new(roots.make("cache"));
config.discovery_wait_ms = 0;
let mut worker = AssetWorker::start(config).unwrap();
let mut executor = PublishExecutor::new(Some(worker.handle()), "demo".into(), "one".into(), String::new());
executor.start(&node(), &[("value".into(), picture(10))]).unwrap();
let deadline = Instant::now() + Duration::from_secs(2);
loop {
match executor.poll() {
Poll::Failed(error) => {
assert!(error.contains("no asset server discovered on this LAN"), "{error}");
break;
}
_ if Instant::now() < deadline => std::thread::sleep(Duration::from_millis(10)),
_ => panic!("missing store failure timed out"),
}
}
worker.stop();
}