mod common;
use std::env;
use std::process::{Command, Stdio};
use std::thread;
use std::time::Duration;
use maa_framework::agent_client::AgentClient;
use maa_framework::agent_server::AgentServer;
use maa_framework::context::Context;
use maa_framework::controller::Controller;
use maa_framework::custom::{CustomAction, CustomRecognition};
use maa_framework::resource::Resource;
use maa_framework::tasker::Tasker;
use maa_framework::{self, sys};
use common::{ImageController, get_test_resources_dir, init_test_env};
struct ServerRecognition;
impl CustomRecognition for ServerRecognition {
fn analyze(
&self,
context: &Context,
_task_id: sys::MaaTaskId,
node_name: &str,
custom_recognition_name: &str,
_custom_recognition_param: &str,
_image: &maa_framework::buffer::MaaImageBuffer,
_roi: &maa_framework::common::Rect,
) -> Option<(maa_framework::common::Rect, String)> {
println!(
"[Server] MyRecognition analyze called! node: {}, reco: {}",
node_name, custom_recognition_name
);
let ppover = r#"{
"ColorMatch": {
"recognition": "ColorMatch",
"lower": [100, 100, 100],
"upper": [255, 255, 255],
"action": "Click"
}
}"#;
let run_result = context.run_task("ColorMatch", ppover);
println!(" [Server] run_task result: {:?}", run_result);
if let Ok(new_ctx) = context.clone_context() {
println!(" [Server] clone_context: OK");
let override_result = new_ctx.override_pipeline(r#"{"TaskA": {}, "TaskB": {}}"#);
println!(" [Server] override_pipeline result: {:?}", override_result);
let node_data = new_ctx.get_node_data(node_name);
println!(
" [Server] get_node_data({}) is_some: {}",
node_name,
node_data.as_ref().map_or(false, |o| o.is_some())
);
let set_result = new_ctx.set_anchor("test_anchor", "TaskA");
println!(" [Server] set_anchor result: {:?}", set_result);
let anchor = new_ctx.get_anchor("test_anchor");
println!(" [Server] get_anchor result: {:?}", anchor);
let hit_count = new_ctx.get_hit_count(node_name);
println!(" [Server] get_hit_count({}) = {:?}", node_name, hit_count);
let clear_result = new_ctx.clear_hit_count(node_name);
println!(" [Server] clear_hit_count result: {:?}", clear_result);
let task_job = new_ctx.get_task_job();
let task_detail = task_job.get(false);
println!(
" [Server] get_task_job entry: {:?}",
task_detail
.as_ref()
.ok()
.and_then(|o| o.as_ref().map(|d| d.entry.clone()))
);
}
let task_id = context.task_id();
println!(" [Server] context.task_id: {}", task_id);
Some((
maa_framework::common::Rect {
x: 10,
y: 20,
width: 100,
height: 200,
},
"server_verified".to_string(), ))
}
}
struct ServerAction;
impl CustomAction for ServerAction {
fn run(
&self,
_context: &Context,
_task_id: sys::MaaTaskId,
_node_name: &str,
_custom_action_name: &str,
_custom_action_param: &str,
_reco_id: sys::MaaRecoId,
_box_rect: &maa_framework::common::Rect,
) -> bool {
println!("[Server] MyAction run called!");
true
}
}
fn agent_server_main() {
eprintln!("[Server] Starting...");
let identifier = env::var("MAA_AGENT_IDENTIFIER")
.unwrap_or_else(|_| "MaaAgentTest_MultiProcess".to_string());
if let Err(e) = AgentServer::start_up(&identifier) {
eprintln!("[Server] Failed to start AgentServer: {:?}", e);
panic!("Server failed to start");
}
eprintln!("[Server] Registering callbacks...");
AgentServer::register_custom_recognition("MyRec", Box::new(ServerRecognition)).unwrap();
AgentServer::register_custom_action("MyAct", Box::new(ServerAction)).unwrap();
eprintln!("[Server] Running. Waiting for client...");
loop {
thread::sleep(Duration::from_millis(100));
}
}
#[test]
fn test_agent_full_integration() {
if env::var("MAA_AGENT_TEST_MODE").unwrap_or_default() == "SERVER" {
agent_server_main();
return;
}
println!("\n=== test_agent_full_integration (Client) ===");
init_test_env().unwrap();
let current_exe = env::current_exe().expect("Failed to get current exe path");
println!("Spawning server: {:?}", current_exe);
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis()
% 10000000;
let identifier = format!("MAT_{}", ts);
let mut server_process = Command::new(¤t_exe)
.arg("test_agent_full_integration")
.arg("--nocapture")
.env("MAA_AGENT_TEST_MODE", "SERVER")
.env("MAA_AGENT_IDENTIFIER", &identifier)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.expect("Failed to spawn server process");
thread::sleep(Duration::from_millis(500));
let mut client = AgentClient::new(Some(&identifier)).expect("Failed to create AgentClient");
let res_dir = get_test_resources_dir();
let resource_dir = res_dir.join("resource");
let screenshot_dir = res_dir.join("Screenshot");
assert!(resource_dir.exists());
let resource = Resource::new().unwrap();
resource
.post_bundle(resource_dir.to_str().unwrap())
.unwrap()
.wait();
client
.bind(resource.clone())
.expect("Failed to bind resource");
let img_ctrl = ImageController::new(screenshot_dir);
let controller = Controller::new_custom(img_ctrl).unwrap();
let conn_id = controller.post_connection().unwrap();
controller.wait(conn_id);
thread::sleep(Duration::from_millis(1000));
if let Ok(Some(status)) = server_process.try_wait() {
if status.success() {
println!("AgentServer library is missing or skipped. Skipping test.");
return;
}
panic!("AgentServer process crashed! Exit status: {}", status);
}
println!("Connecting to agent...");
let mut connected = false;
for i in 0..20 {
if let Ok(Some(status)) = server_process.try_wait() {
if status.success() {
println!("AgentServer library is missing or skipped. Skipping test.");
return;
}
panic!("AgentServer process crashed! Exit status: {}", status);
}
if client.connect().is_ok() {
connected = true;
println!("Connected on attempt {}", i + 1);
break;
}
thread::sleep(Duration::from_millis(500));
}
if !connected {
let _ = server_process.kill();
let _ = server_process.wait();
panic!("Failed to connect to AgentServer after retries");
}
assert!(client.connected(), "Agent must be connected");
let tasker = Tasker::new().unwrap();
tasker.bind_resource(&resource).unwrap();
tasker.bind_controller(&controller).unwrap();
let ppover = r#"{
"TestEntry": {"next": ["TestNode"]},
"TestNode": {
"recognition": "Custom",
"custom_recognition": "MyRec",
"action": "Custom",
"custom_action": "MyAct"
}
}"#;
let job = tasker
.post_task("TestEntry", ppover)
.expect("post_task failed");
let status = job.wait();
println!("Task status: {:?}", status);
let detail = job.get(true).expect("Task failed to produce detail");
let detail_str = format!("{:?}", detail);
println!("Task Detail: {}", detail_str);
let mut recognition_detail_found = false;
if let Some(d) = detail {
println!("Iterating node_id_list: {:?}", d.node_id_list);
for &node_id in &d.node_id_list {
match tasker.get_node_detail(node_id) {
Ok(Some(node_detail)) => {
println!("Node Detail [{}]: {:?}", node_id, node_detail);
if node_detail.reco_id > 0 {
match tasker.get_recognition_detail(node_detail.reco_id) {
Ok(Some(reco_detail)) => {
println!(
"Reco Detail [{}]: {:?}",
node_detail.reco_id, reco_detail
);
let json_str = reco_detail.detail.to_string();
println!("Reco JSON: {}", json_str);
if json_str.contains("server_verified") {
recognition_detail_found = true;
break;
}
}
Ok(None) => println!("Reco Detail [{}] is None", node_detail.reco_id),
Err(e) => println!("Error getting reco detail: {:?}", e),
}
} else {
println!("Node [{}] has no reco_id", node_id);
}
}
Ok(None) => println!("Node Detail [{}] is None", node_id),
Err(e) => println!("Error getting node detail: {:?}", e),
}
}
} else {
println!("Task detail is None");
}
client.disconnect().unwrap();
let _ = server_process.kill();
let _ = server_process.wait();
if !status.succeeded() {
panic!("Task failed execution");
}
if !recognition_detail_found {
panic!(
"IPC Verification Failed: Did not find 'server_verified' in recognition details. Task Detail: {}",
detail_str
);
}
println!("PASS: agent multi-process integration");
}
#[test]
fn test_agent_client_create_tcp() {
println!("\n=== test_agent_client_create_tcp ===");
init_test_env().unwrap();
let client = AgentClient::create_tcp(0).expect("Failed to create TCP AgentClient");
println!(" TCP AgentClient created");
let identifier = client.identifier();
println!(" identifier: {:?}", identifier);
assert!(identifier.is_some(), "identifier should be Some");
let port_str = identifier.unwrap();
assert!(!port_str.is_empty(), "identifier should not be empty");
let port: u16 = port_str
.parse()
.expect("identifier should be a valid port number");
assert!(port > 0, "auto-selected port should be > 0");
println!(" auto-selected port: {}", port);
println!(" PASS: AgentClient TCP mode");
}